I'm neither a programmer not a mathematician (although I do have a science background) and I need basic help with 3D geometry. I've read all of the excellent Martin Baker site but still find the maths a bit much.
In my spare time I program in C++ to write simple games for my smartphone. I decided to try and do something with wireframe graphics. I am struggling to control the rotation of objects in the way that I want.
The space I am modelling uses 3D Cartesian coordinates and vectors, with the positive directions being x=right, y=forward, z=up. Each entity in space is a list of triangles, and each triangle has an origin and two vectors (variables named “edge1” and “edge2”) to find the vertices; from there I draw lines between the three vertices. The starting point, the origin, is variable "position" which is a 3D coordinate.
Each entity also has an "angle", made up of x,y and z parts. All the vectors (from origin to vertex) within the entity are rotated according to this angle. I modified some code I found on the web to do this:
//START OF CODE
double sx=sin(angle.x), sy=sin(angle.y), sz=sin(angle.z);
double cx=cos(angle.x), cy=cos(angle.y), cz=cos(angle.z);
//create a 3x3 matrix “mat” with 1’s down the diagonal and the rest 0’s
//note there is a fourth column in the matrix which stores the “origin”
//rotate each triangle using rotate formula matrix (all triangles rotated to same angle)
//to convert the end of each edge into a vertex position
mat=cy*cz+sy*sx*sz; mat=-cy*sz+cz*sy*sx; mat=cx*sy;
mat=cx*sz; mat=cx*cz; mat=-sx;
mat=-cz*sy+cy*sx*sz; mat=sy*sz+cy*cz*sx; mat=cy*cx;
for (int loop=0; loop<num_triangs; loop++)
//enter the triangle position into the matrix
//(for current test-run all triangles have same position)
triang = &triang_list[loop];
//instead of looping through 3 vertices
//just reiterate each corner, starting with the origin
triang->origin.x = mat;//(as if vector is all zeroes)
triang->origin.y = mat;
triang->origin.z = mat;
triang->vertex1.x = triang->edge1.x * mat + triang->edge1.y * mat + triang->edge1.z * mat + mat;
triang->vertex1.y = triang->edge1.x * mat + triang->edge1.y * mat + triang->edge1.z * mat + mat;
triang->vertex1.z = triang->edge1.x * mat + triang->edge1.y * mat + triang->edge1.z * mat + mat;
triang->vertex2.x = triang->edge2.x * mat + triang->edge2.y * mat + triang->edge2.z * mat + mat;
triang->vertex2.y = triang->edge2.x * mat + triang->edge2.y * mat + triang->edge2.z * mat + mat;
triang->vertex2.z = triang->edge2.x * mat + triang->edge2.y * mat + triang->edge2.z * mat + mat;
//and flag the triangle as having been rotated,
triang->rotated = true;
//to allow the main routine to call getX,Y and Z
//END OF CODE
So far, so good? Actually no because the x,y and z angles behave rather strangely. I want to be able to rotate objects using pitch, yaw and roll, and hoped that the x,y, and z would correspond to these, but they only work at the base position of angle(0,0,0) [equivalent to orientation along unit vector (0,1,0)]. They appear to behave as follows:
Angle.z is consistent whatever the heading. It always acts as a yaw, i.e. the orientation swings left and right relative to the present orientation.
Angle.x rotates about the x axis at the start (0,0,0) in a pitching motion. However if I yaw 90 deg to the left or right, angle.x becomes a roll. So it is still rotating around the x axis but I want it to now rotate around the y axis. Even more confusing, if I first pitch down (to the “south pole”) using angle.y and then change angle.x, this x rotation becomes a yaw about the y axis.
Angle.y does something similar to angle.x. It starts as a roll about the y axis. If I dip down to the south pole it becomes a yaw, rotating about the y axis still. If I start from (0,0,0), yaw to the right, then angle.y continues to move around the y axis, now acting as a pitch.
In between the 90 deg directions, various intermediate things occur.
So, can anyone help?? As I said I’ve tried to understand the problem by reading on the web but I am stuck. My thoughts are that maybe I shouldn’t be using this angle system in the first place because it is so dependent on where your object is oriented and how it got there. (Something to do with Euler angles?) But I don’t know what to use instead of the code above to rotate the vectors.
Maybe there is a simple way of converting pitch, yaw and roll controls into the angles that I am using?
Maybe, when obeying key commands, I need to convert the current orientation into a vector, rotate about that vector according to the instruction, then convert back into the angle format? But I don’t know how to do the rotation about the vector /or/ the conversion back to the angle system.
I would be very appreciative of any advice.
| Hi Lewie,
Looks like you are nearly there, I'm not sure exactly what the problem is but a couple of things occurred to me on first reading your message:
> each triangle has an origin and two vectors (variables named “edge1” and “edge2”)
This seems to imply that you are rotating each triangle in its own axis and then adjusting the origin? I guess it could be done that way but, if I have understood correctly, I think you may be making things unnecessarily hard for yourself?
I think what is usually done is: the vertices of each triangle are stored in the coordinate system of the object you are transforming. If we assume you are modelling, say an aircraft, then choose a coordinate system, x along the fuselage, y along the wing or whatever. Then encode all the vertices for that object in that coordinate system directly. Then to rotate that object multiply each vertex(vector) by the rotation matrix to give the transformed vertex(vector). This rotates the whole object around its origin. You can then offset the aircraft to be where you want on the screen by adding a fixed vector to each vertex (or equivalently by using a 4x4 matrix as you describe).
I strongly agree with you that it is best to avoid Euler angles if possible. Sometimes there are angles implied in the situation itself, for example in the case of an aircraft, the control surfaces imply rotation around certain axis. In this case I think you are doing the right thing in converting the angle information to matrix form as soon as possible. I guess the thing to keep in mind is that, when combining rotations, order is important. So if you want yaw then roll then pitch then yaw you will need a different matrix than if you want yaw then pitch then roll, these angles are relative to the aircraft not the ground.
Rather than go on and write a long message about all the things that could be wrong, perhaps I should check with you to see if I am on the right track?
| Thanks Martin,
I am one step ahead and have ditched the Euler angles entirely (you have confirmed that is what I was using and that they were not really up to the job). I've switched over to trying to use matrices only for the rotations and using the functions you provide to do the necessary conversions and calculations. I can rotate the object nicely in front of a fixed camera, and am now doing the code to move the camera independently. So, a lot of progress on that front.
Regarding the encoding of the object, the way I have done it makes it easy for me to "join the dots" once I've found where the points are in space. I will try to simplify/speed up the coding of the objects once I've got the rotation code sorted out.
I had hoped to use Quaternions as they seemed interesting but I couldn't quite understand them, particularly their relationship with axis-angle notation. I have some queries which i hope are straightforward for you to answer, even if the questions are bit long:
My understanding is that axis-angle is encoded as an axis, which is a 3D vector (x,y,z) describing the way that the object is pointed, and an angle, which is a rotation about the axis. If it was an aeroplane, the axis would run along the middle of the fuselage and the plane would be banking according to the angle. Am I right so far?
On the page on converting axis-angle to quaternions, you state that
qx = ax * sin(angle/2)
qy = ay * sin(angle/2)
qz = az * sin(angle/2)
qw = cos(angle/2)
This would mean that if angle=0, all of qx, qy and qz are also zero (because sin(0)=0). This doesn’t make sense to me because it means that if angle=0 (which I take to mean that the aircraft is not banking) then the axis is of no consequence as the resulting quaternion is always the same!
For example, an aircraft flying to the east (positively along the x axis) without banking would have an axis of (1,0,0) and angle 0. The resulting quaternion (qw,qx,qy,qz) would be (1,0,0,0).
Another aircraft orientated at 90 degrees to the first, still flying level but now heading north (along the y axis) would have axis (0,1,0) and angle (0). But the resulting quaternion is exactly the same: (1,0,0,0). So a lot of information has been lost.
Converting the quaternion (1,0,0,0) back to axis-angle gives problems because it is at a singularity, so we have to set the axis to an arbitrary value and thus lose the information about the axis that we put in before the conversion!
What is the solution to this in programming? Do we have to avoid allowing angle to ever reach zero? Or if it does reach zero do we have to store axis information somewhere else in order to put it back later?
On the page about axis-angle notation https://www.euclideanspace.com/maths/geometry/rotations/axisAngle/index.htm
you give examples of possible orientations, with diagrams of an aeroplane and the corresponding axis-angle notation. The first one is a plane travelling to the right, with angle=0 and axis=1,0,0. So far so clear.
The next diagram is a plane travelling either towards or away from the viewer, I’m not sure which. The information given is angle=90, axis=0,1,0. How is this so? I would have thought that the angle was 0, with axis 0,1,0 ?! Ninety degrees to what? If it is 90 degrees to the original position, about the vertical axis, then why has the axis also changed? There is redundant information here, in that a change in orientation in a single dimension has changed 2 of the variables.
Your next diagram is of a plane pointing left, and now the axis has not changed from the last one (still 0,1,0) but the angle is 180. The movement between the 1st and 2nd diagrams is the same as between the 2nd and 3rd diagrams, yet the adjustments to the axis-angle are completely different! In one 90 degree change in heading, the angle changed 90 degrees and the axis changed also. In the second identical change in heading, the angle changed +90 but the axis did not change. Please explain this to me.
Moving to the next row of aeroplane diagrams, it gets even more confusing. The first in the line is a plane pointing straight up with an angle of 90 and an axis of (0,0,1). I understand this one. The second plane in the row has rotated about its long axis by 90 degrees, but has apparently achieved only a 30 degree change in angle and a radical change of axis. The axis is now (sqrt(1/3), sqrt(1/3), sqrt(1/3) – which I thought would mean that it is pointing somewhere off diagonally with respect to all axes?! I thought that this plane's orientation should be angle=180 axis=(0,0,1). Please can you explain where the figures come from?
Thanks for your time and attention,
> My understanding is that axis-angle is encoded as an axis, which is a 3D
> vector (x,y,z) describing the way that the object is pointed, and an angle,
> which is a rotation about the axis. If it was an aeroplane, the axis would
> run along the middle of the fuselage and the plane would be banking
> according to the angle. Am I right so far?
Yes quite right.
> Query 1:
Yes, you are right, I think the thing to keep in mind is that the quaternion defines a 3D rotation only, its not trying to store any other information. This is actually an advantage, a rotation of 0deg around the x-axis represents the same rotation as 0deg around the y-axis, so its good that they are represented by the same quaternion. In the case of axis-angle the axis is redundant when the angle is zero, so we don't have to avoid it, we can just set the axis to an arbitrary value.
If you need to know the direction of the fuselage: you take the vector representing its direction before it has been rotated say,
v = 0,0,1
(this will depend on the coordinate system and the initial orientation of the aircraft)
Its position after the rotation is:
v2 = q*v*q'
> Query (2)
> The information given is angle=90, axis=0,1,0. How is this so?
Again axis-angle is only intended to represent a 3D rotation. So this represents a ninety degree rotation around the y-axis. This rotation only defines the position of the fuselage relative to its position before rotation.
> Your next diagram is of a plane pointing left, and now the axis has not
> changed from the last one (still 0,1,0) but the angle is 180. The
> movement between the 1st and 2nd diagrams is the same as between
> the 2nd and 3rd diagrams,
The diagrams are supposed to represent the rotation relative to the aircrafts initial position (when angle = zero) not relative to the position shown next to it. I should have made that clearer on the page.
> Query (3)
Same again, each picture is relative to its position before it has been rotated.
I would welcome any ideas to improve the web pages clearer, I'll put a better explanation on the diagrams of aeroplane orientation, let me know if there is anything else that I can do?
Also I'm sure lots of people who are new to the subject will have similar questions to these, so I would like to put a link to this thread, if that's all right with you?
| By all means, you can let
anyone else in on this discussion if you wish.
I understand your reply to query(1) - thanks.
I think I get the answer to my other queries... It would seem that the values describing a rotation make more sense if you know where you started from, because the end result of a rotation is always relative to some previous orientation. Any given orientation has an infinite number of possible rotations that led to that state.
Regarding the programming, what I have now is an object in space that I can rotate any way I like using the controls. Unfortunately when I try to tilt the camera is all gets a bit twisted, for reasons I have not yet figured out. But... it's sunday evening, it's time to switch off... I'll get back to this one next week.