3
\$\begingroup\$

I’m programming a C raytracing project.

I’ve been wanting to implement object rotations (square, cylinder) for several days but I can’t do it.

For each object I have its position (x, y, z) in the world space and its 3d normalized orientation vector.

For the moment I do nothing more than applying once of the three following functions on the 3d normalized orientation vector. I don’t know if I’m doing it right. Rotation doesn't work well.

For example I want to rotate my object left with the j key and right with the l key (x_rotation), up with the i key and down with the k key (y_rotation). I'm not sure how to use the z axis.

I'm starting to despair, can you please help me ?

I will be very grateful for your help :)

t_vec x_rotation(t_vec vec, float alpha)
{
    t_vec   res;

    res.x = vec.x;
    res.y = vec.y * cos(alpha) - vec.z * sin(alpha);
    res.z = vec.y * sin(alpha) + vec.z * cos(alpha);
    return (res);
}

t_vec y_rotation(t_vec vec, float alpha)
{
    t_vec   res;

    res.x = vec.z * sin(alpha) + vec.x * cos(alpha);
    res.y = vec.y;
    res.z = vec.z * cos(alpha) - vec.x * sin(alpha);
    return (res);
}

t_vec z_rotation(t_vec vec, float alpha)
{
    t_vec   res;

    res.x = vec.x * cos(alpha) - vec.y * sin(alpha);
    res.y = vec.x * sin(alpha) + vec.y * cos(alpha);
    res.z = vec.z;
    return (res);
}
\$\endgroup\$
6
  • \$\begingroup\$ How do you define an orientation vector? Judging from your code, it looks like it might represent a facing direction? This isn't enough to describe an orientation in 3 dimensions, since we can still spin the object around this vector to get additional orientations that all have the same facing direction. \$\endgroup\$
    – DMGregory
    Jul 29, 2020 at 21:25
  • \$\begingroup\$ For example for the square, this is the normal at the center point. I know that with a single 3D normalized orientation vector we can have several possible squares. But I define one by default. \$\endgroup\$
    – zoom
    Jul 29, 2020 at 21:40
  • \$\begingroup\$ Then you do not have full coverage over the set of possible orientations, and the result of some rotation operations may not be as expected, because they rotate the object into an orientation that is not representable in your chosen scheme. I highly recommend using either a rotation matrix, quaternion, or two basis vectors as your internal representation, so that you have full coverage over all possible orientations. \$\endgroup\$
    – DMGregory
    Jul 29, 2020 at 21:43
  • \$\begingroup\$ These functions are equivalent to applying either the x, y or z rotation matrix. \$\endgroup\$
    – zoom
    Jul 29, 2020 at 22:03
  • \$\begingroup\$ Equivalent to applying the rotation to a single vector, yes. This is not equivalent to applying the rotation to the whole 3D object. That is borne out by the fact that your z rotation is not working as desired with this code. If your initial facing direction is along the z axis, then z rotations do not change the vector at all - even though they should change the orientation of your object. So rotating a single vector is insufficient to update the orientation of the whole object. \$\endgroup\$
    – DMGregory
    Jul 29, 2020 at 22:10

1 Answer 1

2
\$\begingroup\$

As mentioned in the comments, rotation in 3 dimensions has 3 degrees of freedom (you can think of them like yaw, pitch, and roll), but a unit vector in 3 dimensions has only 2 degrees of freedom (latitude and longitude). So we need to store more than just a single unit vector to describe our orientation.

The smallest change from your current code would be to store two perpendicular unit vectors: one representing your forward/facing direction, and one representing your "up" direction. That way the second vector can track any "twist" around the first vector's axis.

When you rotate your forward vector using one of the three methods you've shown, also rotate your up vector by the same method.

To keep rounding errors from building up and making your vectors no longer perpendicular, you'll want to periodically orthonormalize this two-vector basis, something like this:

forward = normalize(forward);
up = normalize(up - dot(up, forward) * forward);

You can expand this to a full rotation matrix just by computing your third basis vector as the cross product of the other two:

// In a left-handed coordinate system. Flip the order for a right-handed system.
right = cross(up, forward);

Then [right | up | forward] is an orthonormal rotation matrix ready to use in transforming your points.


A more conventional solution to this problem is to store your object's orientation as a quaternion. That's four floats instead of six for two basis vectors, and quaternion rotations compose quite nicely:

Quaternion Compose(Quaternion after, Quaternion before) {
    Quaternion q;

    q.x = after.w * before.x + after.x * before.w + after.y * before.z - after.z * before.y;
    q.y = after.w * before.y - after.x * before.z + after.y * before.w + after.z * before.x;
    q.z = after.w * before.z + after.x * before.y - after.y * before.x + after.z * before.w;
    q.w = after.w * before.w - after.x * before.x - after.y * before.y - after.z * before.z;

    return q;
}

You can construct an axis-aligned rotation like so:

Quaternion ZRotation(float angle) {
    Quaternion q;

    q.x = 0;
    q.y = 0;
    q.z = sin(angle/2f);
    q.w = cos(angle/2f);

    return q;
}

Or one around an arbitrary unit vector like so:

Quaternion AngleAxis(float angle, t_vec unitAxis) {
    Quaternion q;

    float s = sin(angle/2f);
    q.x = unitAxis.x * s;
    q.y = unitAxis.y * s;
    q.z = unitAxis.z * s;
    q.w = cos(angle/2f);

    return q;
}

Then you can compose your rotations to get your new orientation:

orientation = Compose(rotationChange, orientation);

You can transform a vector by the quaternion like so:

t_vec Rotate(Quaternion q, t_vec v) {
    float x = q.x * 2f;
    float y = q.y * 2f;
    float z = q.z * 2f;
    float xx = q.x * x;
    float yy = q.y * y;
    float zz = q.z * z;
    float xy = q.x * y;
    float xz = q.x * z;
    float yz = q.y * z;
    float wx = q.w * x;
    float wy = q.w * y;
    float wz = q.w * z;

    t_vec rotated;
    rotated.x = (1f - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z;
    rotated.y = (xy + wz) * v.x + (1f - (xx + zz)) * v.y + (yz - wx) * v.z;
    rotated.z = (xz - wy) * v.x + (yz + wx) * v.y + (1f - (xx + yy)) * v.z;
    return rotated;
}

Though you can see here, most of these intermediate calculations don't depend on the vector being rotated. So if you're going to rotate a whole bunch of vectors, it can be more efficient to turn the quaternion into a transformation matrix first, then use the matrix to rotate all the vectors.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .