# llSetKeyframedMotion() broken?

Please take a moment to consider if this thread is worth bumping.

## Recommended Posts

I made this script to turn a box-prim 90 degrees around one edge.
It is meant to be a general script that works on the same edge no matter the prim orientation.
Each touch reverse the direction, it is meant to open and close on touches.

```integer open=FALSE;
float angle=PI_BY_TWO; // angle to turn [radians]

turn( float ang, float time)
{
vector v1 = 0.5*llGetScale()*llGetRot();
rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), ang);
llSetKeyframedMotion([v1*deltaRot-v1, deltaRot, time], []);
}

default
{
state_entry()
{
}
touch_end(integer total_number)
{
open=!open;
if (open) turn( angle, 2.0);
else turn( -angle, 2.0);
}
}```

It works fine as long as the Z axis of the prim is vertical.
When it is not vertical the motion goes berserk.

To test the math I substituted the KFM with simple llSetPos and llSetRot functions:

```turn( float ang, float time)
{
vector v1 = 0.5*llGetScale()*llGetRot();
rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), ang);
llSetPos(llGetPos()+v1*deltaRot-v1);
llSetRot(llGetRot()*deltaRot);
}```

With this I get the end results right for all the orientations that do not work with the KFM function.
Am I doing something stupid or is the llSetKeyframedMotion broken?

##### Share on other sites

Yes it is still glitchy yet. Rotation along X looks fine, Y can get a strange elliptical movement but the start and end postiitons will be right. It tends to look better when there is a decent amount of translation, mostly or only rotation really shows off the glitchiness.

##### Share on other sites

I'm getting some random glitchiness when I just rotate on Z, too.  I've been experimenting wih door scripts.  I can create a keyframe - driven door that works beautifully, but one that I Shift-drag a copy of will sometimes have a funny elliptical drift as it rotates.  It's frustrating.  This is SUCH an easy way to create a smooth rotating door, but that little unpredictable drift during the movement is annoying.

##### Share on other sites

Not broken in this cas .

Just debug in displayind llOwnerSay(llList2CSV(yourlistofmotions));

You don t send the same informations between the different clicks

For instance with an inital RotX = 30 degrees

list=, <-0.500000, 0.000000, 0.000000>, <0.000000, -0.353553, 0.612372, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <0.433013, -0.216531, 0.124957>, <0.353554, 0.530310, -0.306221, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <-0.266800, -0.412352, 0.093720>, <0.438756, -0.163451, 0.529884, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <0.114807, 0.406020, -0.268267>, <-0.657138, 0.245159, 0.089818, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <0.068671, -0.362525, -0.337431>, <-0.100604, -0.486981, 0.502721, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <0.145453, 0.005032, 0.478349>, <-0.266578, 0.650714, 0.074215, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <0.208265, -0.415030, -0.185406>, <-0.642050, -0.254229, -0.152117, 0.707107>, 2.000000, 1.570796
[15:01] Object: list=, <-0.267259, -0.401657, -0.131317>, <-0.050111, -0.188837, 0.679581, 0.707107>, 2.000000, 1.570796

I think you want more something like this

`integer open=FALSE;float angle=PI_BY_TWO; // angle to turn [radians]turn( vector v1, float ang, float time){    rotation deltaRot = llEuler2Rot(<0, 0 , ang>);    llSetKeyframedMotion([v1, deltaRot, time], []);    llOwnerSay(llList2CSV(["list=",v1, deltaRot, time, llRot2Angle(deltaRot)]));}default{    state_entry()    {        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); // make client mesh-aware    }    touch_end(integer total_number)    {        open=!open;        if (open) turn( 0.5* llGetScale(), angle, 2.0);        else turn( -0.5* llGetScale(), -angle, 2.0);    }}`

Don t forget that your input to llSKFM are relatives , not absolutes

or something like this

