0
\$\begingroup\$

I'm writing a game engine, and when it comes to the transform part, I just use the representation which unity/ue uses, that is: a vector for the position, a vector for Euler angles, and a vector for scale.

But I found that in most cases, I need to deal with the axis of an object, it's much intuitively to use the three axes.

So in practice, is it better to store Euler angles or vectors for axis?

Let me give a example to explain myself: Here's my transform class:

class Transform {
 public:
    glm::vec3 position_;
    glm::vec3 rotation_;
    glm::vec3 scale_;
}

And each time I want to "move it forward to xxx meters", I need to calculate a front vector, which is a local z axis of the object(I use the term 'local' which comes from blender, the local cooradinate, not sure I make myself clear), what I do is to rotate the (0,0,1) with the Euler angles, to get a vector, which is the "local" z axis of the object.

My question is that is it batter to use the data structure I pasted above, or use there vectors, represent for x,y,z axes for the object?

\$\endgroup\$
  • \$\begingroup\$ Could you give an example of what you mean by "to use the three axes" ? What data would you store? In general the only answer to a question "which is better" is, "whichever works for you". The game dev field is still advancing and new ideas are being tested, so it's normal for devs to try new things and choose what works for them. Also note Unity is not using a vector for Euler angles, it's using Quaternions, but allows developers to change it using Euler angles, for simplicity. \$\endgroup\$ – TomTsagk Jun 9 at 9:13
  • \$\begingroup\$ Note that there's a distinction between "what Unity displays to the editor user", "what Unity stores in the scene file", and "what Unity keeps in memory at runtime". Euler angles are used for the first two, but not the third. Have you considered using quaternions as your internal representation? \$\endgroup\$ – DMGregory Jun 9 at 9:22
  • \$\begingroup\$ @TomTsagk, I add some explaination. Since I'm new to graphics, if you don't understand, can you just illustrate what data structure to use to represent a "transform"? \$\endgroup\$ – ravenisadesk Jun 9 at 9:40
  • \$\begingroup\$ @DMGregory, I pasted my structure, according to you comment, my structure seems wrong. Can you show me some example of data structures on representing a "transform"? \$\endgroup\$ – ravenisadesk Jun 9 at 9:42
  • \$\begingroup\$ Transform from position to position, or scaling object's size is easy, but when it comes to rotation, I'm not quite able to handle it, any tutorials about this? \$\endgroup\$ – ravenisadesk Jun 9 at 9:46
3
\$\begingroup\$

So in practice, is it better to store Euler angles or vectors for axis?

This totally depends on your requirements. In general, object orientations (rotations) can be represented in several ways: A specific sequence of rotations around the coordinate system axis (for example Euler angles), a rotation axis and a corresponding angle, a rotation matrix (which is basically your tree axis approach), or as a quaternion. There might be other representations but the named ones are the ones I know. Each of those representations can be transformed into the others since they describe the same thing. To decide which one is the best for your use-case you have to know about their advantages and disadvantages.

Euler angles are just three angles that represent a specific sequence of rotations around the coordinate system axis. Since the sequence and axis are defined you only need the three rotation angles (related Wikipedia site). While this is a quite memory-efficient representation, it suffers heavily from the gimbal lock problem (see this link) as soon as you start calculating with them. In short: There are certain Euler angle combinations that align 2 axes with each other so that you lose a degree of freedom. If you write a flight simulator or anything that involves a lot of varying orientations Euler angles are probably not what you want to use (fun-fact: Even though they are suboptimal for flight simulators, they are often used in aerospace engineering). Their advantage is, that they are rather easy to understand, and for angles that are multiples of 45°, you can probably use your hand to simulate the rotations.

The rotation matrix representation is basically what you described with your three axes. In case your 3 vectors are normalized and orthogonal, putting them together (in the correct order) gives you a rotation matrix. Their advantage is that they can be used directly in runtime calculations (matrix-vector multiplication) without further transformations. However, they have 3 times higher storage requirements than Euler angles to represent the same thing. This might be an issue if you are on a tight memory budget and have a lot of objects. Additionally, there are some numerical stability problems due to floating-point errors if you have a constantly rotating object and I was told "renormalizing" a rotation matrix is a little bit more complicated. I can't confirm this since I have never done it myself because I use the next possible representation for that: Quaternions.

