Difference between revisions of "Open Prim Animator"

From Second Life Wiki
Jump to navigation Jump to search
m (<lsl> tag to <source>)
 
(6 intermediate revisions by 4 users not shown)
Line 1: Line 1:
<lsl>
<source lang="lsl2">
// 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  
// Note from Todd to other editors: Please document changes you have made to get proper credit.
// Note from Todd to users: People may have edited the script from since I've posted it originally.
// You can always view the original script by clicking the history tab.
 
// 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.


Line 13: Line 18:
// -Animation is scalable and resizeable
// -Animation is scalable and resizeable
// -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.  You are welcomed to sell it if you've made your
// free, you will be cursed with unimaginably bad juju.
// own improvements to it.  This is effectively public domain.  Have fun.


integer COMMAND_CHANNEL = 32;
integer COMMAND_CHANNEL = 32;


integer primCount = 0;
integer primCount;
integer commandListenerHandle = -1;
integer commandListenerHandle = ERR_GENERIC;
 
list posList;
list rotList;
list scaleList;
integer currentSnapshot;
integer recordedSnapshots;
 
vector rootScale;
vector scaleChange = <1.0, 1.0, 1.0>;


list posList    = [];
integer maxMemory;
list rotList    = [];
integer freeMemory;
list scaleList  = [];
integer currentSnapshot  = 0;
integer recordedSnapshots = 0;


vector rootScale  = ZERO_VECTOR;
//  The values for playAnimationStyle means
vector scaleChange = <1,1,1>;
//      0 :=    no animation playing
//      1 :=   play animation once
//      2 :=   play animation looping


// For tracking memory usage.  The amount of snapshots you can record is based
integer playAnimationStyle;
// on the number of prims and available memory.  Less prims = more snapshots
integer maxMemory  = 0;
integer freeMemory = 0;


integer playAnimationStyle = 0;
key op_import = "6b78fcc8-e147-4105-99a6-ff19b4bf559d";
// The values for playAnimationStyle means
key op_export = "7c2ca168-2b64-4836-8727-8e62b78dbd44";
// 0 = no animation playing
key op_alter_rootScale = "f9d3389e-a78c-43f8-9e35-c11adec112a5";
// 1 = play animation once
// 2 = play animation looping


// This function is used to display a recorded snapshot
// _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
showSnapshot(integer snapNumber)
//  _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
 
