# Rotations.

## Recommended Posts

Posted (edited)

I started this about a month ago as a sort of guide/How to on rotations, but it turns out I'm not amazing at explaining things, so it kind of morphed into a library of functions with some filler in-between. Had I all the time in the world maybe I would expand my verbiage, but I've gotten tired of thinking about it, so hopefully it's still useful, at the very least for the code snippets.

Part 0: Non-rotational geometry problems.
0a: Gimme a point!
For getting the Position of an in-world object of interest, use
http://wiki.secondlife.com/wiki/LlGetPos
http://wiki.secondlife.com/wiki/LlGetObjectDetails   or occasionally
http://wiki.secondlife.com/wiki/LlDetectedPos
Since any two points define a line, you can take a weighted sum of two (or more) points to get another point on the line they define:

```vector PosInterp(vector a,vector b,float f)
{   return a*(1-f) + b*(f);
}```

Will return a vector f perunit(percent/100) of the way between a and b.
it's sometimes easier to think about it as scaling a vector:

`return a+f*(b-a);`

is mathematically equivalent.

0b: Gimme a length!
Woa there's a function that does exactly this:
http://wiki.secondlife.com/wiki/LlVecDist
As noted in the wiki, you can use llVecMag(a-b); instead.
0c: Gimme an angle!
<insert discussion on the differences between radians and degrees>
Of all the trigonometric functions, llAtan2() is the answer to 'how do I find this angle?' in 90% of cases you will run into in LSL:
http://wiki.secondlife.com/wiki/LlAtan2
The wikipedia article linked to from the wiki explains it fairly well.
It can be quite useful to pair this with:
http://wiki.secondlife.com/wiki/LlDetectedTouchST
to make (for example) a turnable dial:
rez a cylinder prim and set its dimensions to 0.5,0.5,0.05, and link a Prism with dimensions 0.25, 0.05, 0.05 on top of it.
link the Prism to the Cylinder (cylinder as the root) and add this script:

```default
{   touch(integer total_number)
{   vector touchST = llDetectedTouchST(0)-<0.5,0.5,0>;
// subtract 0.5 from both coordinates to move the origin from the corner to the center of the face.
[   PRIM_ROT_LOCAL, llAxisAngle2Rot(<0,0,1>,
//There is never a good reason to use PRIM_ROTATION, which only works on the root prim.
llAtan2(touchST.y,touchST.x))
//It can be confusing to remember that y comes before x.
]);
}
}```

For calculating the angle between three arbitrary points in (3d) space you can use:

```float uAngleBetween(vector a,vector b,vector c)
{   return llRot2Angle(llRotBetween(c-b,a-b));
}```

Note that this will only return a positive angle between 0 and PI radians (0 and 180 degrees);
In 3d space, there is no way to distinguish between angles abc and cba like in 2d space. (imagine flipping over the piece of paper the angle is on and looking at it from the other side)

Part 1: Rotations as specific Orientations.
There are (at least) 2 semantically different ways of thinking about vectors in 3-dimensional space:
1. As a transformation. You can visualize a vector as a little arrow that points from the origin to some point in space, and by applying that arrow to the origin, you get the point in question.
2. As a static 'point'. The result of applying a vector to the origin is the point represented by the vector.
In a similar way, you can either think of a rotation as a transformation which rotates a reference frame in some way, or as the result of that transformation. While most other tutorials and introductions to rotations lean on the first method of understanding, as it is the most useful for understanding the calculations behind rotations, Understanding them as static orientations, is in my view often easier for using them in a practical context.

1a: getting and setting orientations.
Note that there is a small bug with llSLPPF:
https://jira.secondlife.com/browse/SVC-93
when setting the rotation of a child prim, it is important to use PRIM_ROT_LOCAL rather than PRIM_ROTATION.
To set a prim to a rotation obtained from the build menu, use:
When setting a prims rotation like this, it is always relative to the rotation of the root. you can cancel out the root's rotation to set to a global orientation:
The 24 rotational symmetries of a cube can occasionally come in handy:

