Search Unity

Raycast misses object untill I manually disable and re-enable box collider on it

Discussion in 'Physics' started by shelim, Jul 26, 2021.

  1. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Hello all,

    I am working on serialization/deserialization system which includes storing RigidBody state in Google Protobuf-based structures. The deserialization step is done in Awake() function inside script accessible on given scene - where the previous content of scene is Destroy'ed and new one is loaded.

    Recently I got a very stange bug that is reproducable (sadly, not always) on complex scenes, but it slips away when trying to minimize code involved.

    Basically the situation is that after loading a scene sometimes raycast misses the object clearly in position:

    hit1.png

    When I manually disable and re-enable box collider on this object:

    hit2.png

    The raycast starts to hits OK (from the next frame):

    hit3.png

    It is obvious that I miss a step to re-generate Physics state after de-serialization, but sadly I don't know which.

    I tried:
    - Calling `Physics.SyncTransforms` after load - no effect
    - Moving object manually without reenabling collider -> raycast would still miss them
    - Calling `Rigidbody.ResetInertiaTensor` after load on the box - no effect

    Raycasts in question are cast every frame in Update and they miss every time until I manually disable and then re-enable collision. The problem does not manifest always, leading me to believe there is some kind of race condition...

    I tried also work-arounding:

    Code (CSharp):
    1. foreach(var collider in FindObjectsOfType<Collider>(false))
    2. {
    3.        collider.enabled = false;
    4.        collider.enabled = true;
    5. }
    But had no effect on raycast as well.

    To specify: the 'normal' collisions between objects are working, only raycasts are failing (and only till I disable and re-enable colliders)...
     
  2. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Unity version: 2021.1.12f1
     
  3. nighty2

    nighty2

    Joined:
    Dec 29, 2020
    Posts:
    29
    Dear shelim,

    I have a similar problem.
    After loading a scene additively, the raycasts will work only after re-enabling colliders, and only for about 2 seconds.

    Did you find a solution?
    I am currently on Unity 2020.3.4f1
     
  4. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Actually I had!

    After careful examination and pinpointing the problem I found out that calling DestroyImmediate on any physics-based gameObject is broken and will broke other physics-related things, even happening much later on (including random crashes in final build!).

    I ended up with deserialization step split into several frames - first, I delete all existsing objects (via normal Destroy), wait a fixed frame until physics get to know the objects are not longer there, then re-create serialized gameObjects
     
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,481
    It's nothing to do with physics specifically. DestroyImmediate is a terrible thing to call because objects may still be involved in things like callbacks or later processing in the frame/update. This is why you should never call it unless you absolutely know what you're doing and can guarantee this. It's one of the reasons why things such as physics, explicitly don't allow this to be called during physics callbacks but the same goes for other parts of the engine such as validate, start callbacks etc.

    This is why you have the warning on the API page about it here.
     
  6. nighty2

    nighty2

    Joined:
    Dec 29, 2020
    Posts:
    29
    Hi shelim,
    hi MelvMay

    thank you for the rapid response!
    It did not help me yet, but this rabbithole seems rather deep.
     
  7. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Yes, I understand that clearly; I was under false impression I am solving a different bug altogether, having an rigidbody destroyed, then spawned another rigidbody in similiar position, with inertia/speed/etc. loaded from serialized data would still result in physics to consider both objects overlapping for the duration of the frame. This resulted in all loaded stuff that overlapped with destroyed stuff to bump up on the instant of load. As I said, the only solution I found was to spread loading to separate frames (ie. there is nothing which could tell physics to remove instantly object from simulation)
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,481
    When you use "Destroy" on the GameObject or just a physics component, there is no longer any backend physics stuff around, same as when you simply (say) disable a component like a BoxCollider. It's just that the underlying native Unity GameObject and components are still around (not the physics stuff they created); destroying them fully with DestroyImmediate means removing the native GameObject/Components fully there and then and the native engine might still have work to do with them.

    Using Destroy does mean there's nothing for the physics to do.

    What is described in the post simply sounds like a bug but I don't have any view on that TBH.
     
  9. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    After more testing I found out that the problem will only manifest if called from FixedUpdate.

    Relevant code:
    Code (CSharp):
    1.  
    2. public class Respawn : MonoBehaviour
    3. {
    4.     public Rigidbody PrevObject;
    5.     public Rigidbody NextObject;
    6.  
    7.     public Text TextInfo;
    8.  
    9.     private bool isLoading;
    10.  
    11.     private void Update()
    12.     {
    13.         if (Input.GetKeyDown(KeyCode.Space))
    14.             isLoading = true;
    15.     }
    16.  
    17.     void FixedUpdate()
    18.     {
    19.         if (isLoading)
    20.         {
    21.             isLoading = false;
    22.  
    23.             var pos = PrevObject.transform.position;
    24.             var rot = PrevObject.transform.rotation;
    25.             Destroy(PrevObject.gameObject);
    26.             PrevObject = Instantiate(NextObject, Vector3.zero, Quaternion.identity);
    27.             PrevObject.transform.position = pos;
    28.             PrevObject.transform.rotation = rot;
    29.             PrevObject.velocity = Vector3.zero;
    30.             PrevObject.angularDrag = 0;
    31.             PrevObject.angularVelocity = Vector3.zero;
    32.             TextInfo.text = "Removed a box and created new one!";
    33.         }
    34.     }
    35. }
    36.  
    See recording:
    6iRypfQGEj.gif

    Repro scene attached
     

    Attached Files:

  10. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,481
    I'm not sure I follow what you're demonstrating here if I'm honest but in-case it's relevant, when you instantiate, you must provide the position/rotation there and then. If you change a Transform, ONLY the transform changes, Transforms don't notify other systems they've changed. This include renderers which will read it when they render and also includes physics which will check to see if you changed it too.

    Physics will check to see if you changed any Transforms (which you really shouldn't be doing) only when the simulation runs (after all the FixedUpdate callbacks by default).

    In your code above, if you were to perform a raycast after the Instantitate, the "PrevObject" would be at position zero with no rotation. Also, referring to the Transform as if it were the authority on position/rotation is a bad idea; the Rigidbody is the authority, after all, its primary role is to simulate and write to the Transform so read its pose. For example, when using Interpolation, the Transform is moving from the old Rigidbody pose to the current one!

    So at best it should be:
    Code (CSharp):
    1. var pos = PrevObject.position;
    2. var rot = PrevObject.rotation;
    3. Destroy(PrevObject.gameObject);
    4. PrevObject = Instantiate(NextObject, pos, rot);
     
  11. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    The new position was comming from serialization system, but just to be 100% sure I changed the relevant line to (and removed manually setting pos/rot later on):
    Code (CSharp):
    1. PrevObject = Instantiate(NextObject, pos, rot);
    The bumping up effect is still happening.
     
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,481
    And did you get the pos/rot from the old body or its Transform?

    Also, it'd be interesting to see what would happen if you disabled the original body then re-enabled it again i.e. deactivate/activate the GameObject.

    It could be related to the settled object having transient state while it's stable that the new one doesn't have. It certainly looks like overshoot for impulses i.e. it thinks it's overlapped on the ground plane. To note though, I'm a 2D physics dev so I'm not speaking from authority here but still trying to be helpful.

    I guess what I'm trying to describe is that an object coming to rest at a post in contact is potentially more stable than a new object created there but without knowing why the impulse is happening, it's not clear.
     
  13. shelim

    shelim

    Joined:
    Aug 26, 2017
    Posts:
    29
    Transform, I copied them into Vector/Quaternion structs then destroyed original object. But copying from RigidBody yields in the same values...
    I was doing that experiments when I originally encountered the problem :) It made no difference, and that was why I was under false impression of DestroyImmediate is required in this case.
    To clarify: I was attempting to reset all RigidBody properties from serialized data, including impulse. I also tried to spawning object in the next frame by yielding, and this indeed solved the problem.