I am programming a 3D game in a 2D environment, and I am trying to make a 3D cube. I did that, and I am trying to rotate said cube, so I can see all the sides of it.
I found this lesson on Khan Academy, which showed how to do this. I made all the different corners (I calculated them by getting the center X/Y/Z of the cube, and the radius, and adding or subtracting the radius to the center X/Y/Z based on what corner of the cube I wanted), and I followed the lesson to the best of my ability (it is in JavaScript, while I am using C# /w SFML).
When I tried to rotate my cube, in the exact way that they did it in the last example, it completely failed. The cube was moving physical location every time I tried to rotate it, and it was not retaining any kind of cube-like shape.
Khan Academy's Javascript code:
rotateY3D(mouseX - pmouseX);
rotateX3D(mouseY - pmouseY);
My C# code:
points.p = Vector3.RotateY(theta, points.p);
points.p = Vector3.RotateX(theta, points.p);
C# Translation of rotateX3D:
internal static List<Point> RotateX(float theta, List<Point> points)
{
double sinTheta = Math.Sin(theta);
double cosTheta = Math.Cos(theta);
foreach (Point item in points)
{
float y = item.pointVec.Y;
float z = item.pointVec.Z;
item.pointVec.Y = (float)(y * cosTheta) - (float)(z * sinTheta);
item.pointVec.Z = (float)(z * cosTheta) + (float)(y * sinTheta);
}
return points;
}
C# Translation of rotateY3D:
internal static List<Point> RotateY(float theta, List<Point> points)
{
double sinTheta = Math.Sin(theta);
double cosTheta = Math.Cos(theta);
foreach (Point item in points)
{
float x = item.pointVec.X;
float z = item.pointVec.Z;
item.pointVec.Y = (float)(x * cosTheta) - (float)(z * sinTheta);
item.pointVec.Z = (float)(z * cosTheta) + (float)(x * sinTheta);
}
return points;
}
C# Translation of rotateZ3D:
internal static List<Point> RotateZ(float theta, List<Point> points)
{
double sinTheta = Math.Sin(theta);
double cosTheta = Math.Cos(theta);
foreach (Point item in points)
{
float x = item.pointVec.X;
float y = item.pointVec.Y;
item.pointVec.X = (float)(x * cosTheta) - (float)(y * sinTheta);
item.pointVec.Y = (float)(y * cosTheta) + (float)(x * sinTheta);
}
return points;
}
In the above code, I am looping through all the corners/points, getting the Sin and Cos of the entered theta float, and storing the return in a double. I am then setting the selected point's X and Y in the same way as they do in the Khan Academy examples.
What have I done wrong here?
Aside from typos, your main problem is that you are modifying the coordinates of the object, instead of computing the coordinates for a rotated object. If you modify the coordinates, you apply the transform iteratively, not to the original object; and within a few dozen iterations the limited precision in floating-point numbers start to pile up, and your object loses its shape.
Every time you redraw your scene, you should take the original, unmodified object coordinates, transform (rotate and translate) them, and then draw the resulting coordinates. The transformed coordinates are only used that one time. The next time you redraw your scene (change the Shape), you recalculate the coordinates all over again. This is the way to do it.
Rotation by $\theta$ around the $x$ axis centered at $(x_0, y_0, z_0)$ is $$\left\lbrace ~ \begin{aligned} x_\text{new} &= x_\text{old} \\ y_\text{new} &= y_0 + (y_\text{old} - y_0)\cos(\theta) - (z_\text{old} - z_0)\sin(\theta) \\ z_\text{new} &= z_0 + (y_\text{old} - y_0)\sin(\theta) + (z_\text{old} - z_0)\cos(\theta) \\ \end{aligned} \right.$$
Rotation by $\theta$ around the $y$ axis centered at $(x_0, y_0, z_0)$ is $$\left\lbrace ~ \begin{aligned} x_\text{new} &= x_0 + (x_\text{old} - x_0)\cos(\theta) - (z_\text{old} - z_0)\sin(\theta) \\ y_\text{new} &= y_\text{old} \\ z_\text{new} &= z_0 + (x_\text{old} - x_0)\sin(\theta) + (z_\text{old} - z_0)\cos(\theta) \\ \end{aligned} \right.$$
Rotation by $\theta$ around the $z$ axis centered at $(x_0, y_0, z_0)$ is $$\left\lbrace ~ \begin{aligned} x_\text{new} &= x_0 + (x_\text{old} - x_0)\cos(\theta) - (y_\text{old} - y_0)\sin(\theta) \\ y_\text{new} &= y_0 + (x_\text{old} - x_0)\sin(\theta) + (y_\text{old} - y_0)\cos(\theta) \\ z_\text{new} &= z_\text{old} \\ \end{aligned} \right.$$
If you set $(x_0, y_0, z_0)$, you get the traditional rotations, but for your experiments, it is easier to get it working if you use the above forms (and include the center point around which to rotate as a parameter). Remember that trigonometric functions in both Javascript and C are in radians, not degrees. To convert between radians and degrees, you can use $$\text{radians} = \frac{\pi}{180}\text{degrees} \quad \iff \quad \text{degrees} = \frac{180}{\pi}\text{radians}$$ In other words, $\sin(\pi) = \sin(180°) = 0$.