1
\$\begingroup\$

I read that I "should really be storing the component vectors (rotation, translation, scale) in addition to the quaternion and matrix forms." The reason for this is that, over time, compound numerical numerical errors can build up over time from floating point limits.

This seems a bit excessive to me. I was planning on creating a Transform class that just manipulated a glm::mat4 model matrix. But now I'm concerned that this might not be the best way, however I don't really understand why. Could someone explain why and now numerical errors can build over time using quaternions?

And what should I really be doing instead? Store component vectors, but convert euler rotations to quaternion using glm::angleAxis while setting rotations, and then return a glm::mat4 model matrix that combines these things upon request? That's a whole lot of matrix math flying around every frame. I guess I could check to see the glm::mat4 has changed before assembling a new one but maybe there's a better way.

One advantage I'd have by storing the component vectors is trying to return what the current component vectors actually are. Pulling that from a glm::mat4 seems tedious. I know I can decompose a matrix, but I don't want all the decomposed components when I'm just trying to return, for example, a rotation. But... as I understand it, it needs to be decomposed fully in order to get any of the component vectors. In other words, it's all or nothing.

To restate my questions:

  1. What sort of use case makes matrices and quaternions lose their accuracy over time and why?
  2. How can I avoid this problem (or what should I be doing)? Hoping for something fast AND accurate.
\$\endgroup\$
3
\$\begingroup\$

Short answer: To store position, use a single vec3. To store rotation, use a quaternion and normalize it after every multiplication or after every n (1-1000) multiplications.

You shall only use mat4s when it comes to drawing or transforming lots of vertices: Convert vec3+quaternion pair to mat4 and pass it to your shader or use it to transform vertices directly.


Detailed explanation:

  • Floating-point math:

Floating-point math is not so accurate. If you compute a long mathematical equation using float's, result can be inaccurate. 5.0 can become 5.00000003 or 4.99999998 and so on.

See Drop's answer for more detailed and precise explanation.

  • Quaternions:

A quaternion stores rotation as 4D direction. So, [1,2,3,4] is same rotation as [2,4,6,8]. Only proportions matter here. Quaternions always rotate models correctly. Except one case: when a quaternion suddenly becomes [0,0,0,0] because of these inaccurate calculations. In this case you lose your rotation. To prevent this, you must normalize your quaternions after n multiplications. n can me 1 for simplicity. Or something like 1000.

  • Matrixes:

Matrixes do lose their precision after big amount of calculations. To be valid translate+rotate matrix, a matrix must have it's 4th row equal to [0,0,0,1]. Also, 3 rotation vectors (each one is composed from first 3 numbers in corresponing column (1st, 2nd or 3rd)) must be orthogonal and must have lenght equal to 1.0. If any of these conditions is not met, matrix starts to behave strangely. It may change scale or proportions of your models or disort them. If you want to restore your matrix to the valid state, you must orthogonalize it and fix the 4th row . Orthogonalization requires some tricky and relatively expensive calculations.

  • Quaternions vs Matrixes:

Quaternions are a lot faster to multiply than martixes. If you normalize a quaternion too seldom, it will only lose precision. Your models will be still rotated correctly. Everything will break only if a quaternion will become [0,0,0,0]. If you orthogonalize a matrix too seldom, it will slowly start to disort your models more and more. Normalization is a lot faster than orthogonalization.

\$\endgroup\$
0
\$\begingroup\$

Could someone explain why and now numerical errors can build over time using quaternions?

"float is not real"

Floating point types are an approximation of real numbers implementable on binary machines. For example: there is a real number 0.1, however, there is no IEEE-754 floating point number that is exactly 0.1, as this value cannot be represented exactly in this system. The closest representable value is 0.0999755859375. If you are performing operations on floats they slowly accumulate some minor errors.

See canonical: "What Every Computer Scientist Should Know About Floating-Point Arithmetic".

See also: Wiki - IEEE-754 floating point

Should really be storing the component vectors (rotation, translation, scale) in addition to the quaternion and matrix forms

I don't see how storing original vectors can help to overcome floating point types limitations. Vectors will acummulate errors the same way quaternions or matrices do. They will probably accumulate even more errors, because you will need to convert them to quaternions/matrices and back, additionally to quaternions/matrices operations, this increasing number of operations performed (supposing you want to perform your transforms with quaternions/matrices). It will also introduce performance and memory overhead.

Hoping for something fast AND accurate

No free cookies. Precision is often an antonym of performance. You will need to find a right balance between them.

And what should I really be doing instead?

There is no such thing exist. Neither "best ways", nor "should". Think critical, try, measure and find a right performance/precision/convenience balance in context of your application.

Few illustrative examples: You are doing gamedev: floats will do the trick, and most of the time you will never notice any errors. Scientific calculations: double or long double is probably your way. You are building a Mars rover, aircraft flight controls or a national banking system: well, here you probably shouldn't use floats at all.

We are on GameDev site, so just remember that "float is not real"... but it is very close.

\$\endgroup\$
0
\$\begingroup\$

Right, if you keep rotating a matrix, say, by 1 degree around some axis, after 360 successive steps, it will be almost back where you started, but not exactly. You'll see some 0.999xxx and some 1.00xxx's and the like.

Same with quaternions.

But it takes a while before these are significant.

One approach that I like is occasional renormalization.

Just once every few thousand frames seems to be enough (in my engine).

For a quaternion, just divide it by its absolute value. See http://www.mathworks.com/help/aeroblks/quaternionnormalize.html.

For a matrix, for unscaled rotation or rotation & translation, you can normalize the first two columns of the mat3 top left corner (make the absolute value equal 1.0), and let the third column of the mat3 be the dot product of the first two.

(Depends on handedness and row-column-ness, but that's the gist.)

Cumulative translation can also become "off" from the mathematical ideal, but it doesn't cause distortions in the same way that a renormalized rotation can.

\$\endgroup\$

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.