```list Rotations =
[ // Notice: in LSL the real component of the quaternion is listed last. In other words: i,j,k,r.
// I don't think there's much logic to whether I chose the positive or negative version of any given rotation.
< 0.00, 0.00, 0.00, 1.00>,"Face East; Up Up   ", //
< 0.00, 0.00, 0.71, 0.71>,"Face North; Up Up  ", //
< 0.00, 0.00, 1.00, 0.00>,"Face West; Up Up   ", //
< 0.00, 0.00, 0.71,-0.71>,"Face South; Up Up  ",//

< 1.00, 0.00, 0.00, 0.00>,"Face East; Up Down ", //
< 0.71, 0.71, 0.00, 0.00>,"Face North; Up Down",//
< 0.00, 1.00, 0.00, 0.00>,"Face West; Up Down ", //
< 0.71,-0.71, 0.00, 0.00>,"Face South; Up Down",//

< 0.00, 0.71, 0.00, 0.71>,"Face Down; Up East ", //
< 0.50,-0.50, 0.50,-0.50>,"Face South; Up East",//
< 0.71, 0.00, 0.71, 0.00>,"Face Up; Up East   ", //
< 0.50, 0.50, 0.50, 0.50>,"Face North; Up East",//

<-0.50, 0.50, 0.50, 0.50>,"Face Down; Up North",//
< 0.00, 0.71, 0.71, 0.00>,"Face West; Up North", //
< 0.50, 0.50, 0.50,-0.50>,"Face Up; Up North  ",//
< 0.71, 0.00, 0.00,-0.71>,"Face East; Up North",//

< 0.71, 0.00,-0.71, 0.00>,"Face Down; Up West ",//
<-0.50, 0.50, 0.50,-0.50>,"Face South; Up West",//
< 0.00, 0.71, 0.00,-0.71>,"Face Up; Up West   ",//
< 0.50, 0.50,-0.50,-0.50>,"Face North; Up West",//

< 0.50, 0.50,-0.50, 0.50>,"Face Down; Up South",//
< 0.00, 0.71,-0.71, 0.00>,"Face West; Up South",//
< 0.50,-0.50, 0.50, 0.50>,"Face Up; Up South  ",//
< 0.71, 0.00, 0.00, 0.71>,"Face East; Up South", //

// - - - - - - - -

< 0.00, 0.00, 0.00, 1.00>,"1", //face east up up
< 0.00, 0.00, 1.00, 0.00>,"2", //face west up up
< 0.00, 1.00, 0.00, 0.00>,"3", //face west up down
< 1.00, 0.00, 0.00, 0.00>,"4", //face east up down
< 0.00, 0.00, 0.71, 0.71>,"5", //face north up up
< 0.00, 0.71, 0.00, 0.71>,"6", //face down up east
< 0.71, 0.00, 0.00, 0.71>,"7", //face east up south
< 0.00, 0.71, 0.71, 0.00>,"8", //face west up north
< 0.71, 0.00, 0.71, 0.00>,"9", //face up up east
< 0.71, 0.71, 0.00, 0.00>,"10",//face north up down
< 0.00, 0.00, 0.71,-0.71>,"11",//face south up up
< 0.00, 0.71, 0.00,-0.71>,"12",//face up up west
< 0.71, 0.00, 0.00,-0.71>,"13",//face east up north
< 0.00, 0.71,-0.71, 0.00>,"14",//face west up south
< 0.71, 0.00,-0.71, 0.00>,"15",//face down up west
< 0.71,-0.71, 0.00, 0.00>,"16",//face south up down
< 0.50, 0.50, 0.50, 0.50>,"17",//face north up east
< 0.50, 0.50, 0.50,-0.50>,"18",//face up up north
< 0.50, 0.50,-0.50, 0.50>,"19",//face down up south
< 0.50,-0.50, 0.50, 0.50>,"20",//face up up south
<-0.50, 0.50, 0.50, 0.50>,"21",//face down up north
< 0.50, 0.50,-0.50,-0.50>,"22",//face north up west
< 0.50,-0.50, 0.50,-0.50>,"23",//face south up east
<-0.50, 0.50, 0.50,-0.50>,"24",//face south up west

ZERO_ROTATION,"ZERO_ROTATION"// no final Comma.

];
integer index=-1;
default
{
touch_start(integer total_number)
{   ++index;
if(index==llGetListLength(Rotations))
{   index = 0;
}
rotation rot = llList2Rot(Rotations,index)
llSetRot(rot);
++index;
llSetText(
llList2String(Rotations,index),
<0.0,1.0,0.0>,1.0);
}
}```

1b: Yaw Pitch Roll
Unfortunately llEuler2Rot and llRot2Euler do not use a convention which is immediately intuitive. The equivalent functions for Yaw,Pitch and Roll
https://simple.wikipedia.org/wiki/Pitch,_yaw,_and_roll