show_snapshot(integer snapNumber)
{
{
     if(snapNumber > 0 && snapNumber <= recordedSnapshots )
     if (!snapNumber || recordedSnapshots < snapNumber)
    {
         return;
        integer i = 0;
        vector  pos;
        rotation rot;
         vector  scale;


        vector rootPos = llGetPos();
    vector rootPos = llGetPos();
    rotation rootRot = llGetRot();


        // Might want to move llGetRot() into the loop for fast rotating objects.
    vector pos;
        // Rotation during the animation playback may cause errors.
    rotation rot;
        rotation rootRot = llGetRot();
    vector scale;
    list params;


         //2 is the first linked prim number.
    integer i = 2;
         for( i = 2; i <= primCount; i++)
    do
    {
         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));
 
        if ( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
         {
         {
             pos     = llList2Vector(posList,((snapNumber-1)*(primCount-1))+(i-2));
             pos.x *= scaleChange.x;
             rot    = llList2Rot(rotList,((snapNumber-1)*(primCount-1))+(i-2));
            pos.y *= scaleChange.y;
             scale   = llList2Vector(scaleList,((snapNumber-1)*(primCount-1))+(i-2));
             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
        ];


            //Adjust for scale changes
        if (64 < llGetListLength(params))
            if( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
        {
            {
            llSetLinkPrimitiveParamsFast(LINK_THIS, params);
                pos.x *= scaleChange.x;
            params = [];
                pos.y *= scaleChange.y;
        }
                pos.z *= scaleChange.z;
    }
                scale.x *= scaleChange.x;
    while (++i <= primCount);
                scale.y *= scaleChange.y;
                scale.z *= scaleChange.z;
            }


            llSetLinkPrimitiveParamsFast( i, [ PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ] );
    if (llGetListLength(params))
         }
    {
        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)
playAnimation(float delay, integer loop)
{
{
     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 (1 <= recordedSnapshots)
         llSetTimerEvent(delay);
         llSetTimerEvent(delay);
}
}


// This shows the edit menu
showMenuDialog()
showMenuDialog()
{
{
//  return;
     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 = "Free Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)"
    "\nCurrent Snapshot: " + (string)currentSnapshot +"\tSnapshots Recorded: " + (string)recordedSnapshots +
        + "\nSnapshot " + (string)currentSnapshot +" of " + (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"], COMMAND_CHANNEL);
     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;
}
 
calc_scaleChange()
{
    if (rootScale != ZERO_VECTOR)
    {
        vector newScale = llGetScale();
 
        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;
        }
    }
}
}


Line 121: Line 182:
         showMenuDialog();
         showMenuDialog();


        //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)
    {
//  {
        //only activate after publish.
//     if (commandListenerHandle == ERR_GENERIC)
        if (commandListenerHandle == -1)
//      {
        {
//         if (playAnimationStyle == 0)
            //if animation not playing start it, else stop it.
//              playAnimation(1.0,TRUE);
            if( playAnimationStyle == 0)
//          else
                playAnimation(1.0,TRUE);
//          {
            else
//              playAnimationStyle = 0;
            {
//              llSetTimerEvent((float)FALSE);
                playAnimationStyle = 0;
//          }
                llSetTimerEvent(0);
//      }
            }
//  }
        }
    }


     changed(integer change)
     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 (change & CHANGED_SCALE)
            calc_scaleChange();
        if (change & CHANGED_LINK)
         {
         {
            if (rootScale != ZERO_VECTOR)
             if ( primCount != llGetNumberOfPrims() )
            {
                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.");
                 llOwnerSay("Link change detected, reseting script.");
Line 186: Line 230:
     //To stop any playing animation use
     //To stop any playing animation use
     //      llMessageLinked(LINK_ROOT, 0, "XDstop", NULL_KEY);
     //      llMessageLinked(LINK_ROOT, 0, "XDstop", NULL_KEY);
     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 && 1 <= num && num <= recordedSnapshots)
             showSnapshot(num);
             show_snapshot(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((float)FALSE);
        }
        else if ("XDexport" == str && !num)
        {
            list export = [];
            string foo;
            vector bar;
            rotation baa;
            string baz;
 
            integer i = 2;
            integer j = primCount;
 
            do
                export += [llGetLinkName(i)];
            while (++i <= j);
 
            llMessageLinked(sender_num, 1, llDumpList2String(export,"|")  , op_export);
            export = [];
 
            i = 0;
            j = llGetListLength(posList);
 
            do
            {
                bar = llList2Vector(posList,i);
                export += ["<" + truncate_float(bar.x) + ","
                          + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"];
            }
            while (++i < j);
 
            llMessageLinked(sender_num, 2, llDumpList2String(export,"|")  , op_export);
            export = [];
 
            i = 0;
            j = llGetListLength(rotList);
 
            do
            {
                baa = llList2Rot(rotList,i);
                export += ["<" + truncate_float(baa.x) + "," + truncate_float(baa.y)
                            + "," + truncate_float(baa.z) + "," + truncate_float(baa.s) + ">"];
            }
            while (++i < j);
 
            llMessageLinked(sender_num, 3, llDumpList2String(export,"|")  , op_export);
            export = [];
 
            i = 0;
            j = llGetListLength(scaleList);
 
            do
            {
                bar = llList2Vector(scaleList,i);
                export += ["<" + truncate_float(bar.x) + ","
                            + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"];
            }
            while (++i < j);
 
            llMessageLinked(sender_num, 4, llDumpList2String(export,"|")  , op_export);
        }
        else if ("XDmenu" == str)
        {
            showMenuDialog();
        }
        else if ("XDimportLength" == str && 0 < num)
        {
            list foo;
            list bar;
 
            integer i;
            do
            {
                foo += [ZERO_VECTOR];
                bar += [ZERO_ROTATION];
            }
            while (++i < num);
 
            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 && 0 <= num)
        {
            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)
     listen(integer channel, string name, key id, string message)
     {
     {
         list parsedMessage = llParseString2List(message, [" "], []);
         list parsedMessage = llParseString2List(message, [" "], []);
         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
         if ("show" == firstWord && recordedSnapshots > 0)
         if("show" == firstWord && recordedSnapshots > 0)
         {
         {
            //stop any currently playing animation.
             llSetTimerEvent((float)FALSE);
             llSetTimerEvent(0);


             if(secondWord == "next")
             if (secondWord == "next")
             {
             {
                 currentSnapshot++;
                 ++currentSnapshot;
                 if(currentSnapshot > recordedSnapshots)
 
                 if (recordedSnapshots < currentSnapshot)
                     currentSnapshot = 1;
                     currentSnapshot = 1;
                   
 
                 showSnapshot(currentSnapshot);
                 show_snapshot(currentSnapshot);
             }
             }
             else if(secondWord == "prev")
             else if (secondWord == "prev")
             {
             {
                 currentSnapshot--;
                 --currentSnapshot;
                 if(currentSnapshot < 1)
 
                 if (currentSnapshot < 1)
                     currentSnapshot = recordedSnapshots;
                     currentSnapshot = recordedSnapshots;


                 showSnapshot(currentSnapshot);
                 show_snapshot(currentSnapshot);
             }
             }
             else
             else
             {
             {
                // when the conversion fails, snapshotNumber = 0
                 currentSnapshot = (integer)secondWord;
                 currentSnapshot = (integer)secondWord;
                 if(currentSnapshot > 0 && currentSnapshot <= recordedSnapshots )
 
                 if (currentSnapshot && currentSnapshot <= recordedSnapshots)
                 {
                 {
                     showSnapshot(currentSnapshot);
                     show_snapshot(currentSnapshot);
                     llOwnerSay("Showing snapshot: "+(string)currentSnapshot);
                     llOwnerSay("Showing snapshot: " + (string)currentSnapshot);
                 }
                 }
                 else
                 else
Line 251: Line 398:
                     llOwnerSay("Invalid snapshot number given: " + (string) currentSnapshot +
                     llOwnerSay("Invalid snapshot number given: " + (string) currentSnapshot +
                                 "\nA valid snapshot number is between 1 and " + (string) recordedSnapshots);
                                 "\nA valid snapshot number is between 1 and " + (string) recordedSnapshots);
                     currentSnapshot = 1;
                     currentSnapshot = 1;
                 }
                 }
             }
             }
         }
         }
        //record a snapshot
         else if (firstWord == "record")
         else if(firstWord == "record")
         {
         {
            integer i = 0;
            //2 is the first linked prim number.
             vector rootPos = llGetPos();
             vector rootPos = llGetPos();
             for( i = 2; i <= primCount; i++)
 
             integer i = 2;
            do
             {
             {
                 vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
                 vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]), 0);
                //need to convert into local position
 
                 pos.x -= rootPos.x;
                 pos.x -= rootPos.x;
                 pos.z -= rootPos.z;
                 pos.z -= rootPos.z;
Line 271: Line 418:
                 posList += pos;
                 posList += pos;


                 rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
                 rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]), 0);
                //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++;
             while (++i <= primCount);
 
            ++recordedSnapshots;


             llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
             llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
             freeMemory = llGetFreeMemory();
             freeMemory = llGetFreeMemory();
         }
         }
        //play the animation from beginning to end once without looping.
         else if (firstWord == "play")
         else if (firstWord == "play")
         {
         {
             float delay = (float)secondWord;
             float delay = (float)secondWord;
             currentSnapshot = 1;
             currentSnapshot = 1;
            //play the animation once without loop
             playAnimation(delay, FALSE);
             playAnimation(delay, FALSE);
         }
         }
        //publish disables the recording features and enables the on-touch trigger
         else if ("publish" == firstWord)
         else if("publish" == firstWord)
         {
         {
            //stop any currently playing animation.
             llSetTimerEvent((float)FALSE);
             llSetTimerEvent(0);
             playAnimationStyle = 0;
             playAnimationStyle = 0;
             currentSnapshot = 1;
             currentSnapshot = 1;


            //remove listeners to disable recording
             llListenRemove(commandListenerHandle);
             llListenRemove(commandListenerHandle);
             commandListenerHandle = -1; //indicating that it's been published
             commandListenerHandle = -1;


             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 (commandListenerHandle != ERR_GENERIC)
         if(commandListenerHandle != -1)
             showMenuDialog();
             showMenuDialog();
     }
     }


    //Timer event is used to handle the animation playback.
     timer()
     timer()
     {
     {
         showSnapshot(currentSnapshot);
         show_snapshot(currentSnapshot);


        //if not at the end of the animation, increment the counter
         if (currentSnapshot < recordedSnapshots)
         if(currentSnapshot < recordedSnapshots)
             ++currentSnapshot;
             currentSnapshot++;
         else
         else
         {
         {
            // if animation is looping, set the counter back to 1
             if (playAnimationStyle == 2)
             if( playAnimationStyle == 2)
                 currentSnapshot = 1;
                 currentSnapshot = 1;
            // if animation isn't looping, stop the animation
             else
             else
             {
             {
                 llSetTimerEvent(0);
                 llSetTimerEvent((float)FALSE);
                //if not published, show dialog menu
 
                 if(commandListenerHandle != -1)
                 if (commandListenerHandle != ERR_GENERIC)
                     showMenuDialog();
                     showMenuDialog();
             }
             }
Line 335: Line 487:
     }
     }
}
}
</lsl>
</source>
 
[[Category:Open Prim Animator|Open Prim Animator]]

Latest revision as of 07:15, 25 January 2015

// Open Prim Animator - by Todd Borst
// Extensive Modifications by SignpostMarv Martin

// Note from Todd to other editors: Please document changes you have made to get proper credit.
// Note from Todd to users: People may have edited the script from since I've posted it originally.
// You can always view the original script by clicking the history tab.

// 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.  You are welcomed to sell it if you've made your
// own improvements to it.  This is effectively public domain.  Have fun.

integer COMMAND_CHANNEL = 32;

integer primCount;
integer commandListenerHandle = ERR_GENERIC;

list posList;
list rotList;
list scaleList;
integer currentSnapshot;
integer recordedSnapshots;

vector rootScale;
vector scaleChange = <1.0, 1.0, 1.0>;

integer maxMemory;
integer freeMemory;

//  The values for playAnimationStyle means
//      0 :=    no animation playing
//      1 :=    play animation once
//      2 :=    play animation looping

integer playAnimationStyle;

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

//  _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//  _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

show_snapshot(integer snapNumber)
{
    if (!snapNumber || recordedSnapshots < snapNumber)
        return;

    vector rootPos = llGetPos();
    rotation rootRot = llGetRot();

    vector pos;
    rotation rot;
    vector scale;
    list params;

    integer i = 2;
    do
    {
        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));

        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 (64 < llGetListLength(params))
        {
            llSetLinkPrimitiveParamsFast(LINK_THIS, params);
            params = [];
        }
    }
    while (++i <= primCount);

    if (llGetListLength(params))
    {
        llSetLinkPrimitiveParamsFast(LINK_THIS, params);
        params = [];
    }
}

playAnimation(float delay, integer loop)
{
    if (delay < 0.1)
        delay = 1.0;

    if (loop == FALSE)
        playAnimationStyle = 1;
    else
        playAnimationStyle = 2;

    if (1 <= recordedSnapshots)
        llSetTimerEvent(delay);
}

showMenuDialog()
{
//  return;

    string temp = (string)((float)freeMemory/(float)maxMemory * 100.0);
    string menuText = "Free Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)"
        + "\nSnapshot " + (string)currentSnapshot +" of " + (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;
}

calc_scaleChange()
{
    if (rootScale != ZERO_VECTOR)
    {
        vector newScale = llGetScale();

        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();

        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)
//  {
//      if (commandListenerHandle == ERR_GENERIC)
//      {
//          if (playAnimationStyle == 0)
//              playAnimation(1.0,TRUE);
//          else
//          {
//              playAnimationStyle = 0;
//              llSetTimerEvent((float)FALSE);
//          }
//      }
//  }

    changed(integer change)
    {
        if (change & CHANGED_SCALE)
            calc_scaleChange();

        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 && 1 <= num && num <= recordedSnapshots)
            show_snapshot(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((float)FALSE);
        }
        else if ("XDexport" == str && !num)
        {
            list export = [];
            string foo;
            vector bar;
            rotation baa;
            string baz;

            integer i = 2;
            integer j = primCount;

            do
                export += [llGetLinkName(i)];
            while (++i <= j);

            llMessageLinked(sender_num, 1, llDumpList2String(export,"|")  , op_export);
            export = [];

            i = 0;
            j = llGetListLength(posList);

            do
            {
                bar = llList2Vector(posList,i);
                export += ["<" + truncate_float(bar.x) + ","
                          + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"];
            }
            while (++i < j);

            llMessageLinked(sender_num, 2, llDumpList2String(export,"|")  , op_export);
            export = [];

            i = 0;
            j = llGetListLength(rotList);

            do
            {
                baa = llList2Rot(rotList,i);
                export += ["<" + truncate_float(baa.x) + "," + truncate_float(baa.y)
                            + "," + truncate_float(baa.z) + "," + truncate_float(baa.s) + ">"];
            }
            while (++i < j);

            llMessageLinked(sender_num, 3, llDumpList2String(export,"|")  , op_export);
            export = [];

            i = 0;
            j = llGetListLength(scaleList);

            do
            {
                bar = llList2Vector(scaleList,i);
                export += ["<" + truncate_float(bar.x) + ","
                            + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"];
            }
            while (++i < j);

            llMessageLinked(sender_num, 4, llDumpList2String(export,"|")  , op_export);
        }
        else if ("XDmenu" == str)
        {
            showMenuDialog();
        }
        else if ("XDimportLength" == str && 0 < num)
        {
            list foo;
            list bar;

            integer i;
            do
            {
                foo += [ZERO_VECTOR];
                bar += [ZERO_ROTATION];
            }
            while (++i < num);

            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 && 0 <= num)
        {
            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();
        }
    }

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

        if ("show" == firstWord && recordedSnapshots > 0)
        {
            llSetTimerEvent((float)FALSE);

            if (secondWord == "next")
            {
                ++currentSnapshot;

                if (recordedSnapshots < currentSnapshot)
                    currentSnapshot = 1;

                show_snapshot(currentSnapshot);
            }
            else if (secondWord == "prev")
            {
                --currentSnapshot;

                if (currentSnapshot < 1)
                    currentSnapshot = recordedSnapshots;

                show_snapshot(currentSnapshot);
            }
            else
            {
                currentSnapshot = (integer)secondWord;

                if (currentSnapshot && currentSnapshot <= recordedSnapshots)
                {
                    show_snapshot(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;
                }
            }
        }
        else if (firstWord == "record")
        {
            vector rootPos = llGetPos();

            integer i = 2;
            do
            {
                vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]), 0);

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

                rot = rot / llGetRot();
                rotList += rot;

                scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]), 0);
            }
            while (++i <= primCount);

            ++recordedSnapshots;

            llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
            freeMemory = llGetFreeMemory();
        }
        else if (firstWord == "play")
        {
            float delay = (float)secondWord;
            currentSnapshot = 1;
            playAnimation(delay, FALSE);
        }
        else if ("publish" == firstWord)
        {
            llSetTimerEvent((float)FALSE);
            playAnimationStyle = 0;
            currentSnapshot = 1;

            llListenRemove(commandListenerHandle);
            commandListenerHandle = -1;

            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 (commandListenerHandle != ERR_GENERIC)
            showMenuDialog();
    }

    timer()
    {
        show_snapshot(currentSnapshot);

        if (currentSnapshot < recordedSnapshots)
            ++currentSnapshot;
        else
        {
            if (playAnimationStyle == 2)
                currentSnapshot = 1;
            else
            {
                llSetTimerEvent((float)FALSE);

                if (commandListenerHandle != ERR_GENERIC)
                    showMenuDialog();
            }
        }
    }
}