Search Unity

Rigidbody2D.MoveRotation Drifting - Rounding Loss?

Discussion in 'Physics' started by PeachyPixels, May 1, 2020.

  1. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Hi Everyone,

    I have a series of objects on the screen that are rotating in sync. The objects have kinematic 2d rigidbodies with polygon colliders. They are rotated using the following code...

    Code (CSharp):
    1. public void FixedUpdate()
    2. {
    3.   Rigidbody2D.MoveRotation(Quaternion.Euler(0f, 0f, Rigidbody2D.rotation + RotationSpeed * Time.deltaTime));
    4. }
    5.  
    The trouble is, after a while they start drifting out of sync and it breaks the illusion that I am trying to create.

    I wondered if the drift was a loss in conversion, so changed the code (which is probably better anyway) to...

    Code (CSharp):
    1. public void FixedUpdate()
    2. {
    3.   Rigidbody2D.MoveRotation(Rigidbody2D.rotation + RotationSpeed * Time.deltaTime);
    4. }
    5.  
    But the issue remains, so am thinking this might be a rounding loss?

    I've tried adding interpolation to the rigidbodies, but it made no difference.

    Might it be better to calculate the rotation from the transform then apply using MoveRotation? Or calculate the rotation from a fixed point in time?

    Any help would be greatly appreciated.
     
  2. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Update...

    I can see one issue with my code, it should use fixedDeltaTime not deltaTime...

    Code (CSharp):
    1. Rigidbody2D.MoveRotation(Rigidbody2D.rotation + RotationSpeed * Time.fixedDeltaTime);
    2.  
    But this doesn't fix the drift issue either.
     
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    You should always use the body position/rotation as the reference because the Transform isn't going to be the same if you use interpolation.

    You should use "Time.fixedDeltaTime" even though Unity will temporarily set "Time.deltaTime" to be the same during fixed-update (really don't like this).

    Some guesses:
    • If these are Dynamic Rigidbody2D then they might be colliding in which case the collision response means they won't get to the target pose.
    • They have other stuff like Joints or anything that will interfere with the move.
    • The mass and therefore the rotational inertia should not affect the MoveRotation however check that this is actually the case.
    • Presumably the above code examples are pseudo-code because "Rigidbody2D" is the class type.

    Beyond that I'm not sure. If you have a simple reproduction case and can host it I'll take a look. If you want a location you can upload it to then DM me with your email and I'll send you a secure link.
     
    PeachyPixels likes this.
  4. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Many thanks for the detailed reply. I don't believe it to be any of the four points you posted.

    I've actually managed to isolate the issue to a standalone project, but before sending on wanted to run a theory past you.

    I noticed that the Rigidody2D.rotation value is cumulative. So the longer it runs, the larger it gets and the less precision it has. Could it be that the loss in precision over time is causing the drift?

    When rotating the object (in the editor) using the transform, the Transform.z value is cycled as is the Rigidbody2d.rotation value. But when changing it via Rigidbody2D.MoveRotation then
    Rigidbody2D.rotation is cumulative.

    Anyway, just wanted to run that past you. I'll keep experimenting and get back to you next week.
     
    Last edited: May 1, 2020
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    Yes, the 2D physics engine (Box2D) supports multiple windings because joints like HingeJoint2D support it too.

    Whilst the precision of large float values can become an issue, if you're doing identical work to all then I'd be surprised if it's the cause because they'd all do the same.

    There was a bug fixed in the last month or so related to Rigidbody2D interpolation causing a child to move out of sync with a parent Rigidbody2D when interpolation is used. I didn't think of this initially as it doesn't sound like your issue but it might be worth turning off Interpolation to ensure it's not that.

    My main problem here is that you're describing a situation where you have multiple things running identical code that are not staying in-sync and I don't see how that can happen.
     
  6. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Definitely not using interpolation, so it can't be that.

    I've spent some time experimenting over the weekend and this morning and it's left me even more confused.

    I was noticing bad drift on Friday, but over the weekend and this morning less so. Then suddenly this morning it started to drift by a large amount (that is, within a minute or so of running the example, the drift was very noticeable). I've tried all sort of things to isolate or reproduce consistently, but to no avail.

    Here's something I have noticed though... I'm running this on a Surface Pro 3 with Unity 2019.2.21f1 and when the SP3 heats up and the CPU\GPU is throttled, the frame rate drops and the drift seems to get worse. I can stop the demo, let the SP3 cool down, start it again and no (or minimal drift)

    Now in some ways that makes next to no sense (although possibly a threading\sync\contention issue? although isn't physics 2d single threaded?) and is likely a red herring, but it's something I've noticed. I really hope it's not something silly that I'm doing, but I am seeing inconsistent and unexpected behaviour and it's genuinely left me scratching my head. The odd thing is this feature has been running in the last 6 months of development and I've never noticed this before. It appears to be a recent thing, although running Unity 2019.2.21f1 I haven't updated in a few months so that makes no sense either.

    Anyway, I've zipped up the demo and included three example.png files in the root folder showing what I am seeing. I'll dm you my email and send it across.
     
    Last edited: May 4, 2020
  7. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Quick update...

    I've been seeing the issue running in the editor (with its larger overhead) but just built a Win32 version and the drift is still visible. The SP3 was running at 4% CPU and 17% GPU usage (so not even close to taxing) so the heating observation is obviously a red herring.
     
  8. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Another update...

    I was curious to see what would happen if I rotated using the Transform...

    Code (CSharp):
    1. public void Update()
    2. {
    3.   gameObject.transform.Rotate(0f, 0f, RotationSpeed * Time.deltaTime);
    4. }
    The end result was no drift whatsoever. Using this approach I also spotted that the Rigidbody2D.rotation cycles, hence its precision is higher. So maybe it is a precision thing!?

    Anyway, will include that in the demo as well.

    I've also tried running this under Unity 2019.3.11f1 and am seeing the exact same behaviour as above (with Transform & Rigidbody rotations)
     
    Last edited: May 4, 2020
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    I send you a DM with a workspace where you can upload.

    Yes, it's all on the main-thread unless you turn on MT in physics settings. The Rigidbody2D.rotation wouldn't seem to be the problem because it would make sense that it'd be the same for all of them.

    Looking forward to figuring it out. Doesn't matter if it's something you're doing or if it's some bug somewhere, we'll get to the bottom of it and fix it.
     
    PeachyPixels likes this.
  10. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    713
    Hi Everyone,

    After discussing with Melvyn off-forum and sending a demo, it seems the drifting issue was a result of precision loss. Melvyn suggested the following as a fix...

    Code (CSharp):
    1. Rigidbody2D.rotation = (Rigidbody2D.rotation % 360f) + (RotationSpeed * Time.fixedDeltaTime);
    Setting rotation instead of calling MoveRotation doesn't appear to accumulate the value, so along with the modulus, it means a precision of 4 is always guaranteed. Precision loss still occurs, but at a much slower rate to the point where the scene has to be running for quite a while before it's noticeable.

    Anyway, just thought I would post for completeness and anybody else who may encounter this in the future.

    Many thanks to Melvyn for the help!
     
    Last edited: May 6, 2020
    MelvMay likes this.