`integer open=FALSE;float angle=PI_BY_TWO; // angle to turn [radians]vector T ;turn( vector v1, float ang, float time){    rotation deltaRot = llEuler2Rot(<0, 0 , ang>);    llSetKeyframedMotion([v1, deltaRot, time], []);    llOwnerSay(llList2CSV(["list=",v1, deltaRot, time, llRot2Angle(deltaRot)]));}default{    state_entry()    {        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); // make client mesh-aware                T = 0.5* llGetScale() * llGetRot();    }    touch_end(integer total_number)    {        open=!open;        if (open) turn( T, angle, 2.0);        else turn( -T, -angle, 2.0);    }}`

##### Share on other sites

Miranda Umino wrote:

Not broken in this cas .

Just debug in displayind llOwnerSay(llList2CSV(yourlistofmotions));

You don t send the same informations between the different clicks

For instance with an inital RotX = 30 degrees

That is no wonder since the prim lands in very odd positions and each new click starts wit the position and orientation the prim has when clicked

##### Share on other sites

No , The third click , in your instance , should have exactly the same inputs  that the first click . See my code at teh last post

You compute in using llGetRot , but rotations in llsetkeyframedmotion rotates always relatively with its actual rotation . You don t need llgetrot

##### Share on other sites

Yes but when the prim has drifted off it is not in same position or rotation as on first click.
Only the relative(in the local prim coordinates) values should be the same.
llSetKeyframedMotion() uses relative values in the global system.

##### Share on other sites

In relative,  you need to send

Keyframe 1 : vector T for translation , rotation R , time t1

KeyFrame 2 : Vector  -T for translation, ZERO_ROTATION / R, time t2

So Keyframe1 +Keyframe2 = T - T = ZERO_VECTOR for translation, so invariant

R * ZERO_ROTATION / R = ZERO_ROTATION , so invariant

##### Share on other sites

`integer open=FALSE;float angle=PI_BY_TWO; // angle to turn [radians]vector T ;rotation R;turn( vector v1, rotation  deltaRot , float time){        llSetKeyframedMotion([v1, deltaRot, time], []);        llSetTimerEvent(time);        if ( TRUE)         state turning;   }default{    state_entry()    {        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); // make client mesh-aware                       vector v1 = 0.5*llGetScale()*llGetRot();        rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), angle);        R =   llEuler2Rot(< 0,0,angle>);        T =  v1*deltaRot-v1;        }    touch_end(integer total_number)    {        turn( T, R, 2.0);    }}state turning{    timer()    {        state waiting;    }}state waiting{    touch_end(integer t)    {        T = -T;        R = ZERO_ROTATION / R;        turn( T, R, 2.0);    }   }`

Avoid to be touched while turning , because it can launch a second animation with wrong initial relative values. If you  care , use KFM_COMMAND :   the commands  KFM_PLAY andKFM_STOP

##### Share on other sites

In fact , in your llsetpos code ,

you do the transformation

llGetRot() * deltaRot  so the result is a global rotation in the world. It s normal because llSetRot waits a global rotation

llSetKeyframedMotion seems to do the inverse :

deltaRot * llGetRot() so the result is a local rotation

For instance with an initial rotation of 30 degrees on X-Axis

llGetRot, <0.258819, 0.000000, 0.000000, 0.965926>,
, llRotup, <0.000000, -0.500000, 0.866025>,
, deltaRot, <0.000000, -0.353553, 0.612372, 0.707107>,
, llGetRot*deltarot, <0.183013, -0.183013, 0.683013, 0.683013>,
, deltaror*llGetRot, <0.183013, -0.500000, 0.500000, 0.683013>,

after motion<0.18302, -0.49997, 0.49997, 0.68305>

