Difference between revisions of "Open Prim Animator"

From Second Life Wiki
Jump to navigation Jump to search
(Committing changes made to OPA made over the past year at work)
Line 1: Line 1:
<lsl>
<lsl>
// Open Prim Animator - by Todd Borst
// Open Prim Animator - by Todd Borst
 
// Extensive Modifications by SignpostMarv Martin
// This is provided AS IS without support.  Please don't bug me demanding  
// This is provided AS IS without support.  Please don't bug me demanding  
// help or custom work for this free script.
// help or custom work for this free script.
 
// Summary: This is a simple prim animation script.  Just add this 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.
// to your object and a dialog will automatically pop up for you to use.
 
// Features:
// Features:
// -Single script "Prim Puppeteer" like animation tool
// -Single script "Prim Puppeteer" like animation tool
Line 14: Line 15:
// -On-touch trigger built-in
// -On-touch trigger built-in
// -Completely free and open sourced  
// -Completely free and open sourced  
 
// License:
// License:
// You are welcome to use this anyway you like, even bring to other grids
// 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
// outside of Second Life.  If you try to sell what I'm giving away for
// free, you will be cursed with unimaginably bad juju.
// free, you will be cursed with unimaginably bad juju.
 
integer COMMAND_CHANNEL = 32;
integer COMMAND_CHANNEL = 32;
 
integer primCount = 0;
integer primCount = 0;
integer commandListenerHandle = -1;
integer commandListenerHandle = -1;
 
list posList    = [];
list posList    = [];
list rotList    = [];
list rotList    = [];
Line 30: Line 31:
integer currentSnapshot  = 0;
integer currentSnapshot  = 0;
integer recordedSnapshots = 0;
integer recordedSnapshots = 0;
 
vector rootScale  = ZERO_VECTOR;
vector rootScale  = ZERO_VECTOR;
vector scaleChange = <1,1,1>;
vector scaleChange = <1,1,1>;
 
// For tracking memory usage.  The amount of snapshots you can record is based
// 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
// on the number of prims and available memory.  Less prims = more snapshots
integer maxMemory  = 0;
integer maxMemory  = 0;
integer freeMemory = 0;
integer freeMemory = 0;
 
integer playAnimationStyle = 0;
integer playAnimationStyle = 0;
// The values for playAnimationStyle means
// The values for playAnimationStyle means
Line 44: Line 45:
// 1 = play animation once
// 1 = play animation once
// 2 = play animation looping
// 2 = play animation looping
 
// This function is used to display a recorded snapshot
// This function is used to display a recorded snapshot
showSnapshot(integer snapNumber)
showSnapshot(integer snapNumber)
Line 54: Line 55:
         rotation rot;
         rotation rot;
         vector  scale;
         vector  scale;
 
         vector rootPos = llGetPos();
         vector rootPos = llGetPos();
 
         // Might want to move llGetRot() into the loop for fast rotating objects.
         // Might want to move llGetRot() into the loop for fast rotating objects.
         // Rotation during the animation playback may cause errors.
         // Rotation during the animation playback may cause errors.
         rotation rootRot = llGetRot();
         rotation rootRot = llGetRot();


        list params = [];
         //2 is the first linked prim number.
         //2 is the first linked prim number.
         for( i = 2; i <= primCount; i++)
         for( i = 2; i <= primCount; i++)
Line 67: Line 70:
             rot    = llList2Rot(rotList,((snapNumber-1)*(primCount-1))+(i-2));
             rot    = llList2Rot(rotList,((snapNumber-1)*(primCount-1))+(i-2));
             scale  = llList2Vector(scaleList,((snapNumber-1)*(primCount-1))+(i-2));
             scale  = llList2Vector(scaleList,((snapNumber-1)*(primCount-1))+(i-2));
 
             //Adjust for scale changes
             //Adjust for scale changes
             if( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
             if( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
Line 78: Line 81:
                 scale.z *= scaleChange.z;
                 scale.z *= scaleChange.z;
             }
             }
 
            params += [
            llSetLinkPrimitiveParamsFast( i, [ PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ] );
                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.
// 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.
// If the delay speed is set too low, the script might not be able to keep up.
Line 89: Line 110:
{
{
     if(delay < 0.1) delay = 1.0;
     if(delay < 0.1) delay = 1.0;
 
     if( loop == FALSE)
     if( loop == FALSE)
         playAnimationStyle = 1;
         playAnimationStyle = 1;
     else
     else
         playAnimationStyle = 2;
         playAnimationStyle = 2;
 
     if (recordedSnapshots >= 1)
     if (recordedSnapshots >= 1)
         llSetTimerEvent(delay);
         llSetTimerEvent(delay);
}
}
 
// This shows the edit menu
// This shows the edit menu
showMenuDialog()
showMenuDialog()
{
{
    return; // comment out line to re-enable menu
     string temp = (string)((float)freeMemory/(float)maxMemory * 100.0);
     string temp = (string)((float)freeMemory/(float)maxMemory * 100.0);
     string menuText = "Available Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" +
     string menuText = "Available Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" +
     "\nCurrent Snapshot: " + (string)currentSnapshot +"\tSnapshots Recorded: " + (string)recordedSnapshots +
     "\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";
     "\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;
}


    llDialog(llGetOwner(), menuText, ["Record","Play","Publish","Show Prev","Show Next"], COMMAND_CHANNEL);
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
default
{
{
Line 116: Line 171:
         maxMemory = llGetFreeMemory();
         maxMemory = llGetFreeMemory();
         freeMemory = llGetFreeMemory();
         freeMemory = llGetFreeMemory();
 
         primCount = llGetNumberOfPrims();
         primCount = llGetNumberOfPrims();
         commandListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), "");
         commandListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), "");
         showMenuDialog();
         showMenuDialog();
 
         //setting initial root scale.  this allows the animation to scale if the root size is changed afterwards.
         //setting initial root scale.  this allows the animation to scale if the root size is changed afterwards.
         rootScale = llGetScale();
         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
