Puppeteer

From Second Life Wiki
Jump to: navigation, search

Created by Kira Komarov.

Changelog

  • 17 November 2011
  1. Added some more to the animator itself so that notecards (card paths) are now hot-swappable.
  2. Changed TODO section to FAQ and added some information for attachments and linked primitives.
  3. Added to FAQ how to make a looped animation.
  • 15 August 2011
  1. Capa Langer mentioned that the puppeteer does not output lines for a few number of steps. This was because in testing only large number of steps were tested. The [K] Puppeteer Recorder script was updated to address this and to output lines even for a few number of steps.

Introduction

Although I have never used a puppeteer to animate objects since I believe that the best way to achieve an animation is to manually (and painfully) create your own hard coded animations, I have seen that puppeteers are popular in-world and used by many people. The following pair of scripts and description will offer you a free version to the puppeteering engines out there. The scripts are limited to positions and rotations and modifying them to record other parameters (such as textures, color, etc...) is fairly trivial since the same principle will apply.

One involuntary feature is that once you create the paths and save the data to a notecard, that notecard may be replayed any number of times. That makes the notecards pluggable and will allow you to swap between paths by dragging and dropping a path-notecard into the inventory.

This script can puppeteer entire objects as well as linked objects, depending on what you puppeteer:

  1. If you puppeteer the ROOT primitive of a linked object:
  • The whole object moves using region coordinates.
  1. If you puppeteer a LINKED PRIMITIVE in a linked set:
  • Just that primitive moves using local coordinates.

How it Works

The puppeteer works in the following way:

1.) You drop the [K] Puppeteer Recorder in a prim you wish to create an animation for.

2.) You move / rotate the prim to the next position and at each step, you touch the prim and select "◆ Mark ◆".

3.) Once you have marked several positions, you touch the puppeteer recorder and select "◆ Dump ◆". This will dump all the recoded data on the local chat and it would look something like this:

Object: ------------- BEGIN CUT -------------
Object: #<57.401940, 113.362300, 1001.491000>#<56.057780, 113.362300, 1001.491000>#<56.057780, 113.362300, 1002.707000>#<56.649890, 113.362300, 1002.707000>#<56.649890, 113.362300, 1003.624000>#<55.782050, 113.362300, 1003.624000>#
Object: [K]
Object: #<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#
Object: -------------- END CUT --------------

4.) Now you need to clean it up a little bit to delete the object name (in this case "Object: ") from the data as well as the cut lines. After the cleaning, based on the previous example, the data should look like this:

#<57.401940, 113.362300, 1001.491000>#<56.057780, 113.362300, 1001.491000>#<56.057780, 113.362300, 1002.707000>#<56.649890, 113.362300, 1002.707000>#<56.649890, 113.362300, 1003.624000>#<55.782050, 113.362300, 1003.624000>#
[K]
#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#<0.000000, 0.000000, 0.000000, 1.000000>#

5.) Now, you take that cleaned data, and save it in a notecard called Puppet.

6.) You delete the puppeteer recorder script from the prim and add the notecard Puppet with the data you just cleaned.

7.) Finally you add the [K] Puppeteer Animator script in the prim along with the notecard and the prim will start executing each step.

Scripts

Here is the [K] Puppeteer Recorder script:

//////////////////////////////////////////////////////////
//                 PUPPETEER RECORDER                   //
//////////////////////////////////////////////////////////
// Gremlin: Puppeteer Recorder, see Puppeteer Animator
//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
list pos = [];
list rot = [];
 
integer comChannel;
integer comHandle;
 
