Sample Use of llSetKeyframedMotion

Creating scripts
Post Reply
User avatar
Kayaker Magic
Posts: 354
Joined: Sun Dec 01, 2013 8:40 am
Has thanked: 52 times
Been thanked: 393 times

Sample Use of llSetKeyframedMotion

Post by Kayaker Magic »

**NEWS FLASH** I updated the sample code to use the new osGetRegionSize() function, so now this is an example of how to use that as well.
Now the flyer will fly in any sub-region in a mega region, however, the current parcel test keeps it in the sub-region you rezzed it in. This is because each sub-region has it's own parcel. You can remove that test to let the flyer roam EVERYWHERE in a mega region.

I published a version of this script in the OSgrid forums a while ago, back before OpenSim had the llSetKeyframedMotion function.
This version uses llSetKeyframedMotion, so it is smaller and uses fewer SIM resources to fly around.

The script implements a simple flying critter, it moves smoothly in a straight line until it dives into the water, gets too high, or gets too close to the ground, edge of a region or the edge of a parcel. Then it turns smoothly to avoid the boundaries.

You could modify this to stay underwater instead of above and it would make a pretty good fish.

Stick the script in a prim, preferably one longest in the X direction so you can see it turning. Click on it to stop/start. While it is stopped you can use the build menu to rotate and re-direct it.

Code: Select all

// Kitely Flyer
//      A script that gets pretty smooth motion and
//    moves a critter around sort of like "pathfinding" in SL.
//        This version is an example of how to do that using the
//            llSetKeyframedMotion function, and now the new osGetRegionSize() function!
//
//        Put this script in a prim that is long in the X direction, it will fly in the
//            direction of it's local X axis. Paint it with a texture that allows you to
//            tell which way is the top (the local Y axis).
//
//      This simple critter behaves in the following ways:
//  It moves in a straight line until:
//  dives into the water but flies back out
//  avoids getting close to the ground
//  stays below a maximum height
//  turns away from the SIM edges
//  turns away from parcel boundary edges.

float DEFLECT=0.20;     //how hard things deflect me away (m/s/s)
float MAXHEIGHT=20.0;   //how high I'm allowed above the ground
float LOOKAHEAD=4.0;    //how far to look into the future to avoid stuf
integer run=1;          //flag indicating if I'm running or not
list params=[           //make the smoke
            PSYS_PART_FLAGS,0 ,
            PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP ,
            PSYS_SRC_BURST_RATE,0.125,
            PSYS_SRC_BURST_PART_COUNT ,1,
            PSYS_PART_MAX_AGE,30.0,
            PSYS_PART_START_SCALE, <.75,.75,0>,    //magenta smoke
            PSYS_PART_START_COLOR, <1,0,1>
];

//  This function, OKfly, checks for all the places that you don't want your prim to move.
//  If your prim moves off the sim edges, your script will stop running.
//  If your prim tries to move underground, the script keeps running but motion stops.
//  I use parcels to create boundaries where the critter has to stay, so I test
//      for parcel boundaries here also.
//  You could test for other things, like a fish can't leave the water or a bird
//      can't enter water, but fish jump and birds dive so I do that elsewhere.
//    Nothing prevents this critter from passing through solid objects, even if it is not
//        phantom. But if it is not phantom it will stop when colliding with an avatar.
integer OKfly(vector pos)   //return true if OK to move to pos
{
            //don't go outside the edges of the MEGA region
    vector size=osGetRegionSize();
    if (pos.x<=0 || pos.x>=size.x || pos.y<=0 || pos.y>=size.y)
        return FALSE;;
    if (pos.z<llGround(pos-llGetPos()))     //don't go below ground
        return FALSE;
            //don't leave the current parcel
    key curpar = llList2Key(llGetParcelDetails(llGetPos(),[PARCEL_DETAILS_ID]),0);
    key nxtpar = llList2Key(llGetParcelDetails(pos,       [PARCEL_DETAILS_ID]),0);
    if (curpar!=nxtpar)
        return FALSE;
            //insert other rules here, like fish not allowed out of the water
    return TRUE;    //otherwise, it is OK to fly here!
}



//Single Frame motion routines
//    These 3 functions impliment smooth motion using llSetKeyFramedMotion
//    The way it works is this:
//        You call SFrame with a new position, rotation and time
    //  I usually call SFrame once a second from the timer event with an updated 
    //position and rotation.
    //But it works over longer times and distances.
    //UNLIKE llSetKeyframedMotion, here you specify the region co-ordinates and rotation!
    //I did it in these tree functions to make it modular, I can replace all 3 of them
    //with routines that work on grids that don't have llSetKeyframedMotion

SFsetup()        //any necessary setup
{        //This setup is necessary in SL, and may cause errors on Open Sim, if so delete it
    llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
}

    //move the calling root prim from its current position and rotation
    //to the requested position and rotation in the requested time.
    //UNLIKE llSetKeyframedMotion, you specify the region co-ordinates and rotation!
SFrame(vector pos,rotation rot, float seconds)
{
        llSetKeyframedMotion([pos-llGetPos(),rot/llGetRot(),seconds],
            [KFM_MODE,KFM_FORWARD,KFM_DATA,KFM_TRANSLATION|KFM_ROTATION]);
}

SFstop()        //stop the key framed motion
{
        llSetKeyframedMotion([],[KFM_COMMAND,KFM_CMD_STOP]);
}

SFnotat()        //call this from your not_at_target event
{        //not necessary with llSetKeyframedMotion, so this function is a NOP
}

