Keyframe based Mover

Soen Eber

Vatican mole
VVO Supporter 🍦🎈👾❤
Joined
Sep 20, 2018
Messages
4,055
Keyframe based Mover

Intrasim mover between two arbitrary points in a sim

Setup:
rez a Mover base in one location, then some distance away rez a 2nd Mover base.
Near one of the bases, rez the mover. It should automatically position itself on top of the base.

Both bases must be rezzed before the mover is rezzed. Odd behavior may result.

Usage:
sit in the mover, and enjoy the ride to the other location. If the mover is not at your current base, click on the base to summon it.

notes:
1. The current implementation works with precisely two bases in the sim, no more, no less. This is a code sample for a functioning keyframe based mover.

EDIT: removed legacy code

Base code (the mover will rest on top of this)
Code:
string _me = "kf mover base";
integer _ch_owner;

init()
{
    _ch_owner =
      (integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1));
    llListen(_ch_owner,"","","");
}

default
{
    state_entry()
    {
        init();
    }
    touch_end(integer num_param)
    {
        llRegionSay(_ch_owner, _me+"|kf mover control|go");
    }
    listen(integer ch, string name, key id, string msg)
    {
        if (ch == _ch_owner) {
            list lMsg = llParseString2List(msg, ["|"], []);
            string src = llList2String(lMsg,0);
            string tgt = llList2String(lMsg,1);
            string cmd = llList2String(lMsg,2);
            string arg = llList2String(lMsg,3);
            if ((tgt == _me) || (tgt == "all")) {
                if (cmd == "request") {
                    if (arg == "pos") {
                        llRegionSay(_ch_owner,
                          _me+"|"+src+"|response|"+arg+"|"
                          +(string)llGetPos()
                        );
                    }
                }      
            }
        }
    }
}
kfm Mover code

script _control
Code:
string _me = "kf mover control";
integer _ch_owner; // unique ch is created based on the owner's UUID
//list links = [];
list bases = [];
string timer_context;
vector vStart;
vector vDest;
key kUser;
integer tries;
integer timeout = FALSE;

