Main content
Computer programming - JavaScript and the web
Rotating 3D shapes
Rotating things in three dimensions sounds complicated and it can be, but there are some simple rotations. For example, if we imagine rotating our cube around the z-axis (which points out of the screen), we are actually just rotating a square in two dimensions:
A reason to learn trigonometry
We can simplify things further, by just looking at a single node at position (x, 0). Using simple trigonometry we can find that the position of the point after rotating it by θ around the origin is:
If you don't understand where these equations came from, this video might help.
Rotating a point about the origin
If we rotate by to point (x', y'), then:
Using some trigonometric identities, we get:
Substituting in the values for x and y above, we get an equation for the new coordinates as a function of the old coordinates and the angle of rotation:
Writing a rotate function
Now we know the mathematics, we can write a function to rotate a node, or even better, our array of nodes, around the z-axis. This function will loop through all the nodes in the node array, find its current x and y coordinates and then update them. We store
sin(theta)
and cos(theta)
outside the loop so we only need to calculate them once:var rotateZ3D = function(theta) {
var sinTheta = sin(theta);
var cosTheta = cos(theta);
for (var n = 0; n < nodes.length; n++) {
var node = nodes[n];
var x = node[0];
var y = node[1];
node[0] = x * cosTheta - y * sinTheta;
node[1] = y * cosTheta + x * sinTheta;
}
};
To rotate the cube by 30 degrees, we'll call the function like this:
rotateZ3D(60);
You can see the rotated cube below - it's slightly more interesting than before, but not by much:
Rotating in three dimensions
We can now rotate our cube in two dimensions, but it still looks like a square. What if we want to rotate our cube around the x-axis (horizontal axis)?
We can take our trigonometry and function from before, and just re-label the axis so that the z-axis becomes the x-axis. In this case, the x-coordinates of the node do not change, only the y and the z:
var rotateX3D = function(theta) {
var sinTheta = sin(theta);
var cosTheta = cos(theta);
for (var n = 0; n < nodes.length; n++) {
var node = nodes[n];
var y = node[1];
var z = node[2];
node[1] = y * cosTheta - z * sinTheta;
node[2] = z * cosTheta + y * sinTheta;
}
};
And we can use the same argument to create a function that rotates our cube around the y-axis:
var rotateY3D = function(theta) {
var sinTheta = sin(theta);
var cosTheta = cos(theta);
for (var n = 0; n < nodes.length; n++) {
var node = nodes[n];
var x = node[0];
var z = node[2];
node[0] = x * cosTheta + z * sinTheta;
node[2] = z * cosTheta - x * sinTheta;
}
};
Now that we have those functions defined, we can rotate 60 degrees by the two other axis:
rotateX3D(60);
rotateY3D(60);
You can see the complete code below. Try using the number scrubber to change the values in the function calls.
Rotation direction
When we rotate an object, it can either rotate in a clockwise or counterclockwise direction. Can you tell what direction our cube is turning? If you're not sure, scroll to the very top and study the cube rotating around the z-axis, then come back down here.
If you've studied 3D rotations in math class, you might be surprised. Positive rotations are typically counterclockwise, as shown in the diagram below:
That diagram shows what's known as a right-handed coordinate system, where curling your right hand around an axis will show you the direction of rotation.
However, in our ProcessingJS environment, the y-axis points downwards instead of upwards.
This is a left-handed coordinate system, where curling your left hand around each axis shows the direction of rotation. In this system, positive rotations are clockwise.
Many computer graphics systems use left-handed coordinate systems, since it makes sense for the point to be the upper left corner of the screen.
User interaction
We can rotate the cube by adding function calls, but it's a lot more useful (and satisfying) if we can enable the viewer to rotate the cube using their mouse. For this we need to create a
mouseDragged()
function. This function is automatically called whenever the mouse is dragged.mouseDragged = function() {
rotateY3D(mouseX - pmouseX);
rotateX3D(mouseY - pmouseY);
};
mouseX
and mouseY
are built-in variables that contain the current position of the mouse. pmouseX
and pmouseY
are built-in variables that contain the position of the mouse in the previous frame. So if the x-coordinate has increased (we move the mouse right), we send a positive value to rotateY3D()
and rotate the cube counter-clockwise around the y-axis.You can see for yourself below.
Want to join the conversation?
- I don't understand why you are passing (mouseX - pmouseX) to the rotate function. The function requires an angle (in radians) while this is a mere difference in coordinates. It works, but I don't get why.(33 votes)
- From the author:The idea is find a way to map the movement of the mouse into the rotation of the object. Simply converting the movement of the mouse to the degrees (not radians) of rotation, works well enough. If you look closely, it's not "accurate" the sense that if you click and drag a node, the node will move faster than the mouse. The reason for this is, if you move the mouse, say, two pixels, this will rotate the object by two degrees. Since each node is ~173 units from the centre of rotation, a two degree rotation will move it ~6 pixels (173 * sin(2)).(77 votes)
- In the last program,
var node = nodes[n]
Does this means the changes in content ofnode
will lead to changes in content ofnodes[n]
? Does not a duplicate arraynode
is created independent ofnodes[n]
by above mentioned assignment statement & changes are made to it independently?(17 votes)- If
node[n]
is an object or array, then yes,var node = nodes[n]
means that the changes in content ofnode
will lead to changes in content ofnodes[n]
. That is, objects and arrays are accessed by reference.
Ifnode[n]
is merely a number, then no.(27 votes)
- Why do we need the draw function in the above program? There is nothing within the function that we need to repeat, but still if I delete the function it gives me a black screen.(6 votes)
- The cube is redrawn so that when we drag the mouse, the cube is shown with the new rotation.(30 votes)
- what is the difference of mouseX and pmouseX? same for mouseY and pmouseY(6 votes)
mouseX
is the current position of the mouse,pmouseX
is the previous position of the mouse.
Yes, it's the same formouseY
andpmouseY
.(16 votes)
- In the rotateY3D function, I don't understand why z is treated like y and x like x. To try to visualize this, I put 3 pencils perpendicular to each other, and when I rotated it so that the "y pencil" was pointing at me, the "x pencil" was oriented vertically and the "z pencil" horizontally. Had I not seen the code in this lesson, I would have thought the equations were:
node[0] = z * cosTheta - x * sinTheta;
node[2] = x * cosTheta + z * sinTheta;(6 votes)- 2018-02-04: This answer completely replaces my two year old, incorrect response.
I believe that the implementation ofrotateY3D
incorrectly swappedx
andz
usage. The correct implementation isvar rotateY3D = function(theta) {
var sinTheta = sin(theta);
var cosTheta = cos(theta);
for (var n=0; n<nodes.length; n++) {
var node = nodes[n];
var x = node[2];
var z = node[0];
node[2] = x * cosTheta - z * sinTheta;
node[0] = z * cosTheta + x * sinTheta;
}
};(7 votes)
- In two dimensions, there is one axis that you can rotate an object on which will change the position of each point in both dimensions (x and y). That axis that you rotate it on to get the only two axis of two dimensions to change is the depth axis which is not part of two dimensional space. It is an axis of three dimensional space. So is there an axis that is not in three dimensional space where if you rotate a 3D object in 3D space by that axis, it will change the position of a point in all three 3D axis? If so, would that just be the same as rotating in two or more 3D axis?(5 votes)
- Excellent question.
Peter uses matrix rotations. Each time he rotates in 2 dimensions.
When he rotates in the X and Y dimensions, the rotation goes around the Z axis.
When he rotates in the Y and Z dimensions, the rotation goes around the X axis.
When he rotates in the Z and X dimensions, the rotation goes around the Y axis.
What you proposed, though -rotating around an extra axis-, is also done. This is no longer a matrix rotation, but a quaternion rotation. There you rotate around a 4th dimensional axis.
A quaternion is a matrix that uses complex numbers. That deserves a whole chapters, so I won't go into the details here. But your idea about a dimension outside of 3D space is spot on.(7 votes)
- I noticed that translate() is at the very end of the code. Back when I first learned that, it was placed before the figure was drawn. Why is it different now and how does it even work when it comes after the draw function?(6 votes)
- Well, it's technically left outside the draw loop, so it doesn't matter where it is, it'll always only be called at the beginning of the program. If you place it in the draw loop it has to come after the node and edges code or you'll translate it twice. This is due to it running translate then drawing the nodes and edges then rerendering the frame with another translate. Why doesn't it continue infinitely from there? Because it rerenders the nodes again. If it's done afterwards, it'll render the nodes, then translate it, then rerender, etc.(3 votes)
- I noticed that the back nodes overlap the front edges. Is there a way to fix this?(5 votes)
- Yes, but it requires you to choose the order the nodes and edges are drawn in, based of the coordinates of the nodes.(5 votes)
- In the second and third code window, as I change "theta" values (30) the box pops back to (0,0).
rotateZ3D(30);
rotateY3D(30);
rotateX3D(30);(4 votes) - how to fill a cube? I guess you would just add a fill command somewhere, right?(5 votes)
- Could be, but it begs the question: Does the program draw any fillable shapes, or only lines and "points". (It doesn't.)
So, you can define each face as being a parallelogram easily drawn by passing its four defining nodes' coordinates to thequad
function. Then care must be taken to draw the faces in back-to-front order since the last drawn quadrilateral may obscure previously drawn ones. Even better - at least three faces are always completely obscured, so why draw them at all?
My examples: https://www.khanacademy.org/computer-programming/3d-faces-introduction/4945776207495168
https://www.khanacademy.org/computer-programming/3d-faces/6125609384542208(4 votes)