`turn( float ang, float time){    llOwnerSay( llList2CSV(    [        "llGetRot", llGetRot(), "\n",        "llRotup",llRot2Up(llGetRot()), "\n",        "deltaRot",llAxisAngle2Rot(llRot2Up(llGetRot()), ang),"\n",        "llGetRot*deltarot",llGetRot()*llAxisAngle2Rot(llRot2Up(llGetRot()), ang),"\n",        "deltaror*llGetRot",llAxisAngle2Rot(llRot2Up(llGetRot()), ang)*llGetRot(),"\n"    ]    ));    vector v1 = 0.5*llGetScale()*llGetRot();    rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), ang);    llSetKeyframedMotion([v1*deltaRot-v1, deltaRot, time], []);    llSleep(time+1.0);    llOwnerSay("after motion"+(string)llGetRot());}`

Taken from the wiki http://wiki.secondlife.com/wiki/Rotation

In LSL, the difference between doing a local or global rotation is the order the rotations are evaluated in the statement.

This does a local 30 degree rotation by putting the constant 30 degree rotation to the left of the object's starting rotation (myRot). It is like the first operation in the first example above, just twisting the dart 30 degrees around its own long axis.

 rotation localRot = rot30X * myRot; // do a local rotation by multiplying a constant rotation by a world rotation

To do a global rotation, use the same rotation values, but in the opposite order. This is like the second operation in the second example, the dart rotating up and to the right around the world X axis. In this case, the existing rotation (myRot) is rotated 30 degrees around the global X axis.

 rotation globalRot = myRot * rot30X; // do a global rotation by multiplying a world rotation by a constant rotation
Let s guess you want to do the same rotation as in your llsetpos code .
You have llGetRot * deltarot = unknownRot * llGetRot
Divide per llGetRot
llGetRot * deltarot / llGetRot = unknownRot * llGetRot / llGetrot = unknownRot
So you should give as parameter to llSetKeyframedMotion:  llGetRot * deltarot / llGetRot for the rotation
##### Share on other sites

@Miranda: Thank you for that. You made me see what it takes.
If I substitute the rotation part of the OP script with llEuler2Rot(< 0,0,ang>), then it works exactly as I want  it to.

What confused me is that The transition part is in relative global parameters and the rotation is in relative local parameters.
I took it for granted both would be one or the other:smileyvery-happy:

In this case a key-frame is defined like this:
The prim moves from it's initial position relatively to a point in region coordinates; this is global.
The prim rotates from it's initial rotation relatively around one of the prim axis no matter how the prim is orientated; this is local.

My final script looks like this:

`integer open=FALSE;float angle=PI_BY_TWO; // angle to turn [radians]turn( float ang, float time){    vector v1 = 0.5*llGetScale()*llGetRot();    rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), ang);    llSetKeyframedMotion([v1*deltaRot-v1, llEuler2Rot(< 0,0,ang>), time], []);}default{    state_entry()    {        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); // make client mesh-aware    }    touch_end(integer total_number)    {        open=!open;        if (open) turn( angle, 2.0);        else turn( -angle, 2.0);    }}`

Note: This script doesn't care for drifting the way Miranda has outlined.
This script is basic to the bone and can be refined and specialized in many ways

##### Share on other sites

Ok , now with this version , you finish your movement where you want .

Nevertheless , your version has again a problem .

What does exactly your prim :

- it rotates around the center of the prim

- it moves the center of the prim from A to B with a straight line

Nevertheless , when you look a door , the center of the door doesn t a movement in a straight line but in a arc of circle .

If you are looking carefully your prim when it rotates, near the axis of rotation , your prim  goes in the back while it rotates the half of the rotation . In the real your door is sticked to the axis of rotation.

For instance let s guess your prim goes from < 1,0,0> to <0,1,0> . At the half , your prim is at <0.5,0.5,0>.

A real door should be at <0.707,0.707,0>. So your movement is wrong.

Problem : there are no commands in llsetkeyframedmotion  to tell to my prim "do a circle"

Problem 2 : there are no interpolation of curves in llSetKeyframedmotion . And we NEED it

So , you need to create a motion with several keyframes . Each one of translations do an approximation of an arc of circle .

