Search Unity

Bug Physics Shape combinations cause different results

Discussion in 'DOTS Physics' started by Focus_JZ, May 28, 2021.

  1. Focus_JZ


    Nov 19, 2018
    I have 3 ground planes that are each using physics shapes in a different way. The first has a plane physics shape, the second has a mesh physics shape with the unity standard plane assigned and the last one is segmented into 4 plane physics shapes. For each plane I have two balls with the same initial velocity, one has a sphere physics shape and one has a mesh physics shape with the standard sphere assigned.
    The setup of ground planes and spheres.

    The different physics shapes visualised (I highlighted 2 of the 4 segments so it's obvious it's built out of segments).

    With this setup some of the balls make small bumps with the ground plane in the case of the 'mesh' and the segmented ground plane, putting them slightly into the air. Besides that the mesh sphere collision ball seems to have a completely different simulation in comparison to the sphere collision ball. Beneath you can see a top-down screenshot of the different positions the balls are in after a few seconds.

    To reiterate, the left ball on each plane is the mesh physics shape while the right one is a sphere physics shape.
  2. steveeHavok


    Mar 19, 2019
    It would be worth checking out the section on Speculative CCD in the general docs. Hopefully this will explain the bumping. Basically at the lowest level the individual triangles in the mesh plane are being collided against.
    Also worth checking out the ModifyNarrowphaseContacts demo and associated script.
  3. Focus_JZ


    Nov 19, 2018
    For clarity, does that mean that the mesh generated for a plane physics shape has polygons whose normals are not straight up? The ModifyNarrowphaseContacts script seem to adjust the surface normal so they are all facing the correct way (taking the object's rotation into account), however I'd assume a plane mesh to also have the correct normals while it still causes the bumping.

    Image from the Speculative CCD docs linked above

    I am a bit confused by how the c0 vector is the result from this AABB, is it because the boxes have 'volume' and you clip the corner and if so how does this work for planes?
  4. AlienFreak


    Jul 2, 2012
    I, as well, am confused by this. Are you simulating a bezeled edge or using vertex normals? Is c0 the average vector from the velocity of the balls travel hitting the side of b0? The docs are clear that the error results in a bump but not specifically how the algorithm is computed.

    I am using a regular grid of 1x1x1 blocks (though some may be removed or animated at times) and the ModifyNarrowphaseContacts only works for a singular mesh. How could ModifyNarrowphaseContacts easily be modified to take into account multiple meshes that are vertex snapped together?
  5. Focus_JZ


    Nov 19, 2018
    Maybe I can help with the second part, I am not sure if you are using a simple box physics shape and it's still giving you bumps, but I think modifying the script a bit would allow it to handle multiple meshes. However the script than needs to be attached to every cell (1x1x1 block) and it should each have a physics body, so I'm not sure if that's very peformant but you could run some tests.

    Code (CSharp):
    1. using System;
    2. using Unity.Assertions;
    3. using Unity.Burst;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Jobs;
    7. using Unity.Mathematics;
    8. using Unity.Physics;
    9. using Unity.Physics.Authoring;
    10. using Unity.Physics.Extensions;
    11. using Unity.Physics.Systems;
    12. using UnityEngine;
    13. using MeshCollider = Unity.Physics.MeshCollider;
    15. public struct ModifyNarrowphaseContacts : IComponentData
    16. {
    17.     public Entity surfaceEntity;
    18.     public float3 surfaceNormal;
    19. }
    21. [RequireComponent(typeof(PhysicsBodyAuthoring))]
    22. [DisallowMultipleComponent]
    23. public class ModifyNarrowphaseContactsBehaviour : MonoBehaviour, IConvertGameObjectToEntity
    24. {
    25.     // SurfaceUpNormal used for non-mesh surfaces.
    26.     // For mesh surface we get the normal from the individual polygon
    27.     public Vector3 SurfaceUpNormal = Vector3.up;
    29.     void OnEnable() { }
    31.     void IConvertGameObjectToEntity.Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    32.     {
    33.         if (enabled)
    34.         {
    35.             var component = new ModifyNarrowphaseContacts
    36.             {
    37.                 surfaceEntity = entity,
    38.                 surfaceNormal = transform.rotation * SurfaceUpNormal
    39.             };
    40.             dstManager.AddComponentData(entity, component);
    41.         }
    42.     }
    43. }
    45. // A system which configures the simulation step to rotate certain contact normals
    46. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    47. [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(StepPhysicsWorld))]
    48. public class ModifyNarrowphaseContactsSystem : SystemBase
    49. {
    50.     StepPhysicsWorld m_StepPhysicsWorld;
    51.     BuildPhysicsWorld m_BuildPhysicsWorld;
    53.     protected override void OnCreate()
    54.     {
    55.         m_StepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
    56.         m_BuildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    58.         RequireForUpdate(GetEntityQuery(new ComponentType[] { typeof(ModifyNarrowphaseContacts) }));
    59.     }
    61.     protected override void OnUpdate()
    62.     {
    63.         if (m_StepPhysicsWorld.Simulation.Type == SimulationType.NoPhysics) return;
    65.         EntityQuery modfiyContactsQuery = GetEntityQuery(new ComponentType[] { typeof(ModifyNarrowphaseContacts) });
    66.         NativeArray<Entity> modifyContactEntities = modfiyContactsQuery.ToEntityArray(Allocator.TempJob);
    68.         for (int i = 0; i < modifyContactEntities.Length; i++)
    69.         {
    70.             var modifyContact = GetComponent<ModifyNarrowphaseContacts>(modifyContactEntities[i]);
    72.             var surfaceNormal = modifyContact.surfaceNormal;
    73.             var surfaceEntity = modifyContact.surfaceEntity;
    75.             SimulationCallbacks.Callback callback = (ref ISimulation simulation, ref PhysicsWorld world, JobHandle inDeps) =>
    76.             {
    77.                 return new ModifyNormalsJob
    78.                 {
    79.                     SurfaceEntity = surfaceEntity,
    80.                     SurfaceNormal = surfaceNormal,
    81.                     CollisionWorld = world.CollisionWorld,
    82.                 }.Schedule(simulation, ref world, inDeps);
    83.             };
    84.             m_StepPhysicsWorld.EnqueueCallback(SimulationCallbacks.Phase.PostCreateContacts, callback);
    85.         }
    87.     }
    89.     [BurstCompile]
    90.     struct ModifyNormalsJob : IContactsJob
    91.     {
    92.         public Entity SurfaceEntity;
    93.         public float3 SurfaceNormal;
    94.         [ReadOnly] public CollisionWorld CollisionWorld;
    95.         float distanceScale;
    97.         public void Execute(ref ModifiableContactHeader contactHeader, ref ModifiableContactPoint contactPoint)
    98.         {
    99.             bool isBodyA = (contactHeader.EntityA == SurfaceEntity);
    100.             bool isBodyB = (contactHeader.EntityB == SurfaceEntity);
    101.             if (isBodyA || isBodyB)
    102.             {
    103.                 if (contactPoint.Index == 0)
    104.                 {
    105.                     var surfaceNormal = SurfaceNormal;
    107.                     // if we have a mesh surface we can get the surface normal from the plane of the polygon
    108.                     var rbIdx = CollisionWorld.GetRigidBodyIndex(SurfaceEntity);
    109.                     var body = CollisionWorld.Bodies[rbIdx];
    110.                     if (body.Collider.Value.Type == ColliderType.Mesh)
    111.                     {
    112.                         unsafe
    113.                         {
    114.                             var meshColliderPtr = (MeshCollider*)body.Collider.GetUnsafePtr();
    115.                             meshColliderPtr->GetLeaf(isBodyA ? contactHeader.ColliderKeyA : contactHeader.ColliderKeyB, out var leafCollider);
    116.                             var polygonCollider = (PolygonCollider*)leafCollider.Collider;
    117.                             surfaceNormal = math.rotate(body.WorldFromBody.rot, polygonCollider->Planes[0].Normal);
    118.                         }
    119.                     }
    121.                     var newNormal = surfaceNormal;
    122.                     distanceScale =, contactHeader.Normal);
    124.                     contactHeader.Normal = newNormal;
    125.                 }
    126.                 contactPoint.Distance *= distanceScale;
    127.             }
    128.         }
    129.     }
    130. }
  6. steveeHavok


    Mar 19, 2019
    The broadphase representation of the dynamic body (i.e. the AABB) is expanded by the velocity of a body. Hence in the docs example the red dotted box covers the t0 and t1 positions of the ball. The ball's expanded AABB therefore overlaps both b1 and b0.

    The narrowphase part of the collision pipeline ultimately works on convex primitives (spheres, triangles, convex hulls).This means that the narrowphase needs to collide the ball with both box 1 and box 0.

    box 1 and box 0 could just as easily have been triangles in a composite MeshCollider rather than boxes in a CompoundCollider here. The result will be the same. Individual narrowphase collider queries with the two individual convex primitives.

    The main query for narrowphase collision detection is going to get the closest separating distance and separating plane between two primitives. The c0 and c1 arrows represent the closest position and separating direction between the ball and the boxes (or triangles). The collision resolution code is trying to make sure that the dynamic body stays on the correct side of the separating plane. So the c0 and c1 normals are not face normals.

    The script is effectively turning this separating plane normal to match the face normal meaning that no collision resolution impulse is introduced and therefore no bump. This usually works fine in the connected mesh case but there are other scenarios that can't be solved in this way, that there isn't a general solution for all situations, just trade offs for your own situtation.
  7. Focus_JZ


    Nov 19, 2018
    Thank you very much for the detailed explanation, this atleast gives me more clarity about why the bumps happen!. Hope you guys are also able to find (or already have found) the fix for the different simulation results!
    Last edited: Jun 10, 2021