Search Unity

Resolved Rigidbody MovePosition/Rotation does not work when transform.position is added to

Discussion in 'Scripting' started by JusticeKingCayden, Jul 10, 2023.

  1. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    Here is the script for a planet:
    Code (CSharp):
    1. public class CelestialBody : MonoBehaviour
    2. {
    3.     public float gravityAtSurface = 0;
    4.     public GenerationInfo generationInfo;
    5.     public Rigidbody rb;
    6.     public Vector3 velocity;
    7.  
    8.     public float Mass
    9.     {
    10.         get
    11.         {
    12.             return gravityAtSurface * generationInfo.radius * generationInfo.radius / Universe.G;
    13.         }
    14.     }
    15.     public float getHeightOfSphere(Vector3 pointOnSphere)
    16.     {
    17.         return MeshGenerator.getHeightAtPoint(pointOnSphere, generationInfo);
    18.     }
    19.  
    20.     private void Start()
    21.     {
    22.         rb = GetComponent<Rigidbody>();
    23.         rb.isKinematic = true;
    24.         GenerateMesh(true, true);
    25.         StartCoroutine(updateTick());
    26.     }
    27.  
    28.     //This function stops working in this scenario
    29.     public void FixedUpdate()
    30.     {
    31.         rb.MovePosition(transform.position + velocity * Time.fixedDeltaTime);
    32.         rb.MoveRotation(rb.rotation * Quaternion.Euler(generationInfo.rotationAxis / generationInfo.rotationTime * Time.fixedDeltaTime * 360));
    33.     }
    34. }
    This script does work reliably. However, I have recently added another script which recenters the origin to the player:
    Code (CSharp):
    1. public class Rubberbanding : MonoBehaviour
    2. {
    3.     public GameObject player;
    4.     public float maxDist = 100f;
    5.  
    6.     public CelestialBody[] planets;
    7.  
    8.     public void Start()
    9.     {
    10.         planets = FindObjectsOfType<CelestialBody>();
    11.     }
    12.  
    13.     public void LateUpdate()
    14.     {
    15.         if(player.transform.position.sqrMagnitude > maxDist * maxDist)
    16.         {
    17.             Vector3 offset = -player.transform.position;
    18.             player.transform.position += offset;
    19.             foreach(CelestialBody planet in planets)
    20.             {
    21.                 planet.transform.position += offset;
    22.             }
    23.         }
    24.     }
    25. }
    For the most part, this works. However, I noticed a small drift over time (~3m / s) when on a planet's surface.
    Changing the maxDist to 0 makes the origin recenter every frame. In this scenario, the surface drift is very apparent. The planet doesn't move with velocity or rotate with the rotationTime. It is completely stationary.

    Another interesting observation is Inspector updates (Hovering over a field or clicking on a GameObject) allow the planet to move and rotate for a single frame. This makes me think that it involves some problem during the updating process, which the Inspector update forces to happen.

    I have tried changing the function from LateUpdate to Update to FixedUpdate, nothing changed.
    I have tried changing the order in which the functions occur, no success.
    I have added and removed variables for debugging purposes, no success.

    However, when I disable the Rubberbanding script, the planet moves and rotates exactly as intented.

    I also have tried directly setting the transform.position/rotation of the planets instead of rb.MovePosition/Rotation. However, translations and rotations are not handled by the Physics correctly.

    I have tried setting rb.position/rotation as well, but the same problem still occurs. It is also affected by Inspector updates as well, which is interesting.

    Video of the bug is attached below (maxDist is set to 0. View the Position X Coord and Rotation of the planet as the inspector is violated)

    Solution:
    After many hours and many different keywords, I came across a stackoverflow forum with the same problem as mine:
    https://stackoverflow.com/questions...not-working-if-transform-position-is-just-cha

    It turns out, transform.Translate (transform.position += acts similarly) and RigidBody.MovePosition kind of don't work together. To solve for this, you also need to run Physics.SyncTransforms(); This tells the Physics engine to update the positions, solving the problem.

    Code (CSharp):
    1. public void LateUpdate()
    2.     {
    3.         if(player.transform.position.sqrMagnitude > maxDist * maxDist)
    4.         {
    5.             Vector3 offset = -player.transform.position;
    6.             player.transform.position += offset;
    7.             foreach(CelestialBody planet in planets)
    8.             {
    9.                 planet.transform.position += offset;
    10.             }
    11.             //This syncs the Transform with the Physics Engine, solving the problem
    12.             //https://stackoverflow.com/questions/68801079/unity-rigidbody-moveposition-is-not-working-if-transform-position-is-just-cha
    13.             Physics.SyncTransforms();
    14.         }
    15.     }
     

    Attached Files:

    Last edited: Jul 10, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    "Think of [floating point] as JPEG of numbers." - orionsyndrome on the Unity3D Forums

    Floating (float) point imprecision:

    Never test floating point (float) quantities for equality / inequality. Here's why:

    https://starmanta.gitbooks.io/unitytipsredux/content/floating-point.html

    https://forum.unity.com/threads/debug-log-2000-0f-000-1f-is-200.1153397/#post-7399994

    https://forum.unity.com/threads/why-doesnt-this-code-work.1120498/#post-7208431

    You have about 6 to 9 digits of precision with single-precision
    float
    , also used in
    Vector3


    https://en.wikipedia.org/wiki/Single-precision_floating-point_format
     
  3. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    453
    Not being a mind-reader and not having access to your project, I read your text and looked at your code a couple of times, and could not understand what youre trying to do, what your problem is or what is happening.
     
  4. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    Note for clarity:
    The Celestial Bodies enact a force of gravity on the Player Controller, which flies like a spaceship. To make sure the player does not get too far from the origin (Precision errors occur past 100km) the Rubberbanding class is called. This class shifts over every object so that the player returns to the center.

    So say the maxDist is 100m: if the player's distance from the origin is > 100m, then shift everything over so that the player is now 0 meters from the origin and all the planet's distances from the player are the same.

    The problem is that small errors accrue over time due to (what I believe is) the rigidbody not correctly updating because of the Rubberbanding script. It is hard to explain this bug using text, so I have a short video attached showing the bug and the response to Inspector interactions.
     
    cosmochristo likes this.
  5. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    I don't think it is due to floating-point precision, as turning off the velocity and rotation axis stops the bug. The Physics engine seems to be getting the rb.MovePosition/Rotation calls, however the Transform is never updated to show the call.

    Also, when I meant small I mean like 3m / s, not like 0.00001m / s. I'll update that to be more clear.
     
  6. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    453
    Sorry but my brain still cannot grasp whats going on there. How about some screenshots of your game? Your video file at least for me was 4 seconds long and didnt show any gameplay

    Rubberbanding equals setting all planets position to negative player position? This does not compute. I'm not that smart.
     
  7. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    I used the name Rubberbanding because it reminds me of all the objects "rubberbanding" back to the origin. A better name would probably be "Recentering".

    I can't manage to upload a video short enough to be allowed. So here goes another horrible explanation:

    Once the player reaches some distance from the origin, the player and all Celestial Bodies are offset so that the player is at the origin and the planet's relative positions are still the same. It basically shifts everything over so the player is the center of the universe.
     
  8. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    453
    I would call it resetting instead of rubberbanding.

    If it is resetting, you should rather store the absolute positions of the player and those bodies, in variables, and your reset should reset these positions. Then you won't accumulate any error.
     
  9. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    It seems that the Rigidbody#MovePosition/MoveRotation doesn't work on the same frame/timestep the objects are reset. When the planets are reset less often (higher values of maxDist), the error from the 1 frozen frame is small. However, when maxDist is set to a low value, the number of frames where the planet's rigidbody stops moving increases.

    You did actually give me a solution to a different problem I wanted to solve, though. However, it wouldn't work since storing the absolute position would result in the same problem as not using a Resetting script: as the player gets further from the origin, floating-point-precision errors increase. A player might be able to move 1/10000th of a meter at the origin, but 100km out, precision makes the player move in snaps of 1m or so.

    The intention of the Resetting script is to keep the player near the origin where there is more precision. It also keeps the planets further away from the origin, where location precision is not as vital.
     
  10. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    453
    I'm sure you're aware of that your code is in FixedUpdate and LateUpdate that by definition execute completely separately from one another?
     
  11. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    Yes, however changing the function from LateUpdate to FixedUpdate does not solve the problem.

    Also, according to the Manual (https://docs.unity3d.com/ScriptReference/Rigidbody.MovePosition.html)

    Rigidbody.MovePosition seems to work with interpolation, which may mean that there is a delay between calling it and the body actually moving. If there are two render frames (2 LateUpdates) for every FixedUpdate, then one of those LateUpdate calls will be in the middle of the two FixedUpdates. I wonder if setting the position during that time causes the MovePosition to be cancelled out or not. It's also weird that changing the function to FixedUpdate also does not work correctly, also hinting that MovePosition is delayed by a FixedUpdate before it completes.
     
  12. JusticeKingCayden

    JusticeKingCayden

    Joined:
    May 5, 2018
    Posts:
    51
    After many hours and many different keywords, I came across a stackoverflow forum with the same problem as mine:
    https://stackoverflow.com/questions...not-working-if-transform-position-is-just-cha

    It turns out, transform.Translate (transform.position += acts similarly) and RigidBody.MovePosition kind of don't work together. To solve for this, you also need to run Physics.SyncTransforms(); This tells the Physics engine to update the positions, solving the problem.

    Code (CSharp):
    1. public void LateUpdate()
    2.     {
    3.         if(player.transform.position.sqrMagnitude > maxDist * maxDist)
    4.         {
    5.             Vector3 offset = -player.transform.position;
    6.             player.transform.position += offset;
    7.             foreach(CelestialBody planet in planets)
    8.             {
    9.                 planet.transform.position += offset;
    10.             }
    11.             //This syncs the Transform with the Physics Engine, solving the problem
    12.             //https://stackoverflow.com/questions/68801079/unity-rigidbody-moveposition-is-not-working-if-transform-position-is-just-cha
    13.             Physics.SyncTransforms();
    14.         }
    15.     }
     
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    If you're using that, you're doing it wrong. It's only there for legacy reasons. Saying you set the Transform and issue a MovePosition is like saying I set it to Red then set it to Blue and it's ignoring the Red. Two scripts asking for two different things.

    Don't modify the Transform unless you're scaling. Use the provided Rigidbody API. A Kinematic body is designed to be moved with MovePosition/MoveRotation. If you write to the Transform yourself then that'll be read when the physics next simulates. Until that happens, the Transform is the only thing that has changed. Don't write to the Transform, it's the role of the Rigidbody.

    If you're doing stuff per-frame but your physics is running (by default) during the FixedUpdate then you're doing things out-of-sync so you need to not do that. You don't need to update physics per-frame, that's what interpolation is for.

    After all these years, these posts are still around because not writing to the Transform is something that a lot of devs cannot understand. :)
     
    Last edited: Jul 11, 2023
    Ryiah likes this.
  14. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250
    That is most likely the cause - the size of the translation you are using when a shift occurs will have a proportional error x 3 x (potentially) multiplication magnification during calculations. The errors will manifest in different ways depending on what you are doing and your Scene elements.

    It is better to use continuous small differential shifts.
     
  15. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250
    Can we have a bit more clarification on: "Don't modify the Transform unless you're scaling."? And what about writing to a parent transform the the Rigidbody object?
    I have many examples of using Rigidbodies and writing to the transform and there is no problem, for vehicles, characters; kinematic or not. e.g. the first aircraft in the following video is kinematic and the second is not;
     
  16. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    You don't have a problem because we cannot just ignore you changing those so we're forced to perform an update when the simulation runs. That doesn't mean it's how it's meant to work. The Rigidbody is in charge of the Transform (proxy) in that it has its own pose (position/rotation). You are supposed to uses its API to cause physics-based movement and when the simulation runs, it performs simulation then writes to the Transform. You writing to the Transform means that when the simulation runs, the Rigidbody has to read the Transform and instantly jump there which isn't a physics-based movement.

    The details don't matter, you should ALWAYS drive the Rigidbody if you're using physics if you want physics-based dynamics.

    Changing the parent of a Rigidbody is the same thing although why you'd do this I don't know.

    The pose of a Rigidbody doesn't including scaling as it has no use for it as 3D physics only has 3 position DOF and 3 rotation DOF and no scaling. The only way to change scaling is via the Transform but again, this causes a lot of work to happen for the attached colliders when the simulation runs as colliders are (to the physics system) immutable volumes.

    Your definition of "no problem" might mean to you that you are not (currently) encountering any problems (great!) but that all depends on what you're doing. You can ensure you NEVER have problems (collision tunnelling, joint issues etc) by always only using the Rigidbody API.

    You should also know that writing to the Transform doesn't change anything but the Transform. When you do that, nothing in physics will change. Only when the simulation runs does it have to deal with it.

    Hope that helps clarify.
     
    Kurt-Dekker and spiney199 like this.
  17. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250
    @MelvMay , thanks for explaining these things more. Are the details you have given in online documentation?

    I develop for continuous motion within large, full-scale open worlds. Mathematically, and because of limitations on storage size (e.g. floats), this can only work optimally with fully-relative motion with the player/vehicle at the origin. Any deviation from fully relative and centred motion leads to sub-optimal accuracy and jitter.

    The validity, and compatibility, of fully relative physics motion is proven and it is stated clearly by Hawking when referring to Einstein's foundation for relativity. I have also proven that it works in computation, even in Unity.

    Unity implements absolute motion, internally, e.g. Rigidbodies and CharacterController. Although this is perfectly compatible with the laws of physics for unlimited maths, it is it introduces positionally sensitive errors when applied in the limited registers of computers. Consequently, Unity's approach does not scale and introduces unwanted jitter from as little as a few meters away from the origin to any distance from there.

    Writing to transforms is currently the only way that this weakness of Unity can be corrected.

    I know from my PhD research, and continued studies, that one of the main reasons “these posts are still around” is because floating point jitter has been a general problem in games and simulation for the past 30 years. It is not because we “devs” don't know how Unity has encapsulated absolute motion.

    Lack of understanding goes both ways: I could argue that “Unity staff” do not understand how to utilise floating point numbers optimally by applying knowledge of relative physics motion. However, I am also aware of some Unity staff implementing some form of origin-centric relative motion in HDRP.
     
    Last edited: Feb 17, 2024
  18. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250
    I just realised there is a contradiction between this the following and the original post by @JusticeKingCayden:

    Here you say Unity responds to a change of parent transform by performing an update, implying that you simulate the change in some way, e.g. as a .Move, perhaps?

    And the original post says the developer needs to explicitly run it:

    "run Physics.SyncTransforms(); This tells the Physics engine to update the positions, solving the problem".

    What truly happens?

    There is one thing that should have been clear, after over 13 years, from the time of the 2010 Wiki on origin-shifting:

    Developers modifying the parent transform of a Rigid-body to support large-scale open Worlds do not want you to force the physics to respond: they want the position to change without the physics jerking about.

    The same applies to real floating origin algorithms: we don't want the physics to respond in this case. If I wanted physics simulation I would use the Rigidbody API.

    Suggestion: If Unity wants to support developers in making large-scale open Worlds, why not provide an API option that they can call on application Start()?

    E.G. Physics.DontSimulateParentTransformChanges(Rigidbody rb).
     
  19. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    You might be suprised at the experience level here at Unity. Personally I've been doing it professionally for over 30 years in many industries. Lack of a feature doesn't equate to lack of knowledge.

    That's a creative way of comparing the math of physics law to the limitations of the single-precision 32 FP IEEE 754 spec used in most hardware. ;)

    There's nothing specific about a parent changing here. The Transform has changed (somehow), that's all it knows.

    Let me explain: When a Transform hierarchy changes, this happens without the physics system or any other Unity system knowing about it or being involved in any way. There is no callback internally to let systems know about this change. For instance, renderers only know the Transform state when they come to render.

    Physics only knows about this when the physics simulation runs. When the simulation runs, it runs a transform-sync where it asks the Transform system what (if anything) the user might have changed, if it has changed it reads the absolute position/rotation of the Transform (it knows nothing about a relative change) and writes that to the Rigidbody i.e. "teleport" to ensure they're in sync, satisfying the whole purpose of that function i.e. dev has changed the transform directly and has bypassed the physics engine. Any Transform scale change can affect the scaling of collider shapes.

    Transform Sync isn't a "feature", it was forced upon physics teams when the Transform system changed many, many years ago. The change there was that the Transform system no longer allowed any systems to hook into a change notification so the physics system didn't immediately know about changes so an explicit sync (read) of transforms was required. Previously, this happened synchronous to the Transform change as physics and other systems were hooked into change callbacks.

    The change to the Transform system was part of the multithreaded Job System work which had to ensure that a change to a Transform had no side-effects. Transform sync was introduced for backwards compatibilty only. The related method of AutoSyncTransforms defaulted to on and causes any read operation related to spatial values (positions, rotations etc) to call a sync-transform.

    I only wish the source code was browsable here as it’d be much easier to explain than the poor job I’m doing above! ;)

    Back to what I think your point was; what the sync-transform does depends on what type of body we're talking about or even if there is a body or not. 3D Kinematic bodies in Unity are designed to be driven via the Transform. You set a target Transform and it moves there. A more direct API call is to use MovePosition. This directly exposes a feature in PhysX to move to that target position. It's how PhysX Kinematic bodies were designed. If you explicitly set the Rigidbody.position then it'll instantly be there. Dynamic bodies in 3D don't do that AFAIK and they'll just teleport instantly to that position.

    I would suggest creating your own thread discussing specific feature request(s) as I personally have no way of landing any feature in 3D physics, I don't work in that area nor am I a product manager etc. I was only here to provide information on how it currently works internally (given my history with it) and give you pointers on what to avoid in the hope that it would help your understanding.

    For 3D physics features you'd have to speak to others and that would be best achieved on your own thread (I could maybe tag someone in), especially if you wanted different behaviour there and/or wanted something like the world-origin shifting in PhysX to be exposed but this highlights another issue, physics isn't the main issue when dealing with large open worlds in Unity; those would also be rendering, animation, limited memory/performance scalability of the GameObject model etc. There's plenty of work going on in those areas though to solve those problems.
     
    cosmochristo likes this.
  20. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250
    Thank you again @MelvMay for your detailed response. You have provided a lot of clarification detail so I will try to respond to each part I think is important to relative motion.

    Not responding to the change of parent transform change is required for Transforms that are used in relative motion because the relative motion itself should not incur side effects. In other words, AutoSyncTransforms needs to be off for such transform changes.

    If it is turned on by default then it sounds like I would need to turn it off before each relative motion (of the World or other parent top-level tranforms) and then on again for normal unity operation.

    Does that sound correct?
     
  21. cosmochristo

    cosmochristo

    Joined:
    Sep 24, 2018
    Posts:
    250

    NOOO! That is an abomination, a perversion, originally created under the name of floating origin, that uses absolute, not relative motion.

    Absolutely! I developed floating origin as a general solution to positional variance for all operations on any type of position information, such as time, rotation, translation, and any calculation made with positional parameters.

    Physics failures simply exposed a specific type of unwanted variance, or jitter.