For instance with two steps . you go from <1,0,0> to <0.707,0.707,0> ( first keyframe) and from <0.707,0.707,0> to <0,1,0>

But you can repeat with several steps .

Nevertheless :

1) in every keys , because your translation has a slope different , you will have the feeling it s "jerky" when you go from one key to an another key .

2) more you do some keys , more the movement is realistic . It fits better the movement of rotation between the two points

3) more you do some keys ,  less the "jerky" effect will be visible  , because the difference of slopes between the translations will be smaller

4) unfortunately , you can t repeat this at infinity as in mathematics . Because linden impose 0.1 seconds between every keys . So the soltion of door with llsetkeyframedmotion will have always a "jerky" effect

5) you need to be careful of the time in every keys to be multiple of the time of one frame .

6) more you add keys , more you will have approximations of llsetkeyframedmotion , and more you will have drift . Don t think it s an important drift . It s very light , and for your door , with 2 seconds of rotation you will be limited  to 18 keys max , so it won t be important .

But there are some animations who could be more important with sveral thousand of keys .Particulary when the movement does many curves . In this cas , you could have signifant errors

7) May it creates some lag with more keys ? While you build your list of keys certainly , but it s done once time . While the animation is played , i don t know . Maybe yes , maybe no , as the interpolation on the sim is done frame per frame , it has maybe no effects

Finally , the old trick to rotate doors in cutting them by half to have the center of rotation on one side of the prim  will be always useful

This is the code to fix the movement of the center of the prim

`integer open=FALSE;float angle=PI_BY_TWO; // angle to turn [radians]turn( float ang, float time){    list FrameList;    integer maxsteps=(integer)(time * 9);    float newtime = maxsteps / 9.0;    integer i ;    vector v1;    rotation deltaRot = llAxisAngle2Rot(llRot2Up(llGetRot()), ang/(float)maxsteps);    for  ( i = 0; i < maxsteps ; i++)    {        v1 = 0.5*llGetScale() * llEuler2Rot(< 0,0,i * ang/(float)maxsteps>) * llGetRot();                FrameList += [             v1 * deltaRot - v1 ,             llEuler2Rot(< 0,0,ang/(float)maxsteps>),            newtime/(float)maxsteps        ];    }    llSetTimerEvent(newtime);       llSetKeyframedMotion(  FrameList, []);    if ( TRUE )         state turning; }default{    state_entry()    {        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); // make client mesh-aware    }    touch_end(integer total_number)    {        turn( angle, PI);    }}state turning{    timer()    {        state waitingTouch;    }}state waitingTouch{    touch_end(integer t)    {        llOwnerSay((string)llGetPos()+" "+(string)llGetRot());        angle = - angle;        turn( angle, PI);    }   }`

Just for information , i display the rotations and positions to every touchs

I have , for instance

[10:15] Object: <80.78793, 144.08410, 56.51097> <0.18346, 0.01934, 0.57035, 0.80042>
[10:15] Object: <98.93296, 105.77510, 40.66754> <0.11606, 0.14340, -0.16267, 0.96928>
[10:15] Object: <80.78656, 144.08310, 56.51110> <0.18346, 0.01934, 0.57035, 0.80042>
[10:15] Object: <98.93204, 105.77430, 40.66765> <0.11606, 0.14340, -0.16267, 0.96928>
[10:15] Object: <80.78549, 144.08220, 56.51120> <0.18346, 0.01934, 0.57035, 0.80042>
[10:15] Object: <98.93098, 105.77340, 40.66775> <0.11606, 0.14340, -0.16268, 0.96928>
[10:16] Object: <80.78500, 144.08190, 56.51123> <0.18346, 0.01934, 0.57035, 0.80042>

As you can see the drift is low but i don t knwow any  solutions actually to fix it

##### Share on other sites

It's annoying, but at least LL knows about this issue. I opened SVC-7462 and Maestro Linden is able to reproduce it.