default
{
    state_entry()
    {
        llOwnerSay("reset");
        llParticleSystem(params);    //Start the smoke
        llSetTimerEvent(1.0);   //recalculte movement every second
        SFsetup();
    }
    
    on_rez(integer param)
    {       //turn to a random direction on rez
        llSetRot(llEuler2Rot(<0,llFrand(PI-PI/2.0),llFrand(TWO_PI)>));
    }
    
    timer()     //every second, calculate a new direction to turn and move
    {
        vector pos=llGetPos();       //get my current position
        rotation rot=llGetRot();    //and rotation
        
        vector vel=<1,0,0>*rot;      //use my direction as velocity

             //here are the RULES that give this critter behavior:
            //first (four) rule(s): Avoid the edges of the sim.
        vector xvel=llVecNorm(<vel.x,0,0>);   //get orthagonal components of velocity
        vector yvel=llVecNorm(<0,vel.y,0>);
        if (!OKfly(pos+LOOKAHEAD*xvel))     //so you can pong off the edges of the sim
            vel -= DEFLECT*xvel;           //slow down as you approach X edge.
        if (!OKfly(pos-LOOKAHEAD*xvel))   //checking both sides makes me
            vel += DEFLECT*xvel;         //accelerate away from walls
        if (!OKfly(pos+LOOKAHEAD*yvel))     //do the same thing in Y
            vel -= DEFLECT*yvel;
        if (!OKfly(pos-LOOKAHEAD*yvel))
            vel += DEFLECT*yvel;

            //I could use LOOKAHEAD to avoid running into the water (I do that for the
            //ground below) but I thought it would be fun to allow this flyer to hit the
            //water and THEN accelerate it up to fly back out.
        float wat=llWater(ZERO_VECTOR);
        if (pos.z<wat)         //after I have already dipped into the water,
            vel += <0,0,1>*DEFLECT;             //accelerate back up
        
            //if you don't have some sort of MAXHEIGHT test, the critter would fly up
            //and never come back. I turn back at MAXHEIGHT above the land OR water.
            //(if you just tested land, you would have trouble when the water was
            //over MAXHEIGHT deep)
        float gnd=llGround(ZERO_VECTOR);
        if (gnd>wat)
            wat=gnd;        //use the max of water and ground for height limit
        if (pos.z>(wat+MAXHEIGHT))       //if I get too high
            vel -= <0,0,1>*DEFLECT;     //accelerate back down

            //When the critter gets within LOOKAHEAD meters of the ground, I start
            //accelerating back up. Using the ground normal makes it turn sideways
            //away from cliffs instead of always turning straight up.
        vector npos=pos+vel;             //next position
        if ((npos.z-LOOKAHEAD)<gnd)     //if my next position is too close to the ground
            vel += llGroundNormal(vel)*DEFLECT;     //deflect away from the ground normal

            //I'm limiting this critter to 1 meter per second, you could go faster
            //but beware, llAxes2Rot requires unit vectors! You would have to
            //calculate a separate vector that is the normalized velocity and use that below.
        vel = llVecNorm(vel);       //limit my velocity to 1m/sec
        
            //here I convert the velocity vector into a rotation. These steps result in the
            //prim always rotating to keep the head "up". Actually the local Y axis is always
            //parallell to the XY plane, the local Z axis is allowed to rotate away from
            //straight up to turn the nose to rise or fall.
        vector lft=llVecNorm(<0,0,1>%vel);
        rot = llAxes2Rot(vel,lft,vel%lft);  //calculate new rotation in direction of vel

            //SFrame will refuse to go somplace that OKfly does not like, but I do
            //another test here and try to do something smarter to avoid getting stuck.
        if (!OKfly(pos+vel))        //final test: If I'm still going out of bounds,
        {
            if (llVecMag(lft)<0.5)      //detect Gymbol lock!
                lft=<0,1,0>;            //and make a hard turn in this unusual case.
            vel = llVecNorm(vel+lft*(llFrand(2.0)-1.0));  //randomly turn left or right
            lft=llVecNorm(<0,0,1>%vel);             //to try to get out of edge lock
            rot = llAxes2Rot(vel,lft,vel%lft);      //re-calc the rotation
            vel=ZERO_VECTOR;            //stop and wait for rotation to turn me
        }
        SFrame(pos+vel,rot,1.0);        //start moving and turning 
    }
            //once SFrame is running, even the build dialog cannot change the position
           //or rotation without it being changed back. So I added this touch to stop the
          //critter so I could move it or rotate it, then touch start it moving again.
    touch_start(integer num)
    {
        if ((run = run^1) ==0)      //toggle the run flag
        {
            llSetTimerEvent(0);
            SFstop();               //demonstrate how to use SFstop!
            llParticleSystem([]);   //turn off the smoke while stopped.
            llOwnerSay("stopped");
        }
        else
        {
            llSetTimerEvent(1.0);       //the next call to SFrame in timer will start it up again
            llParticleSystem(params);
            llOwnerSay("running");
        }
    }
}

These users thanked the author Kayaker Magic for the post (total 6):
Selby EvansConstance PeregrineMarstol Nitelybeuving BeeswingGraham MillsGregg Legendary
User avatar
Selby Evans
Posts: 620
Joined: Wed Sep 04, 2013 6:00 pm
Has thanked: 1840 times
Been thanked: 822 times

Re: Sample Use of llSetKeyframedMotion

Post by Selby Evans »

Thanks, I found it
User avatar
Ilan Tochner
Posts: 6536
Joined: Sun Dec 23, 2012 8:44 am
Has thanked: 5000 times
Been thanked: 4474 times
Contact:

Re: Sample Use of llSetKeyframedMotion

Post by Ilan Tochner »

Thank you Kayaker,

Could you please update the original thread to point to this as well, and explain that the previous version shouldn't be used due to the avoidable server resources cost it creates?

This can help ensure that performance remains high and people will be able to have more scripted items in their world.
Post Reply