/*    //Feel free to remove this on-touch trigger if you are using your own script to control playback
     touch_start(integer num_detected)
     touch_start(integer num_detected)
     {
     {
Line 140: Line 198:
             }
             }
         }
         }
     }
     }*/
 
     changed(integer change)
     changed(integer change)
     {
     {
Line 147: Line 205:
         if (change & CHANGED_SCALE)
         if (change & CHANGED_SCALE)
         {
         {
             if (rootScale != ZERO_VECTOR)
             calc_scaleChange();
            {
                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
         // if new prims are added or removed from this object then the script resets
Line 173: Line 218:
         }
         }
     }
     }
 
     //The message link function is to allow other scripts to control the snapshot playback
     //The message link function is to allow other scripts to control the snapshot playback
     //This command will display snapshot #2:
     //This command will display snapshot #2:
Line 188: Line 233:
     link_message(integer sender_num, integer num, string str, key id)
     link_message(integer sender_num, integer num, string str, key id)
     {
     {
         if ("XDshow" == str && num >= 1 && num <= recordedSnapshots)
         if ("XDshow" == str && num >= 1 && num <= recordedSnapshots){
             showSnapshot(num);
             showSnapshot(num);
         else if ("XDplay" == str)
         }else if ("XDplay" == str){
        {
             currentSnapshot = 1;
             currentSnapshot = 1;
             float delay = (float)((string)id);
             float delay = (float)((string)id);
             playAnimation(delay,FALSE);
             playAnimation(delay,FALSE);
         }
         }else if ("XDplayLoop" == str){
        else if ("XDplayLoop" == str)
        {
             float delay = (float)((string)id);
             float delay = (float)((string)id);
             playAnimation(delay,TRUE);
             playAnimation(delay,TRUE);
         }
         }else if ("XDstop" == str){
        else if ("XDstop" == str)
        {
             playAnimationStyle = 0;
             playAnimationStyle = 0;
             llSetTimerEvent(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.
     //This event handler takes care of all the editing commands.
     //Available commands are: record, play, publish, show next, show prev, show #
     //Available commands are: record, play, publish, show next, show prev, show #
Line 215: Line 318:
         string firstWord = llToLower(llList2String(parsedMessage,0));
         string firstWord = llToLower(llList2String(parsedMessage,0));
         string secondWord = llToLower(llList2String(parsedMessage,1));
         string secondWord = llToLower(llList2String(parsedMessage,1));
 
         //display a snapshot
         //display a snapshot
         if("show" == firstWord && recordedSnapshots > 0)
         if("show" == firstWord && recordedSnapshots > 0)
Line 221: Line 324:
             //stop any currently playing animation.
             //stop any currently playing animation.
             llSetTimerEvent(0);
             llSetTimerEvent(0);
 
             if(secondWord == "next")
             if(secondWord == "next")
             {
             {
Line 227: Line 330:
                 if(currentSnapshot > recordedSnapshots)
                 if(currentSnapshot > recordedSnapshots)
                     currentSnapshot = 1;
                     currentSnapshot = 1;
                   
                 showSnapshot(currentSnapshot);
                 showSnapshot(currentSnapshot);
             }
             }
Line 235: Line 338:
                 if(currentSnapshot < 1)
                 if(currentSnapshot < 1)
                     currentSnapshot = recordedSnapshots;
                     currentSnapshot = recordedSnapshots;
 
                 showSnapshot(currentSnapshot);
                 showSnapshot(currentSnapshot);
             }
             }
Line 270: Line 373:
                 pos = pos / llGetRot();
                 pos = pos / llGetRot();
                 posList += pos;
                 posList += pos;
 
                 rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
                 rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
                 //Converting into local rot
                 //Converting into local rot
                 rot = rot / llGetRot();
                 rot = rot / llGetRot();
                 rotList += rot;
                 rotList += rot;
 
                 scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
                 scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
             }
             }
             recordedSnapshots++;
             recordedSnapshots++;
 
             llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
             llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
             freeMemory = llGetFreeMemory();
             freeMemory = llGetFreeMemory();
Line 298: Line 401:
             playAnimationStyle = 0;
             playAnimationStyle = 0;
             currentSnapshot = 1;
             currentSnapshot = 1;
 
             //remove listeners to disable recording
             //remove listeners to disable recording
             llListenRemove(commandListenerHandle);
             llListenRemove(commandListenerHandle);
             commandListenerHandle = -1; //indicating that it's been published
             commandListenerHandle = -1; //indicating that it's been published
 
             llOwnerSay("Recording disabled. Publish complete.\nClick me to toggle animation on/off.");
             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 not published, show menu
         if(commandListenerHandle != -1)
         if(commandListenerHandle != -1)
             showMenuDialog();
             showMenuDialog();
     }
     }
 
     //Timer event is used to handle the animation playback.
     //Timer event is used to handle the animation playback.
     timer()
     timer()
     {
     {
         showSnapshot(currentSnapshot);
         showSnapshot(currentSnapshot);
 
         //if not at the end of the animation, increment the counter
         //if not at the end of the animation, increment the counter
         if(currentSnapshot < recordedSnapshots)
         if(currentSnapshot < recordedSnapshots)

Revision as of 04:06, 14 December 2011

<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>