```rotation YPR2Rot(vector v) // Yaw Pitch Roll
{   return
<llSin(v.z/2),0,0,llCos(v.z/2)> *
<0,llSin(v.y/2),0,llCos(v.y/2)> *
<0,0,llSin(v.x/2),llCos(v.x/2)> ;
}```
```vector Rot2YPR(rotation q)
{ //implementation by Mollymews
// https://community.secondlife.com/forums/topic/471561-urot2euler/?do=findComment&comment=2293046
vector v;
// x-axis
float s = 2 * (q.s * q.x + q.y * q.z);
float c = 1 - 2 * (q.x * q.x + q.y * q.y);
v.z = llAtan2(s, c);
// y-axis
s = 2 * (q.s * q.y - q.z * q.x);
if (llFabs(s) >= 1)
v.y = (-1 | (s < 0)) * PI_BY_TWO;
else
v.y = llAsin(s);
// z-axis
s = 2 * (q.s * q.z + q.x * q.y);
c = 1 - 2 * (q.y * q.y + q.z * q.z);
v.x = llAtan2(s, c);
return v;
}```
```vector Rot2YPR(rotation rot)
{//my alternate implementation
vector ret;
vector temp = <1,0,0>*rot;
ret.x = llAtan2(temp.y,temp.x);
rot = rot/llEuler2Rot(<0,0,ret.z>);
temp = <1,0,0>*rot;
ret.y = -llAtan2(temp.z,temp.x);
rot = rot/llEuler2Rot(<0,ret.y,0>);
temp = <0,1,0>*rot;
ret.z = llAtan2(temp.z,temp.y);
return ret;
}```

1c LookAt
While ll provides 2 functions llLookAt() and llRotLookAt() to make an object look at a target, they are a bit limited in scope, and not as generally useful as being able to calculate the rotation which would cause your object to look at a given target.
This is easily doable by combining llAtan2 with YPR2rot:

```rotation uVectorRoll2Rot(vector v,float r)
{   return YPR2Rot(<llAtan2(v.y,v.x),-llAtan2(v.z,llVecMag(<v.x,v.y,0>)),r>);
}```

This will return a rotation that points the local x-axis (the forward vector) to vector v.
Note that there is a similar ll function:
http://wiki.secondlife.com/wiki/LlRotBetween
but due to how rotations work, this can introduce some extra roll into the rotation if used carelessly.
try it in a basic look-at-owner script.

```default
{   state_entry()
{   while(TRUE)
{   vector target=llList2Vector(
llGetObjectDetails(llGetOwner(),
[OBJECT_POS]),0)-llGetPos();
//Use one or the other and compare:
rotation rot = // choose 1
uVectorRoll2Rot(target,0);
//llRotBetween(<1,0,0>,target);
//llAxisAngle2Rot(<0,0,1>,llAtan2(target.y,target.x));
//we can change the 'forward axis' with a left multiplication:
//rot = llRotBetween(<0,0,1>/*forward axis*/ , <1,0,0>) * rot;
llSetRot(rot);
}
}
}```

1d: Reference Frames
While in an orientation-centric mindset, we can understand the product of a vector v and a rotation r (v*r) as 'place v into reference frame r' and similarly we can view the product of 2 rotations q and r as 'place q in reference frame r':

```uMoveToTargetKey(key target, vector offset, rotation localRot)
{  list det = llGetObjectDetails(target,[OBJECT_POS,OBJECT_ROT]);
vector targetPos=llList2Vector(det,0);
rotation targetRot = llList2Rot(det,1);
[   PRIM_POSITION,targetPos+offset*targetRot,
PRIM_ROTATION, localRot*targetRot
]);
}```

1e: interpolation
Similarly to

```vector PosOnLine(vector a,vector b,float f)
{   return a*(1-f) + b*(f);
}```

you can linearly interpolate between quaternions:

```rotation RotInterp(rotation a,rotation b,float f)
{   return // return a rotation f perunit of the way between a and b.
<a.x*(1-f) + b.x*f ,
a.y*(1-f) + b.y*f ,
a.z*(1-f) + b.z*f ,
a.s*(1-f) + b.s*f>;
}// or at least it seems to work.```

but this will give you a non-normalized quaternion (see below).
A more correct way of doing this would be:

```rotation RotInterp(rotation a,rotation b,float f)
{//alternate implementation.
rotation rot = llRotBetween(llRot2Fwd(a),llRot2Fwd(b));
//Untested but should be equivalent:
//rotation rot = (ZERO_ROTATION/a)*b;
return a*llAxisAngle2Rot(llRot2Axis(rot),llRot2Angle(rot)*f);
}```

2: miscellaneous.

llEuler2Rot(<x,0,0>); == llAxisAngle2Rot(<1,0,0>,x); == <llSin(x*0.5),0,0,llCos(x*0.5)>;
llEuler2Rot(<0,y,0>); == llAxisAngle2Rot(<0,1,0>,y); == <0,llSin(y*0.5),0,llCos(y*0.5)>;
llEuler2Rot(<0,0,z>); == llAxisAngle2Rot(<0,0,1>,z); == <0,0,llSin(z*0.5),llCos(z*0.5)>;
rotation uAxisAngle2Rot(vector v, float a)
{   v*= llSin(a*0.5);
return <v.x,v.y,v.z,llCos(a*0.5)>;
}

Consider using llAxisAngle2Rot() instead of llEuler2Rot() in example scripts.
llEuler2Rot is really only useful for single axis-rotations and using the numbers obtained from the build window.

<1,0,0>*<0,0,0,3> == <9,0,0>;
but llSetRot(<0,0,0,3>); does not scale an object by 9.
Quaternion multiplication scales a vector by the square of the quat's magnitude.
use

```rotation NormRot(rotation Q)
{   // taken from the wiki http://wiki.secondlife.com/wiki/LlSetKeyframedMotion
float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
return
<Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}```

to normalize a quaternion so that it does not scale a vector.

<-x,-y,-z,-r> represents the same rotation as <x,y,z,r>
<-x,-y,-z,+r> == <0,0,0,1>/<x,y,z,r> == the 'opposite' or inverse of <x,y,z,r>

Edited by Quistessa
sudden epiphany for RotInterp.
• 2
• 2
##### Share on other sites

-- extra scripts - - -
Gimbals 1: this is a touch to set gimbal.
also, this is 4 scripts in 1, uncomment/recomment the relevant lines to switch between:
//XYZ intrinsic (local),
//ZYX intrinsic (local), (Pitch, Yaw, Roll)
//XYZ extrinsic (global),
//ZYX extrinsic (global),

```list gimbal_links=[0,0,0];

vector Euler_rot=<0,0,0>;
unrot_gimbals()
{   rotation RR = llGetRootRotation();
//we cancel out the root's rotation along the axis of the gimbal so that the uv (used in llAtan2) still makes sense.
[PRIM_ROT_LOCAL,<0.00,0.71,0.00,0.71>
*llEuler2Rot(<-Euler_rot.x,0,0>)]); // local gimbal
// /RR]); // global gimbal
[PRIM_ROT_LOCAL,<0.71,0.00,0.00,0.71>
*llEuler2Rot(<0,-Euler_rot.y,0>)]); // local gimbal
// /RR]); // global gimbal
[PRIM_ROT_LOCAL,
llEuler2Rot(<0,0,-Euler_rot.z>)]); // local gimbal
// /RR]); // global gimbal
}

rotation uEuler2Rot2(vector v)
{   return //ZYX intrinsic = XYZ extrinsic
<llSin(v.x/2),0,0,llCos(v.x/2)> *
<0,llSin(v.y/2),0,llCos(v.y/2)> *
<0,0,llSin(v.z/2),llCos(v.z/2)> ;
}

default
{
state_entry()
{
integer index = llGetNumberOfPrims()+1;
while(--index>=2)
[   PRIM_TEXTURE, 0, "b2d5eb75-b4bf-7763-28dc-d46a03f8e992",
<1,1,0>,<0,0,0>,-PI_BY_TWO,
PRIM_TEXTURE, 3, "571d44bb-8e5f-a579-eaec-cdb0bd109b29",
<1,1,0>,<0,0,0>,-PI_BY_TWO
]);
[   PRIM_TEXTURE, 0, "571d44bb-8e5f-a579-eaec-cdb0bd109b29",
<1,1,0>,<0,0,0>,-PI_BY_TWO,
PRIM_TEXTURE, 3, "b2d5eb75-b4bf-7763-28dc-d46a03f8e992",
<1,1,0>,<0,0,0>,-PI_BY_TWO
]);
[   PRIM_TEXTURE, 0, "b2d5eb75-b4bf-7763-28dc-d46a03f8e992",
<1,1,0>,<0,0,0>,-PI_BY_TWO,
PRIM_TEXTURE, 3, "571d44bb-8e5f-a579-eaec-cdb0bd109b29",
<1,1,0>,<0,0,0>,-PI_BY_TWO
]);
}
}
//initialize rotation and settext.
llSetText("ZYX intrinsic\n"+(string)Euler_rot,<0.0,1.0,0.0>,1.0);
llSetRot(llEuler2Rot(Euler_rot));
unrot_gimbals();
}

touch_start(integer total_number)
{
vector st = llDetectedTouchST(0);
// -0.5 puts the origin in the center
float angle = llAtan2(st.y-0.5,st.x-0.5);
//llOwnerSay((string)llDetectedTouchFace(0));
integer face = llDetectedTouchFace(0);
{   if(face==0)
{   Euler_rot.x=angle;
}else if(face==3)
{   Euler_rot.x=-angle;
}
{   if(face==3)
{   Euler_rot.y=angle;
}else if(face==0)
{   Euler_rot.y=-angle;
}
{   if(face==0)
{   Euler_rot.z=angle;
}else if(face==3)
{   Euler_rot.z=-angle;
}
}else
{   Euler_rot=<0,0,0>;
}
llSetText("ZYX intrinsic\n"+(string) //use uEuler2Rot2
//llSetText("ZYX extrinsic\n"+(string) //use llEuler2Rot
//llSetText("XYZ extrinsic\n"+(string) //use uEuler2Rot2
//llSetText("XYZ intrinsic\n"+(string) //use llEuler2Rot
llSetRot(uEuler2Rot2(Euler_rot));
//llSetRot(llEuler2Rot(Euler_rot));
unrot_gimbals();
}
}```

***
Gimbals 2
these are click-drag to rotate.
2 scripts in one, see comments.
Should auto-assemble after touched if linked prims are named correctly,
suggested gimbal radii 0.8 1.0 and 1.25 with 80% hollow, other objects all dimensions 0.2.
***

```list gimbal_links=[0,0,0];
//Base,UP,DWN,LFT,RGH,FWD,BCK
list object_offsets = // when root in zero_rotation.
[   <0.00,1.00,0.00>, // first is offset from root, then offsets from this offset.
< 0.00, 0.00, 0.20>, // UP
< 0.00, 0.00,-0.20>, //DWN
< 0.00, 0.20, 0.00>, //LFT
< 0.00,-0.20, 0.00>, //RGH
< 0.20, 0.00, 0.00>, //FWD
<-0.20, 0.00, 0.00> //BCK

];
list gimbal_offsets =
[
< 0.00, 1.00, 0.00>, //Gimbal X
< 0.00, 0.00, 0.00>, //Gimbal Y
< 0.00, 0.00, 0.00>  //Gimbal Z
];
list gimbal_rotations =
[
< 0.00, 0.71, 0.00, 0.71 >, //Gimbal X
< 0.71, 0.00, 0.00, 0.71 >, //Gimbal Y
< 0.00, 0.00, 0.00, 1.00 > //Gimbal Z
];
list object_rotations = // when root in zero_rotation
[   < 0.00, 0.00, 0.00, 1.00 >, //Base
< 0.00, 0.00, 0.00, 1.00 >, //UP
< 1.00, 0.00, 0.00, 0.00 >, //DWN
< 0.00, 0.71, 0.71, 0.00 >, //LFT
< 0.71, 0.00, 0.00, 0.71 >, //RGH
< 0.50, 0.50, 0.50, 0.50 >, //FWD
<-0.50, 0.50, 0.50,-0.50 > //BCK
];
rotation gRotBase = <0,0,0,1>; // rotation of example structure
vector start_angle = <0,0,0>; //angle on touch_start.

uSetSubRot(list links,list offsets,list rotations,rotation rot)
{
integer index = llGetListLength(links);
if(index!=llGetListLength(offsets))
{   llSay(DEBUG_CHANNEL,"list length mismatch in uSetSubRot()");
return;
}if(index!=llGetListLength(rotations))
{   llSay(DEBUG_CHANNEL,"list length mismatch in uSetSubRot()");
return;
}
vector base = llList2Vector(offsets,0);
rotation baserot = llList2Rot(rotations,0);
while(--index>=1)
{
[PRIM_POS_LOCAL,
base+
llList2Vector(offsets,index)*
rot*baserot,
PRIM_ROT_LOCAL,
llList2Rot(rotations,index)*
rot
]);
}   // set base rotation :
[PRIM_POS_LOCAL,
llList2Vector(offsets,index),
PRIM_ROT_LOCAL,
llList2Rot(rotations,index)*
rot
]);
}

vector uGetGimbalAngle() // must be run in a touch event.
{
vector st = llDetectedTouchST(0);
// -0.5 puts the origin in the center
float angle = llAtan2(st.y-0.5,st.x-0.5);
//llOwnerSay((string)llDetectedTouchFace(0));
integer face = llDetectedTouchFace(0);
vector return_angle;
{   if(face==0)
{   return_angle.x=angle;
}else if(face==3)
{   return_angle.x=-angle;
}
{   if(face==3)
{   return_angle.y=angle;
}else if(face==0)
{   return_angle.y=-angle;
}
{   if(face==0)
{   return_angle.z=angle;
}else if(face==3)
{   return_angle.z=-angle;
}
}else
{   return_angle=<0,0,0>;
}
return return_angle;
}

vector uRot2Euler2(rotation rot)
{
vector ret;
vector temp = <1,0,0>*rot;
ret.z = llAtan2(temp.y,temp.x);
rot = rot/llEuler2Rot(<0,0,ret.z>);
temp = <1,0,0>*rot;
ret.y = -llAtan2(temp.z,temp.x);
rot = rot/llEuler2Rot(<0,ret.y,0>);
temp = <0,1,0>*rot;
ret.x = llAtan2(temp.z,temp.y);
return ret;
}
vector uRot2Euler(rotation rot)
{
vector ret;
vector temp = <0,1,0>*rot;
ret.x = llAtan2(temp.z,temp.y);
rot = rot/llEuler2Rot(<ret.x,0,0>);
temp = <1,0,0>*rot;
ret.y = -llAtan2(temp.z,temp.x);
rot = rot/llEuler2Rot(<0,ret.y,0>);
temp = <1,0,0>*rot;
ret.z = llAtan2(temp.y,temp.x);
return ret;
}
default
{
state_entry()
{
integer index = llGetNumberOfPrims()+1;

while(--index>=1)
{   // I really should have set a variable to llGetLinkName(index) and checked against that,
// but I'm too lazy to search-replace it.
}
}
llSetText("",<0.0,0.0,0.0>,0.0);
}

touch_start(integer total_number)
{
{   uSetSubRot(
object_offsets,
object_rotations,
gRotBase=<0,0,0,1>
);
}
start_angle=uGetGimbalAngle();

}
touch(integer i)
{
vector now_angle = uGetGimbalAngle();
rotation rot = llEuler2Rot(now_angle-start_angle)*gRotBase;
uSetSubRot(
object_offsets,
object_rotations,
rot
);
llSetText("Rotation:\n"+
(string)(rot)+"\n"+
"XYZ intrinsic:\n"+
"\nZYX intrinsic:\n"+
,<0,1.0,0>,1.0);
//llSetText("Rotation:\n"+ //comment out settext above and uncomment this for extrinsic.
//    (string)(rot)+"\n"+
//    "ZYX extrinsic:\n"+
//    "\nXYZ extrinsic:\n"+
//,<0,1.0,0>,1.0);

}
touch_end(integer i)
{
vector now_angle = uGetGimbalAngle();
gRotBase=llEuler2Rot(now_angle-start_angle)*gRotBase;

uSetSubRot( // comment this out for extrinsic rotation.
gimbal_offsets,
gimbal_rotations,
gRotBase
);
}
}```

##### Share on other sites

For my own sanity I'll leave that last example uncorrected, but uRot2Euler was intended to function like llRot2Euler, the correct implementation being:

```vector uRot2Euler(rotation rot)
{
vector euler;
vector temp;
// x-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.x = llAtan2(temp.z, temp.y) - PI_BY_TWO;
rot /= llEuler2Rot(<euler.x, 0.0, 0.0>);
// y-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.y = -llAtan2(temp.z, temp.x) + PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.y, 0.0>);
// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.y, temp.x);
return euler;
}```

• 1
##### Share on other sites

Looking at this again. I forgot to add a comment in that the touch-grab gimbal example, to modify it to use global rotations, you also need to replace

`gRotBase=llEuler2Rot(now_angle-start_angle)*gRotBase;`

with

`gRotBase=gRotBase*llEuler2Rot(now_angle-start_angle);`

in both the touch and touch_end events.

## Create an account or sign in to comment

You need to be a member in order to leave a comment

## Create an account

Sign up for a new account in our community. It's easy!

Register a new account