godotengine / godot

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

Shrinking VehicleWheel when using physics interpolation #72207

Open GearedForFear opened 1 year ago

GearedForFear commented 1 year ago

Godot version

3.5.1.stable

System information

Windows 10, RTX 3070, driver version 528.24, both GLES2 and 3

Issue description

Physics interpolation causes the child mesh of every wheel to visually shrink. This change is smooth, when the physics FPS is equal to, or a multiple of, the screen's refresh rate. Otherwise, it causes jittering. The shrinking depends on driving speed and physics FPS. Higher speeds generally cause more shrinking. When the vehicle slows down, the wheels return to their normal size. Lower physics FPS also lead to shrinking.

Steps to reproduce

Play the reproduction project for a few seconds. No user input is needed. Results are inconsistent, even with identical settings. By default, the physics FPS is 60. Changes to it, or to the monitor's refresh rate, should lead to different results. The shrinking should only occur while physics interpolation is turned on.

Minimal reproduction project

reproduction.zip

lawnjelly commented 1 year ago

Away from pc at the moment so cant answer fully, but this is due to wheels rotating faster than physics tick rate and phasing. Some options include faster tick rate, turning off interpolation for wheels, or interpolating them manually.

GearedForFear commented 1 year ago

Thanks for the response! I was able to solve the issue in my project by doing the following:

So for me, this issue can be closed. I have submitted an enhancement request for the docs.

Calinou commented 1 year ago

Could this be done automatically in the engine by only interpolating the translation of VehicleWheels, not the rotation?

lawnjelly commented 1 year ago

Back on PC now, just to show I discussed this problem when making the original PRs:

https://github.com/godotengine/godot/pull/52846#issuecomment-979811468 https://github.com/godotengine/godot/pull/52846#issuecomment-980526067

It is analogous to filming a helicopter with e.g. a 50fps camera. If the helicopter blades are rotating at 50 revs per second, they will appear stationary, and at other rates you can get phasing and even the appearance the blades are going backwards.

With interpolation the problem with a wheel is that the basis in the transform only records a rotation between 0 and 360 degrees. When something is rotating 0-180 degrees in a physics tick, this works fine, but the moment something rotates more than 180 degrees, it then becomes difficult to know which direction to interpolate the rotation (backwards or forwards? which is which?).

Usually with wheels it should use slerping, and preserve the shape of the wheel, but in some cases it may revert to basis lerping, which can result in a change of shape you are describing. It is described as "throbbing" in the earlier issues I linked.

This problem is not easily solvable automatically, because the interpolation does not have enough information mathematically to determine how to estimate what should be going on between two physics ticks. Hence the suggestion for either raising the tick rate, slowing the wheels, or using a bespoke interpolation.

I'll have a look at the MRP.

EDIT: Yes this does appear to be the problem. It's kind of interesting that it isn't using the slerping path, I will debug this further if I get the chance (maybe it can't decompose to work out the rotation correctly). I do remember fixing this for some test cases, but maybe it doesn't work in all cases.

We could have a look at making sure this is in the docs as an example case. Wheels are commonly a problem, but anything rotating fast enough is likely to cause problems, especially at low tick rates. It is hard to give an "ideal" solution, because there are trade offs involved.

Your solution isn't bad .. I would generally discourage using get_transform_interpolated() all over the place rather than for e.g. cameras (because it is relatively expensive), but here for a manual solution you would have to record prev and current transforms anyway which the get_transform_interpolated() function does behind the scenes probably more efficiently than writing some gdscript.

The ideal solution for wheels incidentally to get the best result is to have a record of the rotation within the tick of revolution as well as degrees within the current revolution .. i.e. if you know a wheel has rotated 3.6 revs in the physics tick, then if the interpolation fraction is 0.5, then the position should be prev + 1.8. This will give the "perfect" answer, but it requires writing bespoke code, and Godot does not support this out of the box - the physics engine probably just sends whatever the current rotation is to set the global transform of a node on each physics tick.

It might be if we asked a physics contributor, that maybe the physics does contain this angular velocity info and it could be passed / queried for this case of wheel interpolation.

Could this be done automatically in the engine by only interpolating the translation of VehicleWheels, not the rotation?

The engine can't afaik currently detect the case automatically, as it only has a visual rotation modded between 0 - 360 degrees, but not the overall change in rotation since the last tick.

We could offer the option to interpolate only translate (I think we do this in the smoothing addon), but weighed against, there is the risk of confusing users for what is a niche case (which can be got around with some script). Generally for core I was aiming for the least amount of options possible at the time I implemented it.

Calinou commented 4 months ago

For future reference, I can still reproduce this as of 3.6.beta b203829361dc4dc8e31cec7e11a964ea578d7865 and in https://github.com/godotengine/godot/pull/92391.

It was suggested that we could move VehicleWheel interpolation to be done scene-side, so we have full control over it in core and can provide a built-in solution. Ideally, the solution should keep rotation interpolation working when the wheel doesn't spin too fast, rather than disabling it entirely.

Testing projects:

https://github.com/godotengine/godot/assets/180032/a6bb0028-81f7-419c-98e8-abd02c3f7ea2