// ask the two bases for the kf mover to transmit their positions
// this is picked up again in the listen event for _ch_owner
request_base_pos()
{
    bases = [];
    timer_context == "polling";
    if (!timeout) {
        llSetTimerEvent(0.5);
        llRegionSay(
          _ch_owner,
          _me+"|kf mover base|request|pos"
        );
    }
}
init()
{
    // set channel number
    _ch_owner =
      (integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1));
    llListen(_ch_owner,"","","");
    // set pos
    llSitTarget(<0,0,0.8>,llEuler2Rot(<0,0,0>*DEG_TO_RAD));
    kUser = NULL_KEY;
    // keyframe requres PHYSICS_SHAPE_CONVEX
    llSetLinkPrimitiveParamsFast(
      LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]
    );
    timer_context = "polling";
    llSetTimerEvent(0.5);
    request_base_pos();
}
lookAt(vector vLookAt)
{
    vector dir;
    rotation r;

    // ... and calculate rotation for prim/object                 
    dir = llVecNorm(vLookAt - llGetPos());
    r = clamp_to_Z(llRotBetween(<1.0, 0.0, 0.0>, dir));
    llSetRot(r);
}
// clamp rotation to Z axis only
rotation clamp_to_Z(rotation r)
{
    vector v = llRot2Euler(r)*RAD_TO_DEG;
    v.x = 0;
    v.y = 0;        
    return(llEuler2Rot(v*DEG_TO_RAD));
}
// move the keyframe object
go()
{
    lookAt(vDest);
    llMessageLinked(LINK_SET, 0, _me+"|kf mover keyframe|go",NULL_KEY);
}
default
{
    on_rez(integer start_param)
    {
        init();
    }
    state_entry()
    {
        init();
    }
    changed(integer change) {
        if (llAvatarOnSitTarget() != NULL_KEY) {
            kUser = llAvatarOnSitTarget();
            llRequestPermissions(
              llAvatarOnSitTarget(), PERMISSION_TRIGGER_ANIMATION
            );
        }
    }
    run_time_permissions(integer perm) {
        string anim = llGetInventoryName(INVENTORY_ANIMATION, 0);
        if (anim != "") {
            llStopAnimation("sit");
            llStopAnimation("sit_generic");
            llStopAnimation("sit_female");          
            llStartAnimation(anim);
            go();
        }
    }
    moving_end()
    {
        // refresh base info so a new start & dest can be determined.
        llSetPos(vDest);
        llUnSit(kUser);
        request_base_pos();
    }
    listen(integer ch, string name, key id, string msg)
    {
        // tokenize and process link message parameters
        if (ch == _ch_owner) {
            list lMsg = llParseString2List(msg, ["|"], []);
            string src = llList2String(lMsg,0);
            string tgt = llList2String(lMsg,1);
            string cmd = llList2String(lMsg,2);
            string arg = llList2String(lMsg,3);
            if ((tgt == _me) || (tgt == "all")) {
                if (cmd == "go") {
                    go();
                }
                if (cmd == "response") {
                    if (arg == "pos") {
                        vector v = (vector)llList2String(lMsg,4);
                        bases += [v];
                    }
                }
            }
        }
    }
    timer()
    {
        vector pos = llGetPos();
        integer i;
        vector v;
        llSetTimerEvent(0.0);
        timer_context = "";
      
        // After a half second delay to receive and store base pos,
        // record which base is closest, and move to sit on that base.
        if (llGetListLength(bases) != 2) {
            llOwnerSay("** Not enough bases: 2 are requred **");
        }
        for (i=0; i<llGetListLength(bases); i++) {
            v=llList2Vector(bases, i);
            if (i == 0) vStart = v;
            else {
                if (llVecDist(v, pos) < llVecDist(vStart, pos)) {
                    vStart = v;
                }
            }
        }
        llSetPos(vStart);
        // now determine the other base (assuming it is > 1 meter away)
        for (i=0; i<llGetListLength(bases); i++) {
            v = llList2Vector(bases, i);
            if (llVecDist(v, pos) > 1) vDest = v;
        }
        // set start and dest keyframes
        llMessageLinked(LINK_SET, 0, _me+"|kf mover keyframe|setStart|"
          +(string)vStart, NULL_KEY
        );
        llMessageLinked(LINK_SET, 0, _me+"|kf mover keyframe|setDest|"
          +(string)(vDest), NULL_KEY
        );
    }
}
Script keyframe (perform the actual keyframe actions)
Code:
string _me = "kf mover keyframe";
integer status=0;

vector vStart;
vector vDest;
vector vMove;
float  fTransit;
list options=[
    KFM_DATA, KFM_TRANSLATION
];

init()
{
    // clear & stop previous keyframe movement
    // mention of these steps are not clearly stated in documentation
    llSetKeyframedMotion([], []);
    llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
}
default {  
    state_entry() {
        init();
    }

    touch_start(integer total_number) {
    }
   
    link_message(integer source, integer num, string msg, key id)
    {
        //tokenize and process link messages
        list lMsg = llParseString2List(msg, ["|"], []);
        string src = llList2String(lMsg,0);
        string tgt = llList2String(lMsg,1);
        string cmd = llList2String(lMsg,2);
        string arg = llList2String(lMsg,3);
       
        if ((tgt == _me) || (tgt == "all")) {
            if (cmd == "setStart") {
                vStart = (vector)arg;
            }
            else if (cmd == "setDest") {
                vDest = (vector)arg;
                vMove = vDest-vStart;
                fTransit = llVecDist(vStart, vMove)/100;
            }
            else if (cmd == "go") {
                llSetKeyframedMotion([], []);
                llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
                llSetKeyframedMotion(
                  [vMove, 5], [KFM_DATA, KFM_TRANSLATION]
                );
            }
        }
    }
}
 
Last edited:
  • 1Interesting
Reactions: CronoCloud Creeggan