default
{
    touch_start(integer total_number)
    {
        key owner = llGetOwner();
 
        comChannel = ((integer)("0x"+llGetSubString((string)owner, -8, -1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        comHandle = llListen(comChannel, "", owner, "");
 
        llDialog(owner, "\nWelcome to the Puppeteer:\n\n"
                + "1.) ◆ Mark ◆ is for recording each step. "
                + "Move/Rotate the prim and press ◆ Mark ◆ to record that position.\n"
                + "2.) Use ◆ Wipe ◆ if you want to delete all existing recoded data.\n"
                + "3.) Use ◆ Dump ◆ after you have recoded several steps.\n"
                + "4.) ◆ Undo ◆ lets you delete the previous step. This works multiple times.\n"
                + "5.) ◆ Preview ◆ will allow you to watch the animation before dumping the data.\n",
            [ "◆ Mark ◆", "◆ Wipe ◆", "◆ Dump ◆", "◆ Undo ◆", "◆ Preview ◆" ], comChannel);
    }
 
    listen(integer channel, string name, key id, string message)
    {
        if (message == "◆ Mark ◆")
        {
            pos += (list)llGetLocalPos();
            rot += (list)llGetLocalRot();
            llOwnerSay("--MARK--");
            jump close;
        }
        else if (message == "◆ Dump ◆")
        {
            if (llGetListLength(pos) == 0)
            {
                llOwnerSay("Sorry, you need to first record steps before dumping the data.");
                jump close;
            }
            llOwnerSay("------------- BEGIN CUT -------------");
            string pLine;
 
            integer itra;
            integer mLine;
 
            while (itra < llGetListLength(pos))
            {
                if (llStringLength(pLine) + llStringLength(llList2String(pos, itra)) < 254)
                {
                    pLine += (llList2String(pos, itra) + "#");
                    jump continue_p;
                }
                llOwnerSay("#" + pLine);
                pLine = "";
                mLine = 1;
                ++itra;
@continue_p;
            }
 
            if (!mLine)
                llOwnerSay("#" + pLine);
 
            llOwnerSay("[K]");
            string rLine;
 
 
            itra = 0;
            mLine = 0;
            while (itra < llGetListLength(rot))
            {
                if (llStringLength(rLine) + llStringLength(llList2String(rot, itra)) < 254)
                {
                    rLine += (llList2String(rot, itra)+ "#");
                    jump continue_r;
                }
                llOwnerSay("#" + rLine);
                rLine = "";
                mLine = 1;
                ++itra;
@continue_r;
            }
            if (!mLine)
                llOwnerSay("#" + rLine);
 
            llOwnerSay("-------------- END CUT --------------");
            jump close;
        }
        else if (message == "◆ Wipe ◆")
        {
            pos = [];
            rot = [];
            llOwnerSay("All recorded steps have been wiped.");
            jump close;
        }
        else if (message == "◆ Undo ◆")
        {
            if (llGetListLength(pos) == 0)
            {
                llOwnerSay("No more steps left to undo.");
                jump close;
            }
            vector rPos = llList2Vector(pos, llGetListLength(pos)-1);
            pos = llDeleteSubList(pos, llGetListLength(pos)-1, llGetListLength(pos)-1);
            rotation rRot = llList2Rot(rot, llGetListLength(rot)-1);
            rot = llDeleteSubList(rot, llGetListLength(rot)-1, llGetListLength(rot)-1);
            llSetPos(rPos);
            llSetLocalRot(rRot);
            jump close;
        }
        else if (message == "◆ Preview ◆")
        {
            llOwnerSay("Animation will run once and then stop.");
            state puppet;
        }
@close;
        llListenRemove(comHandle);
    }
}
 
state puppet
{
    state_entry()
    {
        integer itra;
        while (itra < llGetListLength(pos))
        {
            llSetPos(llList2Vector(pos, itra));
            llSetLocalRot(llList2Rot(rot, itra));
            ++itra;
        }
        llOwnerSay("Animation done, resuming normal operation.");
        state default;
    }
}

And here is the [K] Puppeteer Animator:

//////////////////////////////////////////////////////////
//                 PUPPETEER ANIMATOR                   //
//////////////////////////////////////////////////////////
// Gremlin: Puppeteer Animator, see Puppeteer Recorder
//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
string pupData;
key pQuery;
 
integer nDone = 0;
 
init()
{
    integer itra = 0;
    while (itra < llGetInventoryNumber(INVENTORY_NOTECARD))
    {
        if(llGetInventoryName(INVENTORY_NOTECARD, itra) == "Puppet")
            jump found_puppet;
        ++itra;
    }
    llOwnerSay("No puppet found, please load the notecard.");
    return;
    @found_puppet;
    pQuery = llGetNotecardLine("Puppet", itra);
    llSetTimerEvent(1.0);
}
 
default
{
    state_entry()
    {
        init();
    }
 
    on_rez(integer start_param)
    {
        init();
    }
 
    changed(integer change)
    {
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
            init();
    }
 
    dataserver(key query_id,string data)
    {
        if (query_id != pQuery)
            return;
 
        if (data == EOF)
        {
            nDone = 1;
            return;
        }
 
        pupData += data;
        pQuery = llGetNotecardLine("Puppet", ++itra);
    }
 
    timer()
    {
        if(!nDone)
            return;
 
        llSetTimerEvent((float)FALSE);
        state puppet;
    }
}
 
state puppet
{
    on_rez(integer start_param)
    {
        llResetScript();
    }
 
    changed(integer change)
    {
        if(change & (CHANGED_OWNER | CHANGED_INVENTORY))
            llResetScript();
    }
 
    state_entry()
    {
        list tPup = llParseString2List(pupData, ["[K]"], [""]);
        pupData = "";
        list pos = llParseString2List(llList2String(tPup, 0), ["#"], [""]);
        list rot = llParseString2List(llList2String(tPup, 1), ["#"], [""]);
@cycle;
        integer itra = 0;
        while (itra < llGetListLength(pos))
        {
            list tPos = llParseString2List(llList2String(pos, itra), [",", "<", ">"], [""]);
            vector sPos = <llList2Float(tPos, 0), llList2Float(tPos, 1), llList2Float(tPos, 2)>;
            list tRot = llParseString2List(llList2String(rot, itra), [",", "<", ">"], [""]);
            rotation sRot = <llList2Float(tRot, 0), llList2Float(tRot, 1), llList2Float(tRot, 2), llList2Float(tRot, 3)>;
            llSetPos(sPos);
            llSetLocalRot(sRot);
            ++itra;
        }
        jump cycle;
    }
}

If you wish to stop the animation once you have added the [K] Puppeteer Animator script and the notecard, simply delete them both from the prim and the animation will stop. The [K] Puppeteer Recorder script also provides some more useful tools: "◆ Wipe ◆" will delete all the recorded data, "◆ Undo ◆" will remove a step, this works multiple times on a stack-based principle and "◆ Preview ◆" which will allow you to preview the animation itself on the recorder before dumping the data, saving to the notecard and adding the [K] Puppeteer Animator script.

You can also hotswap the path-notecards you generated with the [K] Puppeteer Recorder script by replacing the notecard in the inventory of the primitive.

The script could certainly be extended to include more parameters and perhaps even delays. However, for some basic puppeteering it should work just fine and most of the time positions and rotations is most of what you need. If you need any help setting this up, feel free to contact Kira Komarov in-world.

FAQ

  • Can I use this to animate attachments?
  • Yes, it works the same way, just create the path while that attachment is attached to yourself to grab the local coordinates.

--

  • Can I use this to animate linked primitives, such as wings?
  • Yes, puppeteer each wing. If you puppeteer the root of a linked object, it will move the object itself. If you puppeteer a linked primitive, it will puppeteer just that primitive.

--

  • How can I make the puppeteer loop the animation in a seamless path?
  • Well, suppose that you wanted to make a closed path, such as a circle or a square or a rectangle. You can loop the animation by just marking the last step close to the first one. When the puppeteer reaches the last step, it will jump to the first. However, if that last one is close to the first... It will just loop.