Clover Mills |
However, I had quite a struggle building this windmill, as I was trying to figure out how rotations worked. So I wrote this tutorial article about how I built this Windmill for :
This article describes how I used rotations when building a windmill in Second Life. It is written as a tutorial for people who are trying to come to grips with rotations, and for those who have avoided rotations because they’ve heard of the complexity.
The lslwiki.net is a great resource, but I found that it was not a good tool for learning. They lacked real examples to get my head around and covered the issues in a piece-meal fashion.
This is going to be mostly a “plain English” article. I expect you to have some basic scripting knowledge, understand linked objects, degrees and radians, and have some idea of the difference between global and local co-ordinates and axis. Please note that the script examples given are extracts from the full script.
Things that will be covered: Functions such as llSetRot, llSetLocalRot, llSetPos, llSetLocalPos, llTargetOmega, and vectors, rotation vectors, quaternions. This article does not cover related rotation functions such as llSetRotAt.
The term ‘rotation’ can be used in a variety of contexts, which makes it difficult to figure out what someone is talking about.
Second Life does not provide a single function to do an orbit type rotation. You have to do this yourself, in two parts by setting a position and setting an orientation.
In these diagrams, imagine that the little yellow block is moved at intervals, to a new position in an orbit around the big yellow block in the centre.
Step 1 Set the new position of the prim using llSetPos (or llSetLocalPos if a child)
Step 2 Set the orientation/rotation of the prim using llSetRot (or llSetLocalRot if a child)
Setting The Position Here, each little yellow box is in a different position around the centre box but its orientation is not affected by its position. |
Setting the Orientation Each little yellow box here has been orientated to point/face towards the centre object. The angle of the orientation depends on the position of the object. |
In Second Life, we have three types of vectors – position, rotation & colour. They all look the same < a , b, c>. I shall try to be clear whether I am referring to an euler rotation vector or a position vector.
In
plain English |
An
euler rotation vector is
a way to describe a rotation. It
is the angle to rotate around x-axis, the angle to rotate around the
y-axis, and the angle to rotate around the z-axis. In Second Life it will look like I do not know why they are called ‘Euler’. |
Expressed
in Second Life |
vector my_rotation_vector; // define my variable my_rotation_vector = < angle_x, angle_y, angle_z>; // Where angle_x etc, are floats, and expressed in radians. |
As the rest of this article will show, it’s not the quaternions themselves that are difficult, it is figuring out what the angles are, what’s local , what’s global.
When setting a rotation (or more specifically, an orientation) on a prim, we use llSetRot() on a object, or llSetLocalRot() on a child.
Problem! |
The llSetRot function wants a quaternion, not an euler vector. |
Answer: |
No
Problem! There’s
an easy way to covert eulers to quaternions, and quaternions
to eulers. The_Rotation_Vector = llRot2Euler(The_Quaternion); The_Quaternion = llEuler2Rot(The_Rotation_Vector); |
Next
Problem! |
What’s the rotation vector that I need to use? What is the quaternion that I need to use? |
Answer: |
That’s what the rest of this article is about. |
Next
Problem! |
How to extract or set the x, y & z components of an euler rotation vector? |
Answer: |
It takes a few lines, but see the section below. |
Next
problem! |
But those components are in radians, not degrees. |
Answer: |
No Problem! Linden Labs have given us some constants to simplify the conversion. Multiply by DEG_TO_RAD or RAD_TO_DEG. |
Next
Problem! |
Keeping track of what type all the variables are. |
Answer: |
That’s your problem. |
When moving an object from B to C at an angle θ around the point A, you could use angles, cosines, sines and pythagorus and calculate the new position of C in terms of x and y.
If you would like to do it with fewer mistakes, and fewer lines, you could try the alternative; ie rotate the vector A_B.
The
Principle |
A position vector that is rotated gives a new position vector. |
In
Plain English |
To move the object at B to position C, take the position vector A_to_B and rotate it by angle θ around point A, to get the position vector A_to_C. This vector identifies point C. The rotation of a vector is done by multiplying it by the quaternion that represents the rotation that is needed. |
Expressed
more as a formula |
Vector_A_to_C = Vector_A_to_B rotated by angle θ Vector_A_to_C = Vector_A_to_B multiplied by a Quaternion which represents the rotation θ. For a simple rotation by angle θ around the z-axis at point A, the quaternion can be found from the euler rotation vector, which is <0.0,0.0, θ> where θ is in radians. If your angle is in degrees, then θ = your_angle * DEG_TO_RAD |
In
LSL |
The_Quaternion = llEuler2Rot(<0.0,0.0, θ>; // This represents a rotation by angle θ around the z-axis Vector_A_to_C = Vector_A_to_B * The_Quaternion; // Because this example is not a child need to add point A and use llSetPos Position_C = Position_A + Vector_A_to_C; // Note Position_C and Position_A are vectors which define the position of point A and point C. llSetPos(Position_C); |
Don’t bother trying to display the values in a quaternion directly– they do not make sense. If you can understand the values in radians, then you could display the rotation vector directly using llSay.
But you will probably prefer to see or set the values in degrees, not radians. I wanted the script to give me a chat message which would tell me the orientation of a prim, (when it was touched), in degrees.
In
plain English |
To find the current orientation of a prim the script asks for llGetRot or llGetLocal Rot. But this gives me the current rotation as a quaternion. So I had to convert the quaternion to an euler rotation vector. But that gives me the values in radians, not degrees. So then I get the x, y & z components of that vector, and convert those three values to degrees. Now I can get the script to say those values, in something that looks like a rotation vector that I can understand, ie something like: “The rotation is <30.00000, 20.0000, 10.00000>” |
In
LSL |
// Define the variables rotation the_quaternion; vector as_a_vector; float component_x; float component_y; float component_z;
//Get the rotation of the child prim llGetLocalRot(the_quaternion);
// Convert it to a vector as_a_vector = llRot2Euler(the_quaternion);
//Get the x, y & z components of that vector and convert to degrees component_x = as_a_vector.x * RAD_TO_DEG; component_y = as_a_vector.y * RAD_TO_DEG; component_z = as_a_vector.z * RAD_TO_DEG; // Say the vector components llOwnerSay(“The
rotation is <” + (string)( component_x) + “,
“ |
There is wind in Second Life. You can see the effect of the wind on flexible prims that have been defined to be affected by the wind (see the features tab on the editing prim menu).
I want a realistic windmill. Since a windmill is a fixed, vertical structure, all I need is a movement around the z-axis for the top section to face into the wind. This is a really simple rotation, and is described by the euler rotation vector <0, 0, wind_angle>.
How to get the wind angle?
In
plain English |
To get the wind angle, I get the wind direction as a vector from llWind(). I’m going to ignore the z (vertical) component of the wind, and just use the x and y components of that wind vector. With those components, I can calculate the angle from geometry. |
In
LSL |
// Define variables float wind_angle; float x; float y; // Get the x and y components of the wind vector x = llWind().x; y = llWind().y; //Get the wind angle from the arc-tangent function. Note that wind_angle is in radians. wind_angle = llAtan2(y,x); |
At the top of the windmill structure are the parts which rotate around a central vertical shaft to face into the wind - the gearbox at the top of the central vertical shaft, the horizontal shaft and the tail. The wheel also spins, and is, for reasons explained later, a separate object.
The structure can be rotated by the owner – to put it into a visually pleasing position on their own land.
If the root prim was one of the parts that turned, then the whole structure would (and did!) turn.
So, the vertical shaft was selected as the root prim, because it was stationary, central and did not have to turn. This central prim has a script with a timer that polls the wind every 15 seconds and passes this information to the scripts in the gearbox, horizontal shaft and tail, using llMessageLinked, and to the script in the wheel using a llListen.
The rotations of the wheel, horizontal shaft and the tail are all very similar problems, but I shall include the approach for each, for the sake of completeness in a tutorial article and because there are differences in what rotation quaternions you use, depending on if the prim is a child or root.
This diagram shows the parts of the windmill with all initial orientations removed. Notice how the tail, the horizontal shaft and the entire wheel object, are all out of alignment. To get them into the correct arrangement, they each had to be given an initial orientation.
Initially, I used llSetRot for the gearbox, horizontal shaft and tail. Everything worked well until I gave the entire structure a new orientation by rotating it. The windmill broke - The gearbox was not the right angle, and the horizontal shaft and the tail decided to do their own thing entirely. This is a bug in Second Life, and there is more information on it at http://forums.secondlife.com/showthread.php?t=94705 if you are interested.
While the root prim is at < 0,0,0> rotation, using llSetRot on the child, with the wind angle, worked. If the root has an orientation, then you must use llSetLocalRot, with the relative angle between the wind angle and the windmill structure.
For the wheel script I used llSetRot with the wind angle because the wheel is a separate object; it has no dependence on the orientation of the windmill structure itself.
A diagram is seldom a wasted effort, and often a huge time-saver.
Consider the following diagram showing the windmill structure, the gearbox at the centre point, and the windmill tail.
So when it comes to setting the rotation of the child, I will be using the relative (local) angle between the child and the parent (purple). The “Required change to Tail” is the Wind Angle minus the Structure Angle.
The gearbox is easy. Although it is a child prim, it does not have to change position, just its orientation (rotation) ie a simple rotation around the z-axis.
When built, this child has an initial rotation (orientation) of <0,0,0> so that wont come into the problem.
With such simple z-axis rotations, I could use vectors, by subtracting the appropriate z-axis component. However, if this were a more complex rotation, then I would have to use quaternions. So might as well use quaternions here. Now subtracting in quaternions is done by dividing, so:
In
Plain English |
The gearbox just has to rotate around its centre to get to the wind angle. But since it is a child, I need to use llSetLocalRot. Therefore I need to use the (local) relative angle between the wind and the structure’s orientation. The local (relative angle) between the gearbox (child) and the root prim is the wind angle minus the Structure_Orientation. From inside a child, you can get the Structure Orientation (as a Quaternion) using llGetRootRotation(); |
Expressed
more as a formula |
The Childs Relative Rotation Required (as a Quaternion ) = Wind Angle (as Quaternion) divided by the Structure Orientation (as Quaternion) |
Expressed
in LSL |
rotation Required_Rotation; //Define variable Required_Rotation = llEuler2Rot( <0.0,0.0, Wind_angle>) / llGetRootRotation llSetLocalRot(Required_Rotation) ; |
I want the wheel to spin smoothly, using llTargetOmega. But the wheel itself consists of multiple prims. The easiest way to co-ordinate all parts of the wheel so that it spins together smoothly, is to have the wheel as its own separate object, with llTargetOmega in the root prim. This is why the structure has to communicate with the wheel using llSay on a channel, and have the wheel listen for messages on that channel. I can't use llMessageLinked because it is not a linked prim.
So the wheel has a script that listens to the windmill structure to get the windspeed, wind direction and the position of the windmill structure. The script then sets the spinning speed using llTargetOmega, and then moves the wheel to the correct position and orientation around the windmill structure.
Now the wheel is not at the centre of the windmill structure – it has an offset, so we are in the orbit situation, so:
1 Set the position
2 Set the orientation
The wheel will be orbiting the around the vertical axis of the centre of the windmill. Its position relative to the root prim is <-0.5, 0, 2.5> (note the z-component here is from the centre of the long vertical shaft).
In
Principle |
A
position vector that is rotated gives us a new position vector. |
In
plain English |
There is an offset vector from the structure to the wheel in its initial position. If we rotate that offset vector around the z-axis by the wind angle, we will have the new position of the wheel Since the wheel is actually an independent object, the wheel’s (global) position is the position of the root of the windmill structure plus its relative position (offset). The wind angle is based on global co-ordinates, and so is the initial offset. The required rotation around the structure is the wind angle. |
Expressed
more a formula |
New_Offset_of_the_wheel (position vector) = the_initial_offset_of_the_wheel (position vector) multiplied by the rotation required (as a quaternion) The (global) position of the wheel = the position of the structure plus the new offset of the wheel from the structure |
Expressed
in Second Life |
Initial_Offset_Vector = <-0.5, 0.0, 2.5>; Wind_Angle_Q = llEuler2Rot (<0.0,0.0,wind_angle>); New_Offset_Vector = Initial_Offset_Vector * Wind_Angle_Q; New_Position_Vector = Structure_Position + New_Offset_Vector; llSetPos (New_Position_Vector); |
The wheel just has to be turned around the z-axis by the wind angle. But we have the complication of the initial 90° orientation on the wheel
In
principle |
An
orientation multiplied by a
rotation to change by, gives you a new orientation. |
In
plain English |
As we have already changed the position of the wheel, now the initial orientation of the wheel has to be rotated around the z-axis by the wind angle too. Because it is a separate object, and not a child, we use llSetRot. |
Expressed
more as a formula |
The
new orientation of the wheel = initial orientation of the wheel
multiplied by the required rotation. Required rotation = wind angle |
In
LSL script |
// Defining the initial orientation of the wheel in the as-built situation Initial_Wheel_Orientation = llEuler2Rot(< 0.0, 90* DEG_TO_RAD, 0.0>); // Defining the wind angle rotation as a quaternion Wind_Angle_Q = llEuler2Rot(<0.0, 0.0, wind_angle>); // Calculating the new wheel orientation. New_Wheel Orientation_Q = Initial_Wheel_Orientation * Wind_Angle_Q; // Set the new orientation of the wheel llSetRot( New_Wheel_Orientaion_Q); |
The scripts in the Horizontal Shaft and the Tail are exactly the same, just the numbers for their individual initial off-set positions, and initial orientations, are different, so I will base this explanation around the tail.
Similar to the wheel, the tail is not at the centre of the windmill structure – it has an offset, so we are in the orbit example, but this time, the prim is a child. So,
1 Set the position of the child
2 Set the orientation of the child
How to get the new position of the Tail?
The Principle |
A vector that is rotated gives us a new vector. |
In
plain English |
The
tail is offset from the root prim, by an initial vector If we rotate that vector around the z-axis by the wind angle, we will have the new position of the tail Because the tail is a child, we will have to use llSetLocalPos.This means that we have to find the relative angle between the root axis, and the tail (child). Looking at the diagram from earlier on, this local angle is the Wind_angle minus the Structure_Angle. Note: This is very similar to the wheel, except here we are using llSetLocalPos and the position vector will be relative to the parent (root). Also, the angle that the offset is rotated by is the relative angle between the structure and the wind, ie the wind_angle minus the structure_angle |
Expressed
more a formula |
New
Position (vector) =the initial position (vector) multiplied by the required rotation
(quaternion) |
Expressed
in Second Life |
// Set and Get the initial values Initial_Offset_Vector = <1.5, 0.0, 2.5>; Wind_Angle_Q = llEuler2Rot (<0.0,0.0,wind_angle>); Structure_Orientation_Q = llGetRootRotation(); // Determine the Relative rotation required, the change Angle_to_Change_By_Q = Wind_Angle_Q / Structure_Orientation_Q; // Calculate the new offset (local) vector New_Offset_Vector = Initial_Offset_Vector * Angle_to_Change_By; // Set the child to the new offset (local) position llSetLocalPos (New_Offset_Vector); |
Since we are dealing with a child, we will be using local rotations, which means use llSetLocalRot. But what is the quaternion to set the tail too?
Eeek! Now I have three Rotations to combine.
Euler vectors can’t be combined successfully in more complex situations. This is definitely a situation for quaternions.
The child has already been moved from initial to new position. At this new position, it needs a new orientation.
What is that new orientation?
In
principle |
A rotation (or orientation) multiplied by a required rotation (change) gives you a new orientation. (same as for the wheel) |
In
plain English |
We should be able to set the orientation of the tail to be the wind angle minus the structure_angle, just like the gearbox. But this time the initial orientation of the child is not <0,0,0>, it is <0, 90°,0>: So
we have to rotate that initial orientation of the tail, around the
z-axis. But that means the angle of rotation that we use has to be relative angle between the child (tail) and the root axis. Looking at the diagram, this is Wind_angle minus the Structure_Angle, (just like the gearbox). |
Expressed
more as a formula |
The new orientation of the child = initial orientation of the child multiplied by the required change rotation. As a child, we need local (relative) angles, so similar to get gearbox, the Change_Required = Wind Angle minus Structure Orientation In quaternions, the Change_Requried_Q = Wind_angle_Q divided by the Structure_Orientation_Q |
In
LSL script |
// Set the value of the tails’ initial orientation based on its as built position Tail_Initial_Orientation = llEuler2Rot( < 0.0, 90*DEG_TO_RAD, 0.0>; // From the child, find the root’s orientation Structure_Orientation_Q = llGetRootRotation(); // Calculate the ‘change’ Change_Required_Q = Wind_Rotation_Q / Structure_Orientation_Q; // Calculate and set the new orientation. New_Tail_Orientation_Q = Tail_Initial_Orientation * Change_Required_Q; llSetLocalRot( New_Tail_Orientation_Q); |
The result? A working windmill. It’s a bit jerky when the 15 second timer event tells all the components to move, but as an exercise in understanding rotations in Second Life, it was worthwhile.
Quaternions are not as scary as they sound.
When dealing with rotations in Second Life, my recommendations are:
Feel free to give some feedback on this article to Clover Mills, in Second Life; corrections and suggestions are appreciated.