Difference between revisions of "Rotation"

From Second Life Wiki
Jump to navigation Jump to search
(→‎Rotation: Added clearer breakdown of a rotation.)
m (Removed link to domain isner.com - page no longer exists.)
Line 258: Line 258:
     }
     }
}// Mephistopheles Thalheimer</source>
}// Mephistopheles Thalheimer</source>
{{CopyAsComment|Useful functions converted to LSL from [http://www.isner.com/tutorials/quatSpells/quaternion_spells_12.htm this page]: {{HiddenButCopy|http://www.isner.com/tutorials/quatSpells/quaternion_spells_12.htm}}}}


{{CopyAsComment|Scale a rotation:}}
{{CopyAsComment|Scale a rotation:}}

Revision as of 16:03, 12 February 2021

Rotation

A rotation is a data type that contains a set of four float vlaues.

Each element can be accessed individually by appending .x, .y, .z, or .s to the variable name.

rotation rot;
float x = rot.x;
float y = rot.y;
float z = rot.z;
float s = rot.s;

The LSL rotation type is one of several ways to represent an orientation in 3D. (Note that we try to write the type name in bold.)

The rotation can be viewed as a discrete twist in three dimensional space, and the orientation of an object is how much it has been twisted around from whichever axes we are using - normally the region's axes.

It is a mathematical object called a quaternion. You can think of a quaternion as four numbers, three of which represent the direction an object is facing and a fourth that represents the object's banking left or right around that direction. The main advantage of using quaternions is that they are not susceptible to gimbal lock. For the complex inner workings of quaternion mathematics, see quaternion. For a list of functions and events related to rotations see LSL Rotation Synopsis. There is also information about causing textures to rotate in textures.

Rotations are often regarded a very confusing subject, where scripters use trial-and-error to get it right. The reasons for this confusion are:

  • Nobody really knows what a quaternion is, or how to think about it (not entirely true, the brain just isn't good at thinking in 4 geometric dimensions).
  • There are actually several different types of vectors ('dir', 'vec' and 'pos') that need to acted upon differently.
  • The order in which translation and rotations need to be applied can vary from case to case.
  • There is confusion about the difference between an applied rotation and the rotation 'offset' between coordinate systems.

To master rotations it is therefore essential to use a good naming system for your variables. Such a system is described in the excellent article About Coordinate Systems and Rotations by Timmy Foxclaw.

Other representations

Euler vector

Another way to represent a 3D angle is using three numbers, <X, Y, Z>, which represent the amount which the object is rotated around each axis. This is used in the Edit window, for example, and is generally easy for people to visualize. It is easy to adjust the Rotation <x, y, z> numbers in the Edit window and see how the object behaves. Note that in the Edit window, the numbers are in degrees, that is, a right angle is 90.

In LSL, these three angles are expressed in radians instead of degrees, that is, a right angle is PI/2. (A radian is sort of a very fat degree.) Note that these three numbers are a vector type and not a rotation type, though it can represent the same information. This is called the Euler representation of a 3D angle. In LSL the rotation around z is done first, then around y, and finally around x.

Axis plus Angle

In this method you define an axis of rotation, like defining the axis about which the earth spins, and use that together with the angle of rotation about the axis, which defines the amount of turn, to give the rotation.

So if you want to define a rotation about an axis at 45 degrees in the x-y plane (North East in region coordinates), you'd need to point the axis with the same amount of x and y, but with no z. The axis could be <1.0, 1.0, 0.0>. The absolute size of the numbers defining the axis don’t matter in this representation; <2.0, 2.0, 0.0> would work just as well. The angle of rotation is a separate number given in radians, eg. PI/3 = 60 degrees. Together they define a global rotation of 60 degrees about the North East axis.

Like a quaternion Axis plus Angle uses four numbers, but it doesn't need to be "normalized".

FWD, LEFT, UP

Another way to represent the same 3D angle is to use three vectors, showing what the front is pointing at (fwd), what the top is pointing at (up), and what the left side is pointing at (left). Actually, only two of the three are needed, because any two determines the third.

For good reasons, such as being able to easily combine rotations, the four number version, the quaternion rotation, is better, though perhaps harder for a beginner to grasp. Fortunately it's very seldom necessary to do anything with the actual internal representation of rotations and there are functions for converting easily back and forth between the three LSL types, and between degrees and radians.

Right hand rule

In LSL all rotations are done according to the right hand rule. With your right hand, extend the first finger in the direction of the positive direction of the x-axis. Extend your second finger at right angles to your first finger, it will point along the positive y-axis, and your thumb, extended at right angles to both will point along the positive z-axis. When you're editing an object, the three colored axis arrows point in the positive direction for each axis (X: red, Y: green, Z: blue).

http://en.wikipedia.org/wiki/Right_hand_rule

Now, don't remove your right hand just yet, there is another use for it, determining the direction of a positive rotation. Make a fist with your right hand, thumb extended and pointing in the positive direction of the axis you are interested in. Your fingers curl around in the direction of positive rotation. Rotations around the X, Y, and Z axis are often referred to as Roll, Pitch, and Yaw, particularly for vehicles.

Roll Pitch Yaw

Combining Rotations

' Suppose you have two rotations. r1 is rotate 90 degrees to the left, and r2 is rotate 30 degrees to the right. (Any rotations will work; these are just an example.) You can combine r1 and r2 to make r3 using the * operator. It doesn't really multiply them, it composes them.

rotation r3 = r1 * r2;

The result in this case is that r3 means rotate 60 degrees to the left.

In other words, to combine rotations, you use the multiply and divide operators. Don't try to use addition or subtraction operators on rotations, as they will not do what you expect. The multiply operation applies the rotation in the positive direction, the divide operation does a negative rotation. You can also negate a rotation directly, just negate the s component, e.g. X.s = -X.s.

Unlike other types such as float, the order in which the operations are done, non-commutative, is important. The reason for this is simple: the order you do rotations in is important in RL. For example, if you had a dart with four feathers, started from rotation <0, 0, 0> with its tail on the origin, it would lie on the X axis with its point aimed in the positive X direction, its feathers along the Z and Y axes, and the axes of the dart and the axes of the world would be aligned. We're going to rotate it 45 degrees around X and 30 degrees around Y, but in different orders.

First, after rotating 45 deg around X the dart would still be on the X axis, unmoved, just turned along its long axis, so the feathers would be at 45 deg to the axes. Then rotating 30 deg around Y would move it in the XZ plane to point down 30 deg from the X axis (remember the right hand rule for rotations means a small positive rotation around Y moves the point down). The dart winds up pointing 30 deg down, in the same vertical plane it started in, but turned around its own long axis so the feathers are no longer up and down.

If you did it the other way, first rotating 30 deg in Y, the dart would rotate down in the XZ plane, but notice that it no longer is on the X axis; its X axis and the world's aren't aligned any more. Now a 45 degree rotation around the X axis would pivot the dart around its tail, the point following a 30 deg cone whose axis is along the positive world X axis, for 45 degrees up and to the right. If you were looking down the X axis, it would pivot from pointing 30 deg below the X axis, up and to the right, out of the XZ plane, to a point below the 1st quadrant in the XY plane, its feathers rotating as it went.

Clearly this is a different result from the first rotation, but the order of rotation is the only thing changed.

To do a constant rotation you need to define a rotation value which can be done by creating a vector with the X, Y, Z angles in radians as components (called an Euler angle), then converting that to a rotation by using the llEuler2Rot function. To go from a rotation to an Euler angle vector use llRot2Euler.

If you want an axial rotation you insert the axis of rotation and the turn angle into the llAxisAngle2Rot function, and this will return the rotation. To go from a rotation back to axis and angle, use llRot2Axis and llRot2Angle respectively.

You can alternately create the native rotation directly: the real part is the cosine of half the angle of rotation, and the vector part is the normalized axis of rotation multiplied by the sine of half the angle of rotation.

NOTE: angles in LSL are in radians, not degrees, but you can easily convert by using the built-in constants RAD_TO_DEG and DEG_TO_RAD. For a 30 degree rotation around the X axis you might use:

rotation rot30X = llEuler2Rot(<30, 0, 0>*DEG_TO_RAD);           // convert the degrees to radians, then convert that vector into a rotation, rot30x
vector   vec30X = llRot2Euler(rot30X);                          // convert the rotation back to a vector (the values will be in radians)
rotation rot30X = llAxisAngle2Rot(<1, 0, 0>, 30.0*DEG_TO_RAD);  // convert the degrees to radians, then convert into a rotation, rot30x

Differences between math's quaternions and LSL rotations

There are a few differences between LSL and maths that have little consequences while scripting, but that might puzzle people with prior mathematical knowledge. So we thought it would be good to list them here:

  • In LSL, all quaternions are normalized (the dot product of R by R is always 1), and therefore represent ways to rotate objects without changing their size. In maths, generic quaternions might be not normalized, and they represent affinities, i.e. a way to rotate and change the size of objects.
  • In LSL, the s term is the fourth member of the rotation: <x, y, z, s>. In maths, the s term, also called "real part", is written as the first coordinate of the quaternion: (s, x, y, z).
  • Multiplication is written in reverse order in LSL and in maths. In LSL, you would write R * S, where in maths you would write S . R.

Order of rotation for Euler Vectors

From the above discussion, it's clear that when dealing with rotations around more than one axis, the order they are done in is critical. In the Euler discussion above this was kind of glossed over a bit, the individual rotations around the three axis define an overall rotation, but that begs the question: What axis order are the rotations done in? The answer is Z, Y, X in global coordinates. If you are trying to rotate an object around more than one axis at a time using the Euler representation, determine the correct Euler vector using the Z, Y, X rotation order, then use the llEuler2Rot function to get the rotation for use in combining rotations or applying the rotation to the object.

Local vs Global (World) rotations

It is important to distinguish between the rotation relative to the world, and the rotation relative to the local object itself. In the editor, you can switch back and forth from one to the other. In a script, you must convert from one to the other to get the desired behavior.

Local rotations are ones done around the axes embedded in the object itself forward/back, left/right, up/down, irrespective of how the object is rotated in the world. Global rotations are ones done around the world axes, North/South, East/West, Higher/Lower. You can see the difference by rotating a prim, then edit it and change the axes settings between local and global, notice how the colored axes arrows change.

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

Another way to think about combining rotations

You may want to think about this local vs global difference by considering that rotations are done in evaluation order, that is left to right except for parenthesized expressions.

In the localRot case, what happened was that starting from <0, 0, 0>, the rot30X was done first, rotating the prim around the world X axis, but since when it's unrotated, the local and global axes are identical it has the effect of doing the rotation around the object's local X axis. Then the second rotation myRot was done which rotated the prim to its original rotation, but now with the additional X axis rotation baked in. What this looks like is that the prim rotated in place, around its own X axis, with the Y and Z rotations unchanged, a local rotation.

In the globalRot case, again starting from <0, 0, 0>, first the object is rotated to its original rotation (myRot), but now the object's axes and the world's axes are no longer aligned! So, the second rotation rot30x does exactly what it did in the local case, rotates the object 30 degrees around the world X axis, but the effect is to rotate the object through a cone around the world X axis since the object's X axis and the world's X axis aren't the same this time. What this looks like is that the prim pivoted 30 degrees around the world X axis, hence a global rotation.

Division of rotations has the effect of doing the rotation in the opposite direction, multiplying by a 330 degree rotation is the same as dividing by a 30 degree rotation.

Using Rotations

You can access the individual components of a rotation R by R.x, R.y, R.z, & R.s (not R.w). The scalar part R.s is the cosine of half the angle of rotation. The vector part (R.x,R.y,R.z) is the product of the normalized axis of rotation and the sine of half the angle of rotation. You can generate an inverse rotation by negating the x,y,z members (or by making the s value negative). As an aside, you can also use a rotation just as a repository of float values, each rotation stores four of them and a list consisting of rotation is more efficient than a list consisting of floats, but there is overhead in unpacking them.

rotation rot30X      = llEuler2Rot(<30, 0, 0> * DEG_TO_RAD );//  Create a rotation constant
rotation rotCopy     = rot30X;                               //  Just copy it into rotCopy, it copies all 4 float components
float    X           = rotCopy.x;                            //  Get out the individual components of the rotation
float    Y           = rotCopy.y;
float    Z           = rotCopy.z;
float    S           = rotCopy.s;
rotation anotherCopy = <X, Y, Z, S>;                         // = <rotCopy.x, rotCopy.y, rotCopy.y, rotCopy.s>


There is a built in constant for a zero rotation ZERO_ROTATION which you can use directly or, if you need to invert a rotation R, divide ZERO_ROTATION by R. As a reminder from above, this works by first rotating to the zero position, then because it is a divide, rotating in the opposite sense to the original rotation, thereby doing the inverse rotation.

rotation rot330X        = <-rot30X.x, -rot30X.y, -rot30X.z, rot30X.s>;//  invert a rotation - NOTE the s component isn't negated
rotation another330X    = ZERO_ROTATION / rot30X;                     //  invert a rotation by division, same result as rot330X
rotation yetanother330X = <rot30X.x, rot30X.y, rot30X.z, -rot30X.s>;  //  not literally the same but works the same.

Single or Root Prims vs Linked Prims vs Attachments

The reason for talking about single or linked prim rotations is that for things like doors on vehicles, the desired motion is to move the door relative to the vehicle, no matter what the rotation of the overall vehicle is. While possible to do this with global rotations, it would quickly grow tedious. There are generally three coordinate systems a prim can be in: all alone, part of a linkset, or part of an attachment. When a prim is alone, i.e., not part of a linkset, it acts like a root prim; when it is part of an attachment, it acts differently and is a bit broken.

Getting and setting rotations of prims
Function Ground (rez'ed) Prims Attached Prims
Root Children Root Children
llGetRot
llGPP:PRIM_ROTATION
llGetObjectDetails
global rotation of prim global rotation of prim global rotation of avatar global rotation of avatar * global rotation of prim (Not Useful)
llGetLocalRot
llGPP:PRIM_ROT_LOCAL
global rotation of prim rotation of prim relative to root prim rotation of attachment relative to attach point rotation of prim relative to root prim
llGetRootRotation global rotation of prim global rotation of root prim global rotation of avatar global rotation of avatar
llSetRot*
llSPP:PRIM_ROTATION*
set global rotation complicated, see llSetRot set rotation relative to attach point set rotation to root attachment rotation * new_rot.
llSetLocalRot*
llSPP:PRIM_ROT_LOCAL*
set global rotation set rotation of prim relative to root prim set rotation relative to attach point set rotation of prim relative to root prim
llTargetOmega
ll[GS]PP:PRIM_OMEGA
spin linkset around prim's location spin prim around its location spin linkset around attach point spin prim around its location
Physical objects which are not children in a linkset will not respond to setting rotations.
†  For non-Physical objects llTargetOmega is executed on the client side, providing a simple low lag method to do smooth continuous rotation.

Rotating Vectors

In LSL, rotating a vector is very useful if you want to move an object in an arc or circle when the center of rotation isn't the center of the object.

This sounds very complex, but there is much less here than meets the eye. Remember from the above discussion of rotating the dart, and replace the physical dart with a vector whose origin is the tail of the dart, and whose components in X, Y, and Z describe the position of the tip of the dart. Rotating the dart around its tail moves the tip of the dart through and arc whose center of rotation is the tail of the dart. In exactly the same way, rotating a vector which represents an offset from the center of a prim rotates the prim through the same arc. What this looks like is the object rotates around a position offset by the vector from the center of the prim.

Position of Object Rotated Around A Relative Point

rotation vRotArc       = llEuler2Rot( <30.0, 0.0, 0.0> * DEG_TO_RAD );
 //-- creates a rotation constant, 30 degrees around the X axis

vector   vPosOffset     = <0.0, 1.0, 0.0>;
 //-- creates an offset one meter in the positive Y direction

vector   vPosRotOffset  = vPosOffset * vRotArc;
 //-- rotates the offset to get the motion caused by the rotation

vector   vPosOffsetDiff = vPosOffset - vPosRotOffset;
 //-- gets the local difference between the current offset and the rotated one

vector   vPosRotDiff    = vPosOffsetDiff * llGetRot();
 //-- rotates the difference in the offsets to be relative to the global rotation.

vector   vPosNew        = llGetPos() + vPosRotDiff;
 //-- finds the prims new position by adding the rotated offset difference

rotation vRotNew        = vRotArc * llGetRot();
 //-- finds rot to continue facing offset point
in application, the same action as:
llSetPrimitiveParams( [PRIM_POSITION, llGetPos() + (vPosOffset - vPosOffset * vRotArc) * llGetRot(),
                       PRIM_ROTATION, vRotArc * llGetRot()] );
  • The above method results in the orbiting object always having the same side facing the center. An alternative that preserves the orbiters rotation is as follows
llSetPrimitiveParams( [PRIM_POSITION, llGetPos() + (vPosOffset - vPosOffset * vRotArc) * llGetRot()];
vPosOffset = vPosOffset * vRotArc;

Nota Bene: Doing this is a move, so don't forget about issues of moving a prim off world, below ground, more than 10 meters etc. Also to get a full orbit, you'll need to repeat the listed steps (in a timer perhaps).

Notice: These apply to objects (or root prims if you prefer), child prims should use PRIM_POS_LOCAL for position, and PRIM_ROT_LOCAL or llGetLocalRot for rotation, and the point being rotated around should be relative to the root.

Position of Relative Point Around Rotated Object

To get a point relative to the objects current facing (such as used in rezzors)

vector   vPosOffset     = <0.0, 0.0, 1.0>;
 //-- creates an offset one meter in the positive Z direction.

vector   vPosRotOffset  = vPosOffset * llGetRot();
 //-- rotate the offset to be relative to objects rotation

vector   vPosOffsetIsAt = llGetPos() + vPosRotOffset;
 //-- get the region position of the rotated offset
in application, the same action as:
llRezAtRoot( "Object", llGetPos() + vPosOffset * llGetRot(), ZERO_VECTOR, ZERO_ROTATION, 0 );

Normalizing a Rotation

When you need precision, it is often important -- even necessary -- to work with normalized rotations, which means scaling each quaternion so that its x, y, and z values are equal to 1. Some operations in LSL will actually generate a run-time error if you do not do this. Looking at it another way, you need to express the rotation in a way that applies an angle of rotation to a vector <1.0,1.0,1.0>. Mathematically, normalizing rotation Q means calculating

Normalized Q = Q / Sqrt( Q.x^2 + Q.y^2 + Q.z^2 + Q.s^2)

Putting that in LSL terms:

rotation NormRot(rotation Q)
{
    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>;
}
Note: The only methods in LSL for obtaining a de-normalized rotations are llAxes2Rot (via inputs which are not mutually orthogonal, or via inputs of different magnitude), or direct manipulation of the rotation's elements. All other ll* functions return normalized rotations. Use of the preceding example may introduce small floating point errors into normalized rotations due to limited precision.

Useful Snippets

integer IsRotation(string s)
{
    list split = llParseString2List(s, [" "], ["<", ">", ","]);
    if(llGetListLength(split) != 9)//we must check the list length, or the next test won't work properly.
        return FALSE;
    return !((string)((rotation)s) == (string)((rotation)((string)llListInsertList(split, ["-"], 7))));
    //it works by trying to flip the sign on the S element of the rotation,
    //if it works or breaks the rotation then the values won't match.
    //if the rotation was already broken then the sign flip will have no affect and the values will match
    //we cast back to string so we can catch negative zero which allows for support of <0,0,0,0>
}//Strife Onizuka


/* Calculate a point at distance d in the direction the avatar id is facing */

vector point_in_front_of( key id, float d )
{
    list pose = llGetObjectDetails( id, [ OBJECT_POS, OBJECT_ROT ] );
    return ( llList2Vector( pose, 0 ) + < d, 0.0, 0.0 > * llList2Rot( pose, 1 ) );
}// Mephistopheles Thalheimer


/* Rez an object o at a distance d from the end of the z axis.
The object is rezzed oriented to the rezzer */

rez_object_at_end( string o, float d )
{
    vector s = llGetScale();
    
    if( llGetInventoryType( o ) == INVENTORY_OBJECT )
    {
        llRezObject( o, llGetPos() + llRot2Up( llGetRot() ) * ( s.z / 2.0 + d ) , ZERO_VECTOR, llGetRot(), 0 );
    }
}// Mephistopheles Thalheimer

/* Scale a rotation: */

rotation ScaleQuat(rotation source, float ratio)
{
	return llAxisAngle2Rot(llRot2Axis(source), ratio * llRot2Angle(source));
}

/* Constrain a rotation to a given plane, defined by its normal, very useful for vehicles that remain horizontal in turns:
Note that there is a flaw somewhere in this function, it gives incorrect results in some circumstances. */

rotation ConstrainQuat2Plane(rotation source, vector normal)
{
	return llAxisAngle2Rot(normal, <source.x, source.y, source.z> * normal * llRot2Angle(source));
} // Jesrad Seraph

/* Slerp (rotation combination) function from Slerp: */

rotation BlendQuats(rotation a, rotation b, float ratio)
{
	return llAxisAngle2Rot(llRot2Axis(b /= a), ratio * llRot2Angle(b)) * a;
}

Constants

ZERO_ROTATION

ZERO_ROTATION = <0.0, 0.0, 0.0, 1.0>;
A rotation constant representing a Euler angle of <0.0, 0.0, 0.0>.

DEG_TO_RAD

DEG_TO_RAD = 0.01745329238f;
A float constant that when multiplied by an angle in degrees gives the angle in radians.

RAD_TO_DEG

RAD_TO_DEG = 57.29578f;
A float constant when multiplied by an angle in radians gives the angle in degrees.