Search Unity

Resolved InverseInertia == 0 && ApplyAngularImpulse => NaN

Discussion in 'Physics for ECS' started by nicolasgramlich, Dec 5, 2020.

  1. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Hey everyone,

    so in
    0.4.1-preview
    I used to have a conversion system that sets Inverse Inertia to 0 on a per axis basis. I used this to lock x and z axis, so that my units (it's an RTS game) don't topple over and can only rotate around their y-axis. The system is as as follows:

    Code (CSharp):
    1. public bool LockX;
    2. public bool LockY;
    3. public bool LockZ;
    4.  
    5. public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem gameObjectConversionSystem) {
    6.    if (entityManager.HasComponent<PhysicsMass>(entity)) {
    7.       var physicsMass = entityManager.GetComponentData<PhysicsMass>(entity);
    8.       physicsMass.InverseInertia[0] = LockX ? 0 : physicsMass.InverseInertia[0];
    9.       physicsMass.InverseInertia[1] = LockY ? 0 : physicsMass.InverseInertia[1];
    10.       physicsMass.InverseInertia[2] = LockZ ? 0 : physicsMass.InverseInertia[2];
    11.       entityManager.SetComponentData(entity, physicsMass);
    12.    }
    13. }
    The system that executes the
    ApplyAngularImpulse
    is also pretty simple and looks like this:

    Code (CSharp):
    1. protected override void OnUpdate() {
    2.     var deltaTime = Time.DeltaTime;
    3.     Entities.WithAll<Unit>().ForEach((Entity entity, ref PhysicsVelocity physicsVelocity, in Rotation rotation, in PhysicsMass physicsMass, in Target target, in Movement movement) => {
    4.         // ...
    5.  
    6.         /* Apply impulse along that axis according to the magnitude of the angle: */
    7.         physicsVelocity.ApplyAngularImpulse(physicsMass, cross * angle * deltaTime * movement.TurnSpeed);
    8.     }).ScheduleParallel();
    9. }
    On
    0.4.1-preview
    this works just fine, but on
    0.5.1-preview.2
    , after a second or two the transforms of my entities end up with a bunch of NaNs (and therefore stop rendering). (For testing purposes I hardcoded it to do:
    physicsVelocity.ApplyAngularImpulse(physicsMass, 0);
    and things blow up immediately, but with hardcoded:
    physicsVelocity.ApplyAngularImpulse(physicsMass, 0.00001f);
    things are fine)

    The solution that makes things work again is to not set the
    InverseInertia
    0, but a very small number instead, like
    0.000001f
    , which is good enough. (Note: my units might be toppling over extremely slowly, but it's good enough for my game.) No more NaNs anywhere and I can finally be productive again:

    Code (CSharp):
    1. public bool LockX;
    2. public bool LockY;
    3. public bool LockZ;
    4.  
    5. public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem gameObjectConversionSystem) {
    6.    if (entityManager.HasComponent<PhysicsMass>(entity)) {
    7.       var physicsMass = entityManager.GetComponentData<PhysicsMass>(entity);
    8.       physicsMass.InverseInertia[0] = LockX ? 0.000001f : physicsMass.InverseInertia[0];
    9.       physicsMass.InverseInertia[1] = LockY ? 0.000001f : physicsMass.InverseInertia[1];
    10.       physicsMass.InverseInertia[2] = LockZ ? 0.000001f : physicsMass.InverseInertia[2];
    11.       entityManager.SetComponentData(entity, physicsMass);
    12.    }
    13. }
    So long story short, my question is what the currently recommended way of locking entity rotation is?

    Best Regards,
    Nicolas
     
  2. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    Do you have a minimum repro of this? I opened the 2b6 sample scene and added an Apply Rocket Thrust Authoring component to the Cube with Locked Motion. With an offset thrust angular impulses are applied without problems.
    All
    ApplyAngularImpulse
    does is
    velocity.Angular += impulse * mass.InverseInertia;
    so I'd need more information to determine where the NaNs would come from there!

    0.5.1 introduces the fixed step system which did lead to some issues for folk having issues about where their command buffers and systems were played back and updated. For example, locally we found some command buffer was making change after BuildPhysicsWorld and before ExportPhysicsWorld. This meant that entity ordering was not guaranteed to be the same at ECS boundaries of physics system, which ultimately lead to NaNs. So when is your system being updated?
     
  3. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    The system that executes
    ApplyAngularImpulse
    effectively runs in:

    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup))]
    2. [UpdateBefore(typeof(FixedStepSimulationSystemGroup))]
    Unfortunately I do not have a minimal repro. I could setting one a shot by next weekend maybe :rolleyes:
     
  4. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    Hmm, if its updating before
    BuildPhysicsWorld
    , then after your
    Entities.WithAll
    is scheduled, it might be worth passing
    Dependency
    from your system into
    BuildPhysicsWorld.AddInputDependencyToComplete
    . That way, the Physics system won't do anything with the entities until you've finished modifying them.
     
  5. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Thanks stevee, I'll give that a shot!
     
  6. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    @steveeHavok turns out the real bug was that somewhere I had a normalize instead of a normalizesafe of a vector that was derived from physics velocity. With inverseInertia = 0 it was "too stable" and blew up into NaNs. After fixing the real root cause the 0.000001f InverseInertia hack isn't necessary anymore :cool:
     
    petarmHavok and steveeHavok like this.