Search Unity

[Unity.Physics.MeshCollider.Create] Slow : More parameters to disable internal checks

Discussion in 'DOTS Physics' started by dyox, Nov 12, 2019.

  1. dyox

    dyox

    Joined:
    Aug 19, 2011
    Posts:
    598
    Hello,

    Actually the use of Unity.Physics.MeshCollider.Create to create/modify procedural meshes in realtime is slow, even with Burst, due to many checks inside :
    -indices referencing outside vertex array
    -MeshConnectivityBuilder.WeldVertices
    - Build bounding volume hierarchy
    -etc

    Is it possible to provide a function with more parameters to disable all this checks, if users have already made them on other systems/jobs before, this may increase speed.

    Also it could be interesting to have access to MeshConnectivityBuilder ,MeshBuilder to build/provide mesh directly to MeshCollider or at least decompose the creation process into multiple functions to dispatch over frames. (BoundingVolumeHierarchy,TempSection,AABB).

    Thanks.
     
    Last edited: Nov 12, 2019
  2. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    480
    I've logged this request. How comfortable are you (and folk in general reading this) with making Unity Physics a local package, trying to do this yourself and sharing your implementation here?
     
  3. dyox

    dyox

    Joined:
    Aug 19, 2011
    Posts:
    598
    Yes.

    This is my Implementation of Physics_MeshCollider.cs with CookingOptions parameters :

    Code (CSharp):
    1. using System;
    2. using System;
    3. using System.ComponentModel;
    4. using Unity.Collections;
    5. using Unity.Collections.LowLevel.Unsafe;
    6. using Unity.Mathematics;
    7. using Unity.Entities;
    8.  
    9. namespace Unity.Physics
    10. {
    11.     // A collider representing a mesh comprised of triangles and quads.
    12.     // Warning: This is just the header, it is followed by variable sized data in memory.
    13.     // Therefore this struct must always be passed by reference, never by value.
    14.     public struct MeshCollider : ICompositeCollider
    15.     {
    16.         public enum MeshCookingOptions
    17.         {
    18.             None = 0,
    19.             EnableMeshCleaning = 1,
    20.             WeldColocatedVertices = 2,
    21.         }
    22.      
    23.         ColliderHeader m_Header;
    24.         Aabb m_Aabb;
    25.         internal Mesh Mesh;
    26.  
    27.         // followed by variable sized mesh data
    28.  
    29.         #region Construction
    30.  
    31.         // Create a mesh collider asset from a set of triangles
    32.         public static BlobAssetReference<Collider> Create(NativeArray<float3> vertices, NativeArray<int> indices, MeshCookingOptions cookingOptions = MeshCookingOptions.EnableMeshCleaning | MeshCookingOptions.WeldColocatedVertices) =>
    33.             Create(vertices, indices, CollisionFilter.Default, Material.Default, cookingOptions);
    34.  
    35.         public static BlobAssetReference<Collider> Create(NativeArray<float3> vertices, NativeArray<int> indices, CollisionFilter filter, MeshCookingOptions cookingOptions = MeshCookingOptions.EnableMeshCleaning | MeshCookingOptions.WeldColocatedVertices) =>
    36.             Create(vertices, indices, filter, Material.Default, cookingOptions);
    37.  
    38.         public static unsafe BlobAssetReference<Collider> Create(NativeArray<float3> vertices, NativeArray<int> indices, CollisionFilter filter, Material material, MeshCookingOptions cookingOptions = MeshCookingOptions.EnableMeshCleaning | MeshCookingOptions.WeldColocatedVertices)
    39.         {
    40.             int numIndices = indices.Length;
    41.             int numTriangles = numIndices / 3;
    42.  
    43.             // Copy vertices
    44.             var tempVertices = vertices;
    45.  
    46.             // Copy indices
    47.             NativeArray<int> tempIndices;
    48.  
    49.             if ((cookingOptions & MeshCookingOptions.EnableMeshCleaning) == MeshCookingOptions.EnableMeshCleaning)
    50.             {
    51.                 tempIndices = new NativeArray<int>(numIndices, Allocator.Temp);
    52.              
    53.                 for (int iTriangle = 0; iTriangle < numTriangles; iTriangle++)
    54.                 {
    55.                     int iIndex0 = iTriangle * 3;
    56.                     int iIndex1 = iIndex0 + 1;
    57.                     int iIndex2 = iIndex0 + 2;
    58.  
    59.                     if (indices[iIndex0] >= 0 && indices[iIndex0] < vertices.Length
    60.                         && indices[iIndex1] >= 0 && indices[iIndex1] < vertices.Length
    61.                         && indices[iIndex2] >= 0 && indices[iIndex2] < vertices.Length)
    62.                     {
    63.                         tempIndices[iIndex0] = indices[iIndex0];
    64.                         tempIndices[iIndex1] = indices[iIndex1];
    65.                         tempIndices[iIndex2] = indices[iIndex2];
    66.                     }
    67.                     else
    68.                     {
    69.                         throw new ArgumentException("Tried to create a MeshCollider with indices referencing outside vertex array");
    70.                     }
    71.                 }
    72.             }
    73.             else
    74.             {
    75.                 tempIndices = new NativeArray<int>(indices, Allocator.Temp);
    76.             }
    77.  
    78.             // Build connectivity and primitives
    79.  
    80.             NativeList<float3> uniqueVertices;
    81.  
    82.             if ((cookingOptions & MeshCookingOptions.WeldColocatedVertices) == MeshCookingOptions.WeldColocatedVertices)
    83.             {
    84.                 uniqueVertices = MeshConnectivityBuilder.WeldVertices(tempIndices, tempVertices);
    85.             }
    86.             else
    87.             {
    88.                 uniqueVertices = new NativeList<float3>(tempVertices.Length, Allocator.Temp);
    89.                 uniqueVertices.AddRange(tempVertices);
    90.             }
    91.  
    92.  
    93.             var connectivity = new MeshConnectivityBuilder(tempIndices, uniqueVertices);
    94.             NativeList<MeshConnectivityBuilder.Primitive> primitives = connectivity.EnumerateQuadDominantGeometry(tempIndices, uniqueVertices);
    95.  
    96.             int primitivesLength = primitives.Length;
    97.  
    98.             // Build bounding volume hierarchy
    99.             int nodeCount = math.max(primitivesLength * 2 + 1, 2); // We need at least two nodes - an "invalid" node and a root node.
    100.             var nodes = new NativeArray<BoundingVolumeHierarchy.Node>(nodeCount, Allocator.Temp);
    101.             int numNodes = 0;
    102.  
    103.             {
    104.                 // Prepare data for BVH
    105.                 var points = new NativeList<BoundingVolumeHierarchy.PointAndIndex>(primitivesLength, Allocator.Temp);
    106.                 var aabbs = new NativeArray<Aabb>(primitivesLength, Allocator.Temp);
    107.  
    108.                 for (int i = 0; i < primitives.Length; i++)
    109.                 {
    110.                     MeshConnectivityBuilder.Primitive p = primitives[i];
    111.  
    112.                     // Skip degenerate triangles
    113.                     if (MeshConnectivityBuilder.IsTriangleDegenerate(p.Vertices[0], p.Vertices[1], p.Vertices[2]))
    114.                     {
    115.                         continue;
    116.                     }
    117.  
    118.                     aabbs[i] = Aabb.CreateFromPoints(p.Vertices);
    119.                     points.Add(new BoundingVolumeHierarchy.PointAndIndex
    120.                     {
    121.                         Position = aabbs[i].Center,
    122.                         Index = i
    123.                     });
    124.                 }
    125.  
    126.                 var bvh = new BoundingVolumeHierarchy(nodes);
    127.  
    128.                 bvh.Build(points.AsArray(), aabbs, out numNodes, useSah: true);
    129.             }
    130.  
    131.             // Build mesh sections
    132.             BoundingVolumeHierarchy.Node* nodesPtr = (BoundingVolumeHierarchy.Node*)nodes.GetUnsafePtr();
    133.             MeshBuilder.TempSection sections = MeshBuilder.BuildSections(nodesPtr, numNodes, primitives);
    134.  
    135.             // Allocate collider
    136.             int meshDataSize = Mesh.CalculateMeshDataSize(numNodes, sections.Ranges);
    137.             int totalColliderSize = Math.NextMultipleOf(sizeof(MeshCollider), 16) + meshDataSize;
    138.          
    139.             MeshCollider* meshCollider = (MeshCollider*)UnsafeUtility.Malloc(totalColliderSize, 16, Allocator.Temp);
    140.  
    141.             // Initialize it
    142.             {
    143.                 UnsafeUtility.MemClear(meshCollider, totalColliderSize);
    144.                 meshCollider->MemorySize = totalColliderSize;
    145.  
    146.                 meshCollider->m_Header.Type = ColliderType.Mesh;
    147.                 meshCollider->m_Header.CollisionType = CollisionType.Composite;
    148.                 meshCollider->m_Header.Version += 1;
    149.                 meshCollider->m_Header.Magic = 0xff;
    150.  
    151.                 ref var mesh = ref meshCollider->Mesh;
    152.  
    153.                 mesh.Init(nodesPtr, numNodes, sections, filter, material);
    154.  
    155.                 // Calculate combined filter
    156.                 meshCollider->m_Header.Filter = mesh.Sections.Length > 0 ? mesh.Sections[0].Filters[0] : CollisionFilter.Default;
    157.                 for (int i = 0; i < mesh.Sections.Length; ++i)
    158.                 {
    159.                     for (var j = 0; j < mesh.Sections[i].Filters.Length; ++j)
    160.                     {
    161.                         var f = mesh.Sections[i].Filters[j];
    162.                         meshCollider->m_Header.Filter = CollisionFilter.CreateUnion(meshCollider->m_Header.Filter, f);
    163.                     }
    164.                 }
    165.  
    166.                 meshCollider->m_Aabb = meshCollider->Mesh.BoundingVolumeHierarchy.Domain;
    167.                 meshCollider->NumColliderKeyBits = meshCollider->Mesh.NumColliderKeyBits;
    168.             }
    169.  
    170.             // Copy collider into blob
    171.             var blob = BlobAssetReference<Collider>.Create(meshCollider, totalColliderSize);
    172.             UnsafeUtility.Free(meshCollider, Allocator.Temp);
    173.             return blob;
    174.         }
    175.  
    176.         #endregion
    177.  
    178.         #region ICompositeCollider
    179.  
    180.         public ColliderType Type => m_Header.Type;
    181.         public CollisionType CollisionType => m_Header.CollisionType;
    182.         public int MemorySize { get; private set; }
    183.  
    184.         public CollisionFilter Filter => m_Header.Filter;
    185.  
    186.         public MassProperties MassProperties
    187.         {
    188.             get
    189.             {
    190.                 // Rough approximation based on AABB
    191.                 float3 size = m_Aabb.Extents;
    192.                 return new MassProperties
    193.                 {
    194.                     MassDistribution = new MassDistribution
    195.                     {
    196.                         Transform = new RigidTransform(quaternion.identity, m_Aabb.Center),
    197.                         InertiaTensor = new float3(
    198.                             (size.y * size.y + size.z * size.z) / 12.0f,
    199.                             (size.x * size.x + size.z * size.z) / 12.0f,
    200.                             (size.x * size.x + size.y * size.y) / 12.0f)
    201.                     },
    202.                     Volume = 0,
    203.                     AngularExpansionFactor = math.length(m_Aabb.Extents) * 0.5f
    204.                 };
    205.             }
    206.         }
    207.  
    208.         public Aabb CalculateAabb()
    209.         {
    210.             return m_Aabb;
    211.         }
    212.  
    213.         public Aabb CalculateAabb(RigidTransform transform)
    214.         {
    215.             // TODO: Store a convex hull wrapping the mesh, and use that to calculate tighter AABBs?
    216.             return Math.TransformAabb(transform, m_Aabb);
    217.         }
    218.  
    219.         // Cast a ray against this collider.
    220.         public bool CastRay(RaycastInput input) => QueryWrappers.RayCast(ref this, input);
    221.         public bool CastRay(RaycastInput input, out RaycastHit closestHit) => QueryWrappers.RayCast(ref this, input, out closestHit);
    222.         public bool CastRay(RaycastInput input, ref NativeList<RaycastHit> allHits) => QueryWrappers.RayCast(ref this, input, ref allHits);
    223.         public unsafe bool CastRay<T>(RaycastInput input, ref T collector) where T : struct, ICollector<RaycastHit>
    224.         {
    225.             fixed (MeshCollider* target = &this)
    226.             {
    227.                 return RaycastQueries.RayCollider(input, (Collider*)target, ref collector);
    228.             }
    229.         }
    230.  
    231.         // Cast another collider against this one.
    232.         public bool CastCollider(ColliderCastInput input) => QueryWrappers.ColliderCast(ref this, input);
    233.         public bool CastCollider(ColliderCastInput input, out ColliderCastHit closestHit) => QueryWrappers.ColliderCast(ref this, input, out closestHit);
    234.         public bool CastCollider(ColliderCastInput input, ref NativeList<ColliderCastHit> allHits) => QueryWrappers.ColliderCast(ref this, input, ref allHits);
    235.         public unsafe bool CastCollider<T>(ColliderCastInput input, ref T collector) where T : struct, ICollector<ColliderCastHit>
    236.         {
    237.             fixed (MeshCollider* target = &this)
    238.             {
    239.                 return ColliderCastQueries.ColliderCollider(input, (Collider*)target, ref collector);
    240.             }
    241.         }
    242.  
    243.         // Calculate the distance from a point to this collider.
    244.         public bool CalculateDistance(PointDistanceInput input) => QueryWrappers.CalculateDistance(ref this, input);
    245.         public bool CalculateDistance(PointDistanceInput input, out DistanceHit closestHit) => QueryWrappers.CalculateDistance(ref this, input, out closestHit);
    246.         public bool CalculateDistance(PointDistanceInput input, ref NativeList<DistanceHit> allHits) => QueryWrappers.CalculateDistance(ref this, input, ref allHits);
    247.         public unsafe bool CalculateDistance<T>(PointDistanceInput input, ref T collector) where T : struct, ICollector<DistanceHit>
    248.         {
    249.             fixed (MeshCollider* target = &this)
    250.             {
    251.                 return DistanceQueries.PointCollider(input, (Collider*)target, ref collector);
    252.             }
    253.         }
    254.  
    255.         // Calculate the distance from another collider to this one.
    256.         public bool CalculateDistance(ColliderDistanceInput input) => QueryWrappers.CalculateDistance(ref this, input);
    257.         public bool CalculateDistance(ColliderDistanceInput input, out DistanceHit closestHit) => QueryWrappers.CalculateDistance(ref this, input, out closestHit);
    258.         public bool CalculateDistance(ColliderDistanceInput input, ref NativeList<DistanceHit> allHits) => QueryWrappers.CalculateDistance(ref this, input, ref allHits);
    259.         public unsafe bool CalculateDistance<T>(ColliderDistanceInput input, ref T collector) where T : struct, ICollector<DistanceHit>
    260.         {
    261.             fixed (MeshCollider* target = &this)
    262.             {
    263.                 return DistanceQueries.ColliderCollider(input, (Collider*)target, ref collector);
    264.             }
    265.         }
    266.  
    267.         public uint NumColliderKeyBits { get; private set; }
    268.  
    269.         public bool GetChild(ref ColliderKey key, out ChildCollider child)
    270.         {
    271.             if (key.PopSubKey(NumColliderKeyBits, out uint subKey))
    272.             {
    273.                 int primitiveKey = (int)(subKey >> 1);
    274.                 int polygonIndex = (int)(subKey & 1);
    275.  
    276.                 Mesh.GetPrimitive(primitiveKey, out float3x4 vertices, out Mesh.PrimitiveFlags flags, out CollisionFilter filter, out Material material);
    277.  
    278.                 if (Mesh.IsPrimitveFlagSet(flags, Mesh.PrimitiveFlags.IsQuad))
    279.                 {
    280.                     child = new ChildCollider(vertices[0], vertices[1], vertices[2], vertices[3], filter, material);
    281.                 }
    282.                 else
    283.                 {
    284.                     child = new ChildCollider(vertices[0], vertices[1 + polygonIndex], vertices[2 + polygonIndex], filter, material);
    285.                 }
    286.  
    287.                 return true;
    288.             }
    289.  
    290.             child = new ChildCollider();
    291.             return false;
    292.         }
    293.  
    294.         public bool GetLeaf(ColliderKey key, out ChildCollider leaf)
    295.         {
    296.             return GetChild(ref key, out leaf);
    297.         }
    298.  
    299.         public unsafe void GetLeaves<T>(ref T collector) where T : struct, ILeafColliderCollector
    300.         {
    301.             var polygon = new PolygonCollider();
    302.             polygon.InitEmpty();
    303.             if (Mesh.GetFirstPolygon(out uint meshKey, ref polygon))
    304.             {
    305.                 do
    306.                 {
    307.                     var leaf = new ChildCollider((Collider*)&polygon, RigidTransform.identity);
    308.                     collector.AddLeaf(new ColliderKey(NumColliderKeyBits, meshKey), ref leaf);
    309.                 }
    310.                 while (Mesh.GetNextPolygon(meshKey, out meshKey, ref polygon));
    311.             }
    312.         }
    313.  
    314.         #endregion
    315.  
    316.         #region Obsolete
    317.         [EditorBrowsable(EditorBrowsableState.Never)]
    318.         [Obsolete("This signature has been deprecated. Use a signature passing native containers instead. (RemovedAfter 2019-10-25)")]
    319.         public static unsafe BlobAssetReference<Collider> Create(float3[] vertices, int[] indices, CollisionFilter? filter = null, Material? material = null)
    320.         {
    321.             var v = new NativeArray<float3>(vertices, Allocator.Temp);
    322.             var i = new NativeArray<int>(indices, Allocator.Temp);
    323.             return Create(v, i, filter ?? CollisionFilter.Default, material ?? Material.Default);
    324.         }
    325.         #endregion
    326.     }
    327. }
    328.  
    I've added MeshCookingOptions with Default to EnableMeshCleaning | WeldColocatedVertices .
    Also avoided 2 Unnecessary Temp Copy of Vertices And Indices.

    Code (csharp):
    1.  
    2. public enum MeshCookingOptions
    3.         {
    4.             None = 0,
    5.             EnableMeshCleaning = 1,
    6.             WeldColocatedVertices = 2,
    7.         }
    8.  
    Performances :

    Default : 10-13 ms
    MeshColliderDefault.png
    MeshColliderAfter.png


    Optimized : 2-5ms
    MeshColliderOptimization.png
    MeshColliderBefore.png


    Result :


    I will post other possible changes if there are any
     
    Last edited: Nov 13, 2019
  4. dyox

    dyox

    Joined:
    Aug 19, 2011
    Posts:
    598
  5. dyox

    dyox

    Joined:
    Aug 19, 2011
    Posts:
    598
    UnityPhysics need some cache system, colliders are almost all the same at (+/- 0.01f scale..).
    Baked Colliders is the main problem actually, but for vegetation, i've something interesting in work.
     
  6. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,311
    You can use the binary serialization from the entities package to bake stuff at design time. Raw pointer data that's fast to load no collider creation per say necessary. Combine that with the batch api's for actually creating the entities and it's all extremely fast.


    Edit:

    For runtime updates you can just reserialize the new collider. So next time the player loads the same scene/area you don't have to do that work over again, just stream it right in.
     
    Last edited: Nov 19, 2019
  7. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,311
    Also you can reuse the same collider between a lot of entities if it's the same size.
     
    dyox likes this.
  8. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    101
    Any news/update on this? Can we expect cooking options and more optimized creation to show up in the official package, or should we look into customized solutions? Thank you.
     
  9. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    Hello i am generating terrain chunks procedural and as such also mesh colliders. The game freezes up when the mesh colliders are being generated. So possibly because of all of the internal checks? Hope to hear a solution. Thanks!
     
  10. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    It usually freezes because of synchronous Burst compilation on mesh collider creation, that part is not due to the performance of the internal checks.
     
  11. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    344
    Is there a way to do this asynchronously?
     
  12. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    At the moment, not if you don't want to change the physics package. You can go to Edit/Project Settings, and in the editor tab scroll all the way down to Enter Play Mode Settings and enable the experimental feature. That should cache Burst compilation results so you would get the synchronous compilation only once if nothing has changed in the meantime.
     
    vildauget likes this.
  13. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    344
    Nah, I mean can you call some equivalent of `MeshCollider.Create` from a job/worker thread?
     
  14. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    Oh, of course, if you have points and triangles buffer you can create it from your own job for sure.
     
unityunity