Open Prim Animator
<lsl> // Open Prim Animator - by Todd Borst // Extensive Modifications by SignpostMarv Martin
// 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();
list params = []; //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; } params += [ PRIM_LINK_TARGET, i, PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ]; if(llGetListLength(params) > 64){ llSetLinkPrimitiveParamsFast(LINK_THIS,params); params = []; } } if(llGetListLength(params) > 0){ llSetLinkPrimitiveParamsFast(LINK_THIS,params); params = []; } }
}
// 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() {
return; // comment out line to re-enable menu 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","Loop","Stop","Export"], COMMAND_CHANNEL);
} string truncate_float(float foo){
if(foo == 0.0){ return "0"; }else if(foo == (float)((integer)foo)){ return (string)((integer)foo); } string bar = (string)foo; while(llGetSubString(bar,-1,-1) == "0"){ bar = llGetSubString(bar,0,-2); } if(llGetSubString(bar,-1,-1) == "."){ bar = llGetSubString(bar,0,-2); } return bar;
}
key op_import = "6b78fcc8-e147-4105-99a6-ff19b4bf559d"; key op_export = "7c2ca168-2b64-4836-8727-8e62b78dbd44"; key op_alter_rootScale = "f9d3389e-a78c-43f8-9e35-c11adec112a5"; calc_scaleChange(){
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; } }
} 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(); if(llGetInventoryType("OPA Notecard Import - 2011-11-03") == INVENTORY_SCRIPT){ llResetOtherScript("OPA Notecard Import - 2011-11-03"); } }
/* //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) { calc_scaleChange(); } // 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); }else if("XDexport" == str && num == 0){ // added by Marv list export = []; string foo; vector bar; rotation baa; string baz; integer i; integer j = primCount; for(i=2;i<=j;++i){ export += [llGetLinkName(i)]; } j= llGetListLength(posList); llMessageLinked(sender_num, 1, llDumpList2String(export,"|") , op_export); export = []; for(i=0;i<j;++i){ bar = llList2Vector(posList,i); export += ["<" + truncate_float(bar.x) + "," + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"]; } llMessageLinked(sender_num, 2, llDumpList2String(export,"|") , op_export); export = []; j= llGetListLength(rotList); for(i=0;i<j;++i){ baa = llList2Rot(rotList,i); export += ["<" + truncate_float(baa.x) + "," + truncate_float(baa.y) + "," + truncate_float(baa.z) + "," + truncate_float(baa.s) + ">"]; } llMessageLinked(sender_num, 3, llDumpList2String(export,"|") , op_export); export = []; j= llGetListLength(scaleList); for(i=0;i<j;++i){ bar = llList2Vector(scaleList,i); export += ["<" + truncate_float(bar.x) + "," + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"]; } llMessageLinked(sender_num, 4, llDumpList2String(export,"|") , op_export); }else if("XDmenu" == str){ showMenuDialog(); }else if("XDimportLength" == str && num > 0){ list foo = []; list bar = []; integer i; for(i=0;i<num;++i){ foo += [ZERO_VECTOR]; bar += [ZERO_ROTATION]; } posList = foo; scaleList = foo; rotList = bar; llMessageLinked(sender_num,-1,str,op_import); recordedSnapshots = num / (llGetNumberOfPrims() - 1); llMessageLinked(LINK_SET, recordedSnapshots, "XDrecordedSnapshots", NULL_KEY); currentSnapshot = 1; }else if("XDrecordedSnapshots" == str && num == -1){ llMessageLinked(sender_num,recordedSnapshots,str,NULL_KEY); }else if(id == op_import && num >= 0){ list params = llParseString2List(str, ["|"], []); vector impPos = (vector)llList2String(params,0); rotation impRot = (rotation)llList2String(params,1); vector impSize = (vector)llList2String(params,2); posList = llListReplaceList(posList,[impPos],num,num); rotList = llListReplaceList(rotList,[impRot],num,num); scaleList = llListReplaceList(scaleList,[impSize],num,num); }else if(id == op_alter_rootScale){ rootScale = (vector)str; calc_scaleChange(); } } //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."); }else if("loop" == firstWord){ llMessageLinked(LINK_THIS,0,"XDplayLoop",NULL_KEY); }else if("stop" == firstWord){ llMessageLinked(LINK_THIS,0,"XDstop",NULL_KEY); }else if("export" == firstWord){ llOwnerSay("Should be exporting"); llMessageLinked(LINK_THIS,0,"XDexport",NULL_KEY); } //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>