Well, quaternions are a little bit hard to describe to someone who doesn't already know them, so just search the internet for more specific information (or look into the answer of DMGregory). They have several nice properties:

  • They consist of 4 values, which gives a nice memory footprint and reduces floating-point errors.
  • They can be used directly to apply the rotation to a vector
  • You can renormalize them easily
  • The can be interpolated! - This is a very important property for 3d animations

However, they might be a little bit hard to understand. Also, the quaternion-vector multiplication is a little bit slower than matrix-vector multiplication.

The last representation I mentioned is the axis-angle representation. It just stores an axis in form of a 3d vector and a rotation angle around this axis to describe the orientation of the object. I never used it myself, so read this StackOverflow question for further information about its advantages and disadvantages.

Summary

As I mentioned before, all those representations can be transformed into each other and it is your responsibility to use the right one for the operation you want to perform. In the user interface of an editor, Euler angles might be the best representation since they are the easiest one to understand for a user that is not a graphics engine programmer. However, you might want to transform them into a quaternion or matrix as internal engine representation, since you can use them directly for further calculations. If you store the data to a file, you might want to compress a rotation matrix by turning it into a more memory-efficient representation. In general, there is no best way here, you just have to weight computational effort, memory efficiency, and usage comfort of the different representations against each other for your project.

However, as a personal assessment, I think matrices or quaternions are the way to go as an internal storage format for your engine.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Hi, in my shader all the vertices' final position is calculate by matrices, so can a Quaternion interact with a point directly and get the final position, or I need to change a Quaternion back to a rotation matix? \$\endgroup\$ – ravenisadesk Jun 9 at 11:16
  • 1
    \$\begingroup\$ Well, quaternions can be applied to individual points, but there are several reasons why you might want to convert them to matrices before using them in your shader. For example, matrices can also represent scaling and translations, while quaternions are just rotations. Mixing quaternion and matrix operations might clutter your code. Also, as far as I know, GLSL does not provide quaternions, so you have to code the operations yourself, while matrix operations are supported. Last but not least, matrix-vector multiplication is faster (even though it probably won't make a huge difference) \$\endgroup\$ – wychmaster Jun 9 at 12:56
3
\$\begingroup\$

Most 3D engines do neither of these things.

Euler angle rotations are very messy to try to compose. If you have one object rotated on all three axes, and you want to rotate it about an arbitrary world axis, the trig gets... yikes. And the result has discontinuities (you have to wrap around from 180 to -180 somewhere) which makes comparing or interpolating rotations more difficult.

Basis vectors are much better behaved, but redundant. If you store all three basis vectors, that's a whole rotation matrix, 9 floats to lug around and update anytime something changes, for something that only really has three degrees of freedom. Multiplying two rotations in this form means 27 multiplication and 18 addition operations. Matrices/basis vectors interpolate a bit better than Euler angles, without discontinuities or gimbal artifacts, but need re-orthonormaliztion to keep them from squashing in transit, and they can't interpolate 180 degrees without special handling.

So the usual internal memory representation for a rotation is a quaternion. A quaternion is 4 floats (a tiny bit heavier than Euler angles, but still lighter weight than 2 basis vectors and less than half the size of a matrix), representing a point on a unit hypersphere with three imaginary coordinates.

You can form a quaternion from an angle and a unit vector axis to rotate around like so:

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

Composing two rotation quaternions costs 16 multiplies and 12 adds, substantially cheaper than with matrices/basis vectors (and skipping the trig hell of Euler angles), and they interpolate flawlessly over 180 degrees, much more cheaply than the alternatives.

There are downsides: ransforming a vector by a quaternion is slower than with a matrix/basis vectors. So we'll still convert these to a matrix when we expect to do a lot of that.

A transform will usually keep a cached local-to-world matrix (or its inverse), updated lazily when trying to do any vector operations if the source of truth (translation vector, scale triplet, rotation quaternion, and parent transform) has changed in the meantime. We use this cached matrix to furnish fast vector transformations, or upload to the GPU to transform all the vertices in a mesh. But we don't manipulate it as our way to store the object's rotation, and it can be omitted from serialized data since we can rebuild it from the quaternion & co whenever we need.

Now, quaternions aren't the most intuitive to read and manipulate by hand, so engines typically convert them to Euler angles wherever a rotation is displayed to a human user, like in an inspector panel or text format file, or by exposing Euler angle options in the API. But it's still a quaternion under the hood.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for helping, again. \$\endgroup\$ – ravenisadesk Jun 9 at 11:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.