# Rotations.

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

## Recommended Posts

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.
• 3
• 3
##### 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.

{
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
);
}
}```

• 2
##### 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;
}```

• 3
##### 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.

##### Share on other sites

• 3 months later...

another useful thing:

```rotate_prim_intrinsic(integer link,vector axis, float angle, vector offset)
{
vector local_pos = llList2Vector(l,0);
rotation local_rot = llList2Rot(l,1);
rotation rot = llAxisAngle2Rot(axis,angle);

vector offset2 = offset*(rot);
[   PRIM_POS_LOCAL,local_pos+(offset2-offset)*local_rot,
PRIM_ROT_LOCAL,rot*local_rot
]);
}```

if you have a prim, and you want to rotate it about its own local axes around a point not its center, can use the above function to do just that.

For example, to rotate a flat cube (e.g. a door) with dimensions  x:0.5,y:0.01,z:(any) 30 degrees about its -x side:

`rotate_prim_intrinsic(link_number,<0,0,1>,PI_BY_TWO/3,<-0.25,0,0>);`
• 2
##### Share on other sites

• 7 months later...

It turns out converting rotations into Euler-vectors in strange orders is non-trivial.

I hope nobody ever has to actually use these for anything serious. . .

```vector uRot2XYZ(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);
rot /= llEuler2Rot(<0.0, 0.0, euler.z>);

return euler;
}
vector uRot2XZY(rotation rot)
{
vector euler;
vector temp;

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.x = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.x, 0.0, 0.0>);

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.y = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.y>);

// y-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.z = llAtan2(temp.x, temp.z);
rot /= llEuler2Rot(<0.0, euler.z, 0.0>);

return euler;

}
vector uRot2YXZ(rotation rot)
{
vector euler;
vector temp;

// y-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.x = llAtan2(temp.x, temp.z);
rot /= llEuler2Rot(<0.0, euler.x, 0.0>);

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.y = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.y, 0.0, 0.0>);

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.z>);

return euler;

}
vector uRot2YZX(rotation rot)
{
vector euler;
vector temp;

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.x = llAtan2(temp.x, temp.z)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.x, 0.0>);

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.y = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.y>);

// x-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.z = llAtan2(temp.z, temp.y) - PI_BY_TWO;
rot /= llEuler2Rot(<euler.z, 0.0, 0.0>);

return euler;

}
vector uRot2ZXY(rotation rot)
{
vector euler;
vector temp;

// z-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.x = llAtan2(temp.y, temp.x)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, 0.0, euler.x>);

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.y = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.y, 0.0, 0.0>);

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.x, temp.z)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.z, 0.0>);

return euler;

}
vector uRot2ZYX(rotation rot)
{
vector euler;
vector temp;

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.x = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.x>);

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.y = llAtan2(temp.x, temp.z) - PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.y, 0.0>);

// x-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.z = llAtan2(temp.z, temp.y) - PI_BY_TWO;
rot /= llEuler2Rot(<euler.z, 0.0, 0.0>);

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

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.y = llAtan2(temp.x, temp.z) - PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.y, 0.0>);

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.z = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.z, 0.0, 0.0>);

return euler;
}
vector uRot2XZX(rotation rot)
{
vector euler;
vector temp;

// x-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.x = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.x, 0.0, 0.0>);

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.y = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.y>);

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.z = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.z, 0.0, 0.0>);

return euler;
}
vector uRot2YXY(rotation rot)
{
vector euler;
vector temp;

// y-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.x = llAtan2(temp.x, temp.z);
rot /= llEuler2Rot(<0.0, euler.x, 0.0>);

// x-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.y = llAtan2(temp.z, temp.y);
rot /= llEuler2Rot(<euler.y, 0.0, 0.0>);

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.x, temp.z)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.z, 0.0>);

return euler;
}
vector uRot2YZY(rotation rot)
{
vector euler;
vector temp;

// y-axis
// interestingly, -PI_BY_TWO results in a different valid answer, the check o which is a negative quaternion.
temp = <0.0, 1.0, 0.0> * rot;
euler.x = llAtan2(temp.x, temp.z)+PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.x, 0.0>);

// z-axis
temp = <0.0, 1.0, 0.0> * rot;
euler.y = llAtan2(temp.y, temp.x)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, 0.0, euler.y>);

// y-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.x, temp.z)-PI_BY_TWO;
rot /= llEuler2Rot(<0.0, euler.z, 0.0>);

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

// x-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.y = llAtan2(temp.z, temp.y)-PI_BY_TWO;
rot /= llEuler2Rot(<euler.y, 0.0, 0.0>);

// z-axis
temp = <1.0, 0.0, 0.0> * rot;
euler.z = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.z>);

return euler;
}
vector uRot2ZYZ(rotation rot)
{
vector euler;
vector temp;

// z-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.x = llAtan2(temp.y, temp.x);
rot /= llEuler2Rot(<0.0, 0.0, euler.x>);

// Y-axis
temp = <0.0, 0.0, 1.0> * rot;
euler.y = llAtan2(temp.x, temp.z);
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);
rot /= llEuler2Rot(<0.0, 0.0, euler.z>);

return euler;
}
default
{
state_entry()
{
rotation r = <0.5,0.4,0.3,0.7071>;
rotation check;

vector XYZ = uRot2XYZ(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,0,1>,XYZ.z)*
llAxisAngle2Rot(<0,1,0>,XYZ.y)*
llAxisAngle2Rot(<1,0,0>,XYZ.x);

vector XZY = uRot2XZY(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,1,0>,XZY.z)*
llAxisAngle2Rot(<0,0,1>,XZY.y)*
llAxisAngle2Rot(<1,0,0>,XZY.x);

vector YXZ = uRot2YXZ(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,0,1>,YXZ.z)*
llAxisAngle2Rot(<1,0,0>,YXZ.y)*
llAxisAngle2Rot(<0,1,0>,YXZ.x);

vector YZX = uRot2YZX(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<1,0,0>,YZX.z)*
llAxisAngle2Rot(<0,0,1>,YZX.y)*
llAxisAngle2Rot(<0,1,0>,YZX.x);

vector ZXY = uRot2ZXY(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,1,0>,ZXY.z)*
llAxisAngle2Rot(<1,0,0>,ZXY.y)*
llAxisAngle2Rot(<0,0,1>,ZXY.x);

vector ZYX = uRot2ZYX(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<1,0,0>,ZYX.z)*
llAxisAngle2Rot(<0,1,0>,ZYX.y)*
llAxisAngle2Rot(<0,0,1>,ZYX.x);

vector XYX = uRot2XYX(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<1,0,0>,XYX.z)*
llAxisAngle2Rot(<0,1,0>,XYX.y)*
llAxisAngle2Rot(<1,0,0>,XYX.x);

vector XZX = uRot2XZX(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<1,0,0>,XZX.z)*
llAxisAngle2Rot(<0,0,1>,XZX.y)*
llAxisAngle2Rot(<1,0,0>,XZX.x);

vector YXY = uRot2YXY(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,1,0>,YXY.z)*
llAxisAngle2Rot(<1,0,0>,YXY.y)*
llAxisAngle2Rot(<0,1,0>,YXY.x);

vector YZY = uRot2YZY(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,1,0>,YZY.z)*
llAxisAngle2Rot(<0,0,1>,YZY.y)*
llAxisAngle2Rot(<0,1,0>,YZY.x);

vector ZXZ = uRot2ZXZ(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,0,1>,ZXZ.z)*
llAxisAngle2Rot(<1,0,0>,ZXZ.y)*
llAxisAngle2Rot(<0,0,1>,ZXZ.x);

vector ZYZ = uRot2ZYZ(r);
check = // linden rots are reverse from math rots.
llAxisAngle2Rot(<0,0,1>,ZYZ.z)*
llAxisAngle2Rot(<0,1,0>,ZYZ.y)*
llAxisAngle2Rot(<0,0,1>,ZYZ.x);
}
}```
• 2
##### Share on other sites

