Search code examples
godotgdscript

Godot how to fix rotation precision errors


I have a skeleton that my program rotates every frame. The rotation angle and axis is calculated using a starting direction vector (the bone position to the start position of its tip) and an ending direction vector (the bone position to the end position of its tip). The bones of the skeleton are rotated completely from the starting vector to the ending vector each frame. For the first 70 frames everything is fine and it rotates correctly. However, after that point, there are visible errors in the rotations. Each frame the error gets bigger and bigger until the rotation is completely wrong. I think this happens because of a precision error that causes this error to accumulate.

In the Godot docs it says:

If a transform is rotated every frame, it will eventually start deforming over time. This is unavoidable.

There are two different ways to handle this. The first is to orthonormalize the transform after some time (maybe once per frame if you modify it every frame):

To fix this issue I tried this:

transform.orthonormalized()

Which didn't seem to change anything, the errors were still there.

I was thinking of using the .look_at() method instead of rotating by an axis and angle, but I'm not sure if the precision errors would be there or not. Also, I'm not sure how to call that method on a bone of a skeleton.

What's the best way to fix this problem?


Solution


  • New Answer

    So you are dealing with physics…

    Let us rebuild the transform instead. We can extract the parts we need from the transform we have:

    var start_location := transform.origin
    var start_rotation := transform.basis.get_rotation_quat()
    var start_scale := transform.basis.get_scale()
    

    And you have a rotation you want to apply, keeping the other parts. So I think that would be:

    var new_location := transform.origin
    var new_rotation := transform.basis.get_rotation_quat() * rotation
    var new_scale := transform.basis.get_scale()
    

    Or you need to put rotation * transform.basis.get_rotation_quat() instead.

    And we build a transform from that, which I believe would be like this:

    var new_transform := Transform(
        Basis(
            transform.basis.get_rotation_quat() * rotation
        ).scaled(transform.basis.get_scale()),
        transform.origin
    )
    

    And, if that does not get rid of the accumulated error, this should:

    var new_transform := Transform(
        Basis(
            (transform.basis.get_rotation_quat() * rotation).normalized()
        ).scaled(transform.basis.get_scale()),
        transform.origin
    )
    

    Old Answer

    This answer assumes you are making programmatic animations.

    Create start and end rotations (either quaternions or axis angle), and each frame create a new transform using an interpolated rotation from the end points. That is, you want the a transform that represents the rotation from the start of the motion, not the rotation from the prior frame.


    For the interpolation, you probably want to use the ratio of elapsed time over total time as weight. And by that I mean that you would divide the time since the animation started over the total time the animation must take.

    So, let us say, the animation started at start_time, and must take total_time to complete. At the instant current_time, you have:

    elapsed_time = current_time - start_time
    

    And the weight would be elapsed_time/total_time, which is:

    (current_time - start_time)/total_time
    

    I don't know how you are handling time. However, be aware that if you are using something like OS.get_ticks_msec() for example, which gives you milliseconds as an integer, then you would have an integer division when you want a float between 0.0 and 1.0 for the interpolation. So you would need to cast to float before dividing.


    Similarly, if you want to apply the rotation to an original transform, do not apply incremental rotations each frame. Instead each frame combine the original transform with the rotation from the start of the motion to the current frame.

    Since the rotations would not be based on the prior frame, the errors would not accumulate.