// SINGLE PRIM, FIVE FRAME SLIDESHOW // by // Moriash Moreau // July, 2009 // // With thanks to Gearsome Stonecutter for the original 5-Face XyText Prim Setup script. // // You may this script for whatever purpose you like, provided it's legal // and doesn't violate the SL TOS. Be excellent to each other. Party on. // // Instructions: // 1) Rez a new prim. // 2) Add at least seven texture images to the prim's inventory. // 3) Drop this script into the prim, and edit the CONFIGURATION VARIABLES below. This script will // automatically re-shape the prim as needed and start the slideshow. // // Use: Touch the middle image to pause/unpause the slideshow. Touch the left two images to skip backward // one image. Touch the right two images to skip forward one image. Note that the slideshow will only // automatically advance if an agent is within Range meters. The script will auto-detect if textures are // added or removed, and reset TexCount accordingly. No need to reset the script when new images are added. // It's a good idea to temporarily disable this script, or reset it afterward, if you're changing out the // entire texture inventory. It's assumed that all textures have the same aspect ratio, as defined // in WidthRatio, below. // // CONFIGURATION VARIABLES float FrameHeight = 0.883; // The desired height. Leave as zero if you want width to be the deciding factor. // Will need to do a little math to figure out the maximum frame sizes with differing // WidthRatios. See the note for FaceRatio, below. float FrameWidth = 1.0; // The desired overall width of a single frame. Ignored unless Height is set equal to zero. // Again, maximum FrameWidth is 1.73 meters, as we're limited to a max of 10 meters total. // See the note on FaceRatio below. float WidthRatio = 1.333333; // The desired frame width to height aspect ratio. Images intended to be 4:3 have // a WidthRatio of 1.333. 3:4 images are 0.75. Square images are 1.0. float Period = 30; // Time between image updates, in seconds. float Range = 20; // Sensor range. How close are we expecting the user to be when he looks at this? // We use a repeating sensor instead of a timer to drive our updates, and only update the // slideshow if there's someone in range. No point in adding extra update and // script lag to the simulator if there's nobody around to see it! // The max range is 96 meters, but in practical terms unless the slideshow is several meters // tall, nobody is going to see it. float HoldTime = 120; // Optional pause time when the user touches the center frame. integer Random = FALSE; // Set to TRUE if you want to display in random order. False otherwise. // INTERNAL VARIABLES integer TexCount; // Total number of images in inventory. integer Count; // Current image number in inventory, equal to the image to be shown on the leftmost face. integer FaceCount; // The number of faces. integer Paused = FALSE; // Pause status. list Order; // The display order. Will either be in alphabetical order or random, depending on state of Random variable. float FaceRatio = 5.775; // The required ratio of prim width to height in order to get square faces using this shape. // We need this factor, because when a prim is modified/tortured this much, the dimensions // are often misreported and misleading. In this case, the prim is wider than it appears onscreen. // In effect, this ratio is multiplied by the FaceWidth to get the total prim width required. // Was found to be 5.775:1 by trial-and-error, confirmed by the XyText Prim Setup script. // Accordingly, it won't be exact, due to rounding error and general eyeballing. // Should be close enough for most purposes, however. Unfortunately, this has the side // effect of reducing maximum sizes more than is intuitively obvious. With five // frames showing, our maximum frame width is (10 meters / 5.775) = 1.73 meters. // Frame height is calculated accordingly, per the WidthRatio. Keep this in mind // when working from FrameHeight. If the ratios come out wrong, you've likely picked // a height that dictates a total prim size greater than 10 meters when multiplied // by your specified WidthRatio and the FaceRatio. // Configure the slideshow maker prim with five frames. Calculate required width from height, or vice versa. Setup() { float CalcHeight; float CalcWidth; float CalcFrameWidth; if (FrameHeight == 0 && FrameWidth == 0) // Error check. We need at least one dimension variable set above. { llOwnerSay("Please set either the Width or Height variable and re-save the script. See the script comments."); return; } // If a height is specified, then we calculate the proper width to give us that height with the desired WidthRatio. else if (FrameHeight != 0) { CalcHeight = FrameHeight; CalcFrameWidth = FrameHeight * WidthRatio; CalcWidth = CalcFrameWidth * FaceRatio; } // Otherwise, do the same calculated around the specified width. else if (FrameHeight == 0) { CalcFrameWidth = FrameWidth; CalcHeight = CalcFrameWidth / WidthRatio; CalcWidth = CalcFrameWidth * FaceRatio; } // Report the size and width, in case we want to make a picture frame or whatnot. // Note that the Combined Width is NOT the prim width (the Y dimension)! When a prim is modified/tortured this much, // the dimensions are often misleading. Combined Width is the apparent width in the viewer. llOwnerSay("\nFrame Width = " + (string)CalcFrameWidth + ".\nFrame Height = " + (string)CalcHeight + ".\nCombined Width = " + (string)(CalcFrameWidth * 5) + "."); // Specifics of prim shape lifted from XyText Prim Setup script. // Setup our prim with five front faces visible, and paint the top, bottom, and sides black. llSetPrimitiveParams( [PRIM_TYPE, PRIM_TYPE_PRISM, PRIM_HOLE_DEFAULT, <0.199, 0.8, 0.0>, 0.68,ZERO_VECTOR, <1.0, 1.0, 0.0>, ZERO_VECTOR, PRIM_SIZE, <0.03, CalcWidth, CalcHeight>, // Default prim size is <0.03, 2.89, 0.5> for square face XYText display. PRIM_COLOR, 0, <0,0,0>, 1.0, // Set the top, bottom, back to black. PRIM_COLOR, 2, <0,0,0>, 1.0, PRIM_COLOR, 5, <0,0,0>, 1.0, PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0]); // Set all sides initially to blank. } // Compile a list of numbers from 0 to TexCount, the number of images in the inventory. // If the Random variable is TRUE, randomize the list. Otherwise, images will display in alphabetical order. ListOrder() { TexCount=llGetInventoryNumber(INVENTORY_TEXTURE); integer i; Order = []; for ( i = 0; i < TexCount; i++ ) { Order = Order + [i]; } if (Random) { Order = llListRandomize(Order,1); llOwnerSay("Showing " + (string)(TexCount) + " images in random order."); } else { llOwnerSay("Showing " + (string)(TexCount) + " images in sequential order."); } // Start up the update sensors. // We're going to use sensors to drive the image update instead of a timer. // There's no point in causing added server load to update the images if nobody is around to see it! llSensorRepeat("",NULL_KEY,AGENT,Range,PI,Period); } // Slide images to the left (positive adder) or right (negative) and add the next image to the end. Increment(integer adder) { Count = Count + adder; // Calculate wraparound as needed for Count. if (Count >= TexCount) { Count = 0; } else if (Count < 0) { Count = TexCount - 1; // llGetInventoryNumber is not zero indexed, but references to inventory are. Confusing! } // Set each of the five front faces, as well as the back face (2) and bottom face (5) to pre-cache the // next/previous image in line. // Texture offsets lifted from XyText Prim Setup script by Gearsome Stonecutter. // This could probably be extended to additional XyText prepared prims (using the Setup above on a remote prim, too), // using llSetLinkPrimitiveParams. Just remember to move the precaching face 2 to the linked prim, // otherwise the first image of the linked prim will disappear when the slideshow updates. // We're using llSetPrimitiveParams here, instead of llSetTexture, so we can change all the frames in one // operation instead of one-at-a-time. Given the built in script delays for each, this is much faster. llSetPrimitiveParams([ PRIM_TEXTURE, 5, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count - 1))), <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0, // Bottom face. PRIM_TEXTURE, 3, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,Count)), <2.48, 1.0, 0.0>, <-0.255989, 0.0, 0.0>, 0.0, // Front Face 1 PRIM_TEXTURE, 7, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count + 1))), <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0, // Front Face 2 PRIM_TEXTURE, 4, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count + 2))), <-14.75, 1.0, 0.0>, <0.130009, 0.0, 0.0>, 0.0, // Front Face 3 PRIM_TEXTURE, 6, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count + 3))), <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0, // Front Face 4 PRIM_TEXTURE, 1, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count + 4))), <2.48, 1.0, 0.0>, <-0.740013, 0.0, 0.0>, 0.0, // Front Face 5 PRIM_TEXTURE, 2, llGetInventoryName(INVENTORY_TEXTURE,llList2Integer(Order,WrapCheck(Count + 5))), <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0 // Back face. ]); } // Make sure we're not going over/under the texture count. Performing this check as a remote operation, since // we're calling for the addition within the list parameters of llSetPrimitiveParams. Saves us from having // to calculate Count + N in advance, just to plug it in later in the same function. integer WrapCheck (integer x) { if (x >= TexCount) // If we're over, wrap it back down to count from the beginning. { x = x - TexCount; } else if (x < 0) // If we're under, wrap around to the last image. { x = TexCount - 1; // Remember zero-index, nonzero-index disconnect. } return x; } // Return to normal operation after the pause function has ended. See below. Resume() { Paused = FALSE; llSetTimerEvent(0); llWhisper(0,"Resuming display."); Increment(1); llSensorRepeat("",NULL_KEY,AGENT,Range,PI,Period); } default { state_entry() { Setup(); // Contort the prim into the 5-face display. ListOrder(); // Sort the images. // Paste the first five images on the faces. Increment(0); } // If a user touches the frames, pause if they touch the center fram, // increment if they touch right-hand frames, decrement for left-hand frames. touch_start(integer total_number) { llSensorRemove(); //Kill the sensor. llSensorRepeat("",NULL_KEY,AGENT,Range,PI,Period); // Reset the sensor so we get full Period before update. integer Face = llDetectedTouchFace(0); if ((Face == -1) || (Face == 6) || (Face == 1)) // Right-Hand faces, and error check for old viewers. { if (Paused) { Resume(); } else { Increment(1); // Add the next image on the right-hand frame. } } else if ((Face == 3) || Face == 7) // Left-Hand faces. { if (Paused) { Resume(); } else { Increment(-1); // Go back to the just-removed image on the left-hand frame. } } else if (Face == 4) // The center face. Pause/unpause the display. { if (Paused) { Resume(); } else { llWhisper(0,"Pausing the display for " + (string)((integer)HoldTime) + " seconds. Touch anywhere to unpause. Touch the left and right sides to skip forward/back one image."); llSensorRemove(); // Remember, we're using the repeated sensor as our update timer. // Temporarily kill it in favor of a conventional timer for the pause. Paused = TRUE; llSetTimerEvent(HoldTime); } } } // If someone is in range to see it, update the slideshow. sensor(integer param) { Increment(1); } // Used for the pause function, above. timer() { Resume(); } // If someone swaps out a texture, update the count. Works best for small changes. // Large changes will likely still require a script reset, as this can get overwhelmed. // Initially, it's best to load up the textures first, then drop in the script. changed(integer change) // Something changed. { if (change & CHANGED_INVENTORY) // And it was a change to the inventory. { llSensorRemove(); ListOrder(); } } }