godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91k stars 21.17k forks source link

whole number operations on 2d node rotation cause float errors #83793

Open bulsatar opened 1 year ago

bulsatar commented 1 year ago

Godot version

v4.1.1.stable.mono.official [bd6af8e0e]

System information

Godot v4.1.1.stable.mono - Linux Mint 21.1 (Vera) - Vulkan (Compatibility) - NVIDIA GeForce RTX 3060 Ti (nvidia; 525.125.06) - AMD Ryzen 7 5800X 8-Core Processor (16 Threads)

Issue description

When rotating Node2d and Sprite2d objects with addition or subtraction and whole numbers, floating point errors occasionally occur and accumulate over time. After the floating point errors start to accumulate, explicitly setting the rotation value to a whole number does not eliminate the floating point error. It appears that the decimal value is retained to the new rotational value.

Because the rotational values are off just slightly, positional values of child nodes are also off just slightly. This will cause errors with subsequent calculations, like intersects.

Steps to reproduce

create a main Node2d scene create a new scene Node2d scene and add a couple of child sprites In the main scene, rotate the new Node2d scene by 90 degrees. At the same time counter-rotate the child sprites so they maintain orientation to global

After a couple of rotations, you will start to see accumulated floating point errors. It does not seem to matter if the rotational value is incremented or explicitly set to a whole number as floating point errors creep in either way. In the same way, using any of the math conversion functions on the rotational value doesn't matter. Tried using these when setting rotational values:

Minor Workaround: there is a slight workaround that will work while the accumulated floating point errors stay below the required threshold and that is by using snapped values for calculations, such as intersects. However once the accumulated threshold is too high, that calculation work around will also fail

Minimal reproduction project

link to project: https://github.com/bulsatar/godot_rotation_float_error

Also attached zip with linux and windows exports rotation_error.x86_64.zip rotation_error_win.zip

bulsatar commented 1 year ago

I think I identified where the floating point errors come into play but I have no idea how to fix them. code path: core => math => node_2d.cpp -> set_rotation_degrees (transforms degrees to radians) -> set_rotation -> _update_transform => core => math => transform_2d.h -> set_rotation_scale_and_skew

that last function does matrix transformations on the radians that were converted and looks like applies a standard box transform while adjusting for scale and skew. The cos/sin * scale is probably where the floating point errors are being introduced as there doesn't seem to be a constraint on the resulting floats.

I am not sure if it is possible as matrix transformations are definitely not my wheelhouse, but what would the ramifications be if those calculations were rounded to some nth decimal? Looks like floats are guaranteed to 7 digits and doubles to 15. Maybe that could be the default clamp size? I am just getting into game development so not sure what locking that down would affect.

Would it be worth a setting at the project level for maths accuracy? passing a constraint all the way down that tree would probably introduce too much change at such a core level.

void Transform2D::set_rotation_scale_and_skew(const real_t p_rot, const Size2 &p_scale, const real_t p_skew) {
    columns[0][0] = Math::cos(p_rot) * p_scale.x;
    columns[1][1] = Math::cos(p_rot + p_skew) * p_scale.y;
    columns[1][0] = -Math::sin(p_rot + p_skew) * p_scale.y;
    columns[0][1] = Math::sin(p_rot) * p_scale.x;