Open Prim Animator
<lsl> // Open Prim Animator - by Todd Borst
// This is provided AS IS without support. Please don't bug me demanding // help or custom work for this free script.
// Summary: This is a simple prim animation script. Just add this script // to your object and a dialog will automatically pop up for you to use.
// Features: // -Single script "Prim Puppeteer" like animation tool // -Playback controllable through external scripts // -Animation is scalable and resizeable // -On-touch trigger built-in // -Completely free and open sourced
// License: // You are welcome to use this anyway you like, even bring to other grids // outside of Second Life. If you try to sell what I'm giving away for // free, you will be cursed with unimaginably bad juju.
integer COMMAND_CHANNEL = 32;
integer primCount = 0; integer commandListenerHandle = -1;
list posList = []; list rotList = []; list scaleList = []; integer currentSnapshot = 0; integer recordedSnapshots = 0;
vector rootScale = ZERO_VECTOR; vector scaleChange = <1,1,1>;
// For tracking memory usage. The amount of snapshots you can record is based // on the number of prims and available memory. Less prims = more snapshots integer maxMemory = 0; integer freeMemory = 0;
integer playAnimationStyle = 0; // The values for playAnimationStyle means // 0 = no animation playing // 1 = play animation once // 2 = play animation looping
// This function is used to display a recorded snapshot showSnapshot(integer snapNumber) {
if(snapNumber > 0 && snapNumber <= recordedSnapshots )
{
integer i = 0;
vector pos;
rotation rot;
vector scale;
vector rootPos = llGetPos();
// Might want to move llGetRot() into the loop for fast rotating objects.
// Rotation during the animation playback may cause errors.
rotation rootRot = llGetRot();
//2 is the first linked prim number.
for( i = 2; i <= primCount; i++)
{
pos = llList2Vector(posList,((snapNumber-1)*(primCount-1))+(i-2));
rot = llList2Rot(rotList,((snapNumber-1)*(primCount-1))+(i-2));
scale = llList2Vector(scaleList,((snapNumber-1)*(primCount-1))+(i-2));
//Adjust for scale changes
if( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
{
pos.x *= scaleChange.x;
pos.y *= scaleChange.y;
pos.z *= scaleChange.z;
scale.x *= scaleChange.x;
scale.y *= scaleChange.y;
scale.z *= scaleChange.z;
}
llSetLinkPrimitiveParamsFast( i, [ PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ] );
}
}
}
// This function is used to start a sequential animation playback. // If the delay speed is set too low, the script might not be able to keep up. playAnimation(float delay, integer loop) {
if(delay < 0.1) delay = 1.0;
if( loop == FALSE) playAnimationStyle = 1; else playAnimationStyle = 2;
if (recordedSnapshots >= 1) llSetTimerEvent(delay);
}
// This shows the edit menu showMenuDialog() {
string temp = (string)((float)freeMemory/(float)maxMemory * 100.0);
string menuText = "Available Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" +
"\nCurrent Snapshot: " + (string)currentSnapshot +"\tSnapshots Recorded: " + (string)recordedSnapshots +
"\n\n[ Record ] - Record a snapshot of prim positions\n[ Play ] - Play back all the recorded snapshots\n[ Publish ] - Finish the recording process\n[ Show Next ] - Show the next snapshot\n[ Show Prev ] - Show the previous snapshot";
llDialog(llGetOwner(), menuText, ["Record","Play","Publish","Show Prev","Show Next"], COMMAND_CHANNEL);
}
default {
state_entry()
{
maxMemory = llGetFreeMemory();
freeMemory = llGetFreeMemory();
primCount = llGetNumberOfPrims();
commandListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), "");
showMenuDialog();
//setting initial root scale. this allows the animation to scale if the root size is changed afterwards.
rootScale = llGetScale();
}
//Feel free to remove this on-touch trigger if you are using your own script to control playback
touch_start(integer num_detected)
{
//only activate after publish.
if (commandListenerHandle == -1)
{ //if animation not playing start it, else stop it. if( playAnimationStyle == 0) playAnimation(1.0,TRUE); else { playAnimationStyle = 0; llSetTimerEvent(0); } }
}
changed(integer change)
{
//this is needed to detect scale changes and record the differences in order to adjust the animation accordingly.
if (change & CHANGED_SCALE)
{
if (rootScale != ZERO_VECTOR)
{
vector newScale = llGetScale();
//since change_scale is triggered even with linked prim changes,
//this is to filter out non-root changes.
if( ( newScale.x / rootScale.x) != scaleChange.x ||
( newScale.y / rootScale.y) != scaleChange.y ||
( newScale.z / rootScale.z) != scaleChange.z )
{
scaleChange.x = newScale.x / rootScale.x;
scaleChange.y = newScale.y / rootScale.y;
scaleChange.z = newScale.z / rootScale.z;
}
}
}
// if new prims are added or removed from this object then the script resets // because the animations are now broken. else if (change & CHANGED_LINK)
{
if( primCount != llGetNumberOfPrims() ) { llOwnerSay("Link change detected, reseting script."); llResetScript(); } }
} //The message link function is to allow other scripts to control the snapshot playback
//This command will display snapshot #2: // llMessageLinked(LINK_ROOT, 2, "XDshow", NULL_KEY); llSleep(1.0); // //This command will play through all the recorded snapshots in ascending order. The number "1.0" is the delay speed and can be changed. // llMessageLinked(LINK_ROOT, 0, "XDplay", "1.0"); // //This command will loop through all the recorded snapshots in ascending order. The number "1.0" is the delay speed and can be changed. // llMessageLinked(LINK_ROOT, 0, "XDplayLoop", "1.0"); // //To stop any playing animation use // llMessageLinked(LINK_ROOT, 0, "XDstop", NULL_KEY);
link_message(integer sender_num, integer num, string str, key id)
{
if ("XDshow" == str && num >= 1 && num <= recordedSnapshots)
showSnapshot(num);
else if ("XDplay" == str)
{
currentSnapshot = 1;
float delay = (float)((string)id); playAnimation(delay,FALSE); }
else if ("XDplayLoop" == str)
{ float delay = (float)((string)id); playAnimation(delay,TRUE); } else if ("XDstop" == str) { playAnimationStyle = 0; llSetTimerEvent(0); }
}
//This event handler takes care of all the editing commands. //Available commands are: record, play, publish, show next, show prev, show #
listen(integer channel, string name, key id, string message)
{
list parsedMessage = llParseString2List(message, [" "], []);
string firstWord = llToLower(llList2String(parsedMessage,0));
string secondWord = llToLower(llList2String(parsedMessage,1));
//display a snapshot
if("show" == firstWord && recordedSnapshots > 0)
{
//stop any currently playing animation. llSetTimerEvent(0);
if(secondWord == "next")
{
currentSnapshot++;
if(currentSnapshot > recordedSnapshots)
currentSnapshot = 1;
showSnapshot(currentSnapshot);
}
else if(secondWord == "prev")
{
currentSnapshot--;
if(currentSnapshot < 1)
currentSnapshot = recordedSnapshots;
showSnapshot(currentSnapshot);
}
else
{
// when the conversion fails, snapshotNumber = 0
currentSnapshot = (integer)secondWord;
if(currentSnapshot > 0 && currentSnapshot <= recordedSnapshots )
{
showSnapshot(currentSnapshot);
llOwnerSay("Showing snapshot: "+(string)currentSnapshot);
}
else
{
llOwnerSay("Invalid snapshot number given: " + (string) currentSnapshot +
"\nA valid snapshot number is between 1 and " + (string) recordedSnapshots);
currentSnapshot = 1;
}
}
}
//record a snapshot
else if(firstWord == "record")
{
integer i = 0;
//2 is the first linked prim number.
vector rootPos = llGetPos();
for( i = 2; i <= primCount; i++)
{
vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
//need to convert into local position
pos.x -= rootPos.x;
pos.z -= rootPos.z;
pos.y -= rootPos.y;
pos = pos / llGetRot();
posList += pos;
rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
//Converting into local rot
rot = rot / llGetRot();
rotList += rot;
scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
}
recordedSnapshots++;
llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
freeMemory = llGetFreeMemory();
}
//play the animation from beginning to end once without looping.
else if (firstWord == "play")
{
float delay = (float)secondWord;
currentSnapshot = 1; //play the animation once without loop playAnimation(delay, FALSE);
}
//publish disables the recording features and enables the on-touch trigger
else if("publish" == firstWord)
{
//stop any currently playing animation. llSetTimerEvent(0); playAnimationStyle = 0; currentSnapshot = 1;
//remove listeners to disable recording
llListenRemove(commandListenerHandle);
commandListenerHandle = -1; //indicating that it's been published
llOwnerSay("Recording disabled. Publish complete.\nClick me to toggle animation on/off.");
}
//if not published, show menu
if(commandListenerHandle != -1)
showMenuDialog();
}
//Timer event is used to handle the animation playback. timer()
{
showSnapshot(currentSnapshot);
//if not at the end of the animation, increment the counter if(currentSnapshot < recordedSnapshots) currentSnapshot++; else { // if animation is looping, set the counter back to 1 if( playAnimationStyle == 2) currentSnapshot = 1; // if animation isn't looping, stop the animation else { llSetTimerEvent(0); //if not published, show dialog menu if(commandListenerHandle != -1) showMenuDialog(); } } } } </lsl>