• 2 months later...
On 5/18/2022 at 6:24 PM, Quistess Alpha said:

It turns out converting rotations into Euler-vectors in strange orders is non-trivial.

I hope nobody ever has to actually use these for anything serious. . .

If you have ever had to deal with BVH files, those routines would be VERY useful. BVH files can use arbitrary rotation orders, and these routines are something I had to ask for help with many years back... and that was just for a single variation of rot to a specific order.

Thanks for pointing out the LL reverse order and the check routines that also show a better way to construct a rot from arbitrarily ordered Eulers!

BTW, is it LL is using a reverse order, or is it that evaluation order is right to left?

##### Share on other sites

2 minutes ago, Phate Shepherd said:

BTW, is it LL is using a reverse order, or is it that evaluation order is right to left?

Quaternions are associative. I.E. if you have 3 quaternions p,q,r :

((p*q)*r) == (p*(q*r)); in any valid quaternion implementation. The difference really is that in LSL, multiplication is defined mirror-image with respect to well, literally anything else.

In LSL, the product of 2 quaternions q*r roughly means "Rotate by q, then by r (in region coordinates)", r is a transformation applied to q. In math land, it's flipped around. q*r means "Rotate by r then by q", q is a transformation applied to r, which "makes more sense" if you think of a quaternion/rotation not as a static thing, but as a function, where you usually write the function name on the left (i.e. f(x) ). but that's a lot of semantics.

• 3
##### Share on other sites

• 1 month later...

Another "maybe occasionally useful" thing to put on the pile: getting the bounding box of an arbitrarily oriented cube rectangular cuboid:

Quote
```vector BoxRot2AABox(vector b, rotation r)
{   //b = 0.5*b; // offset from center is more useful than actual dimension.
// it turns out scaling cancels itself out and becomes redundant.
list corners =
[   < b.x, b.y, b.z >*r,
< b.x, b.y,-b.z >*r,
< b.x,-b.y, b.z >*r,
< b.x,-b.y,-b.z >*r
//<-b.x, b.y, b.z >*r, // symmetry.
//<-b.x, b.y,-b.z >*r,
//<-b.x,-b.y, b.z >*r,
//<-b.x,-b.y,-b.z >*r
];
vector max;
integer index = -llGetListLength(corners);
do
{   vector c = llList2Vector(corners,index);
c.x = llFabs(c.x);
c.y = llFabs(c.y);
c.z = llFabs(c.z);
if(c.x>max.x) max.x = c.x;
if(c.y>max.y) max.y = c.y;
if(c.z>max.z) max.z = c.z;
}while(++index);
return max;
}
// https://wiki.secondlife.com/wiki/LlSetKeyframedMotion
rotation NormRot(rotation Q)
{   float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
// MagQ = 1/sqrt... and return <Q.x*MagQ ... would probably be slightly more efficient.
return <Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}
default
{   touch_start(integer total_number)
{   vector box =
<0.1+llFrand(0.9),
0.1+llFrand(0.9),
0.1+llFrand(0.9)>;
// no clue how "even" a distribution this generates.
rotation rot = NormRot(
<1.0-llFrand(2.0),
1.0-llFrand(2.0),
1.0-llFrand(2.0),
1.0-llFrand(2.0)>);
[   PRIM_SIZE, box,
PRIM_ROT_LOCAL, rot,
PRIM_SIZE, BoxRot2AABox(box,rot)
]);
}
}```

The general technique could easily be extended to any specific convex hull specified in terms of its scale.

Edited by Quistess Alpha
##### Share on other sites

• 4 months later...

I wasn't sure whether to make a new topic for this, but it's basically just applied rotation stuff so I'll dump it here.

sun_position_user returns azimuth and elevation values to plug into the environment editor (you'll have to do the math to convert a real day of the year into my fantasy 360 day calendar )

sun_position_environment returns a rotation you can plug into llSetAgentEnvironment

```float gFrequency = 2.0; // 2.0 seems about the fastest it will allow
float gLatitude = 50.0; // degrees from equator.
integer gTime=-1;
integer gDay;
vector sun_position_user(float latitude, float day, float time)
{   // day ranges from [0-360); day 0 is september equinox, 90 winter solstice, 270 summer solstice
// latitude in degrees from equator
// time in hours; exactly 24 hours per day. (sun rises at 6.0 on the equinox, sets at 18.0)
// return is <azimuth, elevation, 0>. (in degrees)
vector axis = <0, llCos(latitude), llSin(latitude)>;
//vector sun = <0, -axis.z, axis.y> + axis*(llSin(day*DEG_TO_RAD)*0.3987);
vector sun = <0, axis.z, -axis.y> *
llAxisAngle2Rot(axis,TWO_PI*-time/24.0);
// .4101524 is the tilt of the earth's axis in radians.
// sun now represents the position of the sun which we want to convert to az, elevation.
if(ret.x<0) ret.x+=360.0;
return ret;
}
rotation sun_position_environment(float latitude, float day, float time)
vector axis = <0, llCos(latitude), llSin(latitude)>;
//llOwnerSay("Axis:"+(string)axis);
vector sun = <0, axis.z, -axis.y> *
llAxisAngle2Rot(axis,TWO_PI*-time/24.0);

//llOwnerSay("Sun:"+(string)sun+":"+(string)time);
return uVectorRoll2Rot(sun,0.0);
}
rotation uVectorRoll2Rot(vector v,float r)
{   return YPR2Rot(<llAtan2(v.y,v.x),-llAtan2(v.z,llVecMag(<v.x,v.y,0>)),r>);
}
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)> ;
}

default
{
state_entry()
{   llSetTimerEvent(gFrequency);
llOwnerSay("Script start.");
}
timer()
{   ++gTime;
if(gTime==24)
{   gTime=0;
++gDay;
}
list agents = llGetAgentList(AGENT_LIST_PARCEL,[]);
integer i = llGetListLength(agents);
while(~--i)
{   llRequestExperiencePermissions(llList2Key(agents,i),"");
}
}
experience_permissions(key ID)
{   llSetAgentEnvironment(ID,gFrequency*0.95,[SKY_SUN,sun_position_environment(gLatitude,gDay,gTime),1.0,<0.1,0.1,0.1>]);
// Update does not happen if the receiving viewer is in the middle of an environment change, so env. change speed must be faster than update frequency.
//llOwnerSay((string)sun_position_user(gLatitude,gDay,gTime));
}
experience_permissions_denied(key ID, integer reason)
{   llOwnerSay(llGetExperienceErrorMessage(reason));
}
}```

• 1
##### Share on other sites

From the rotationally challenged among us: thank you!

• 1