Search Unity

Easiest Way to get hit Entity from Raycast Hit ColliderKey on CompoundCollider?

Discussion in 'Physics for ECS' started by florianhanke, Feb 16, 2020.

  1. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    I have a GO structure as described in https://docs.unity3d.com/Packages/com.unity.physics@0.2/manual/getting_started.html#compound-shapes:
    The root GO has a Physics Body component, and its children have Physics Shape components. At runtime, this is transformed into a single compound PhysicsCollider on the root entity.

    Code (CSharp):
    1. //– [] Root Entity (Physics Body)
    2. //    |
    3. //   [] Child Entity 1 (Physics Shape)
    4. //    |
    5. //   [] Child Entity 2 (Physics Shape)
    In a system, I query via raycasts, and from the hit result(s) I get the following data:

    Code (CSharp):
    1. var rigidBody = collisionWorld.Bodies[hit.RigidBodyIndex];
    2. var hitEntity = rigidBody.Entity;
    3. var hitColliderKey = hit.ColliderKey.Value;
    The
    hitEntity
    is always the root entity. The
    hitColliderKey
    depends on the collider that is hit as far as I can see.

    How do I easiest get the child entity that is hit from the
    hitColliderKey
    ?

    I thought about creating a mapping (key -> entity), but there must be an easier way to get the entity where the Physics Shape was attached to…? I just cannot find anything in the API or the docs, but am probably looking in the wrong places.

    Many thanks for any help in advance!

    P.S: In my case, the children are various parts (turret entity, chassis entity, tracks entities, ...) on a tank and based on the part that is hit I need to get its material so I can calculate the damage to that part.
     
    Last edited: Feb 16, 2020
    Mikael-H likes this.
  2. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    I've arrived at this point (which gets me nowhere):

    Code (CSharp):
    1. var rigidBody = collisionWorld.Bodies[hit.RigidBodyIndex];
    2. var hitEntity = rigidBody.Entity;
    3. var hitColliderKey = hit.ColliderKey;
    4.  
    5. if (EntityManager.HasComponent<PhysicsCollider>(hitEntity))
    6. {
    7.     var physicsCollider = EntityManager.GetComponentData<PhysicsCollider>(hitEntity);
    8.     var collider = physicsCollider.Value.Value;
    9.     if (collider.CollisionType == CollisionType.Composite)
    10.     {
    11.         if (collider.GetLeaf(hitColliderKey, out ChildCollider child))
    12.         {
    13.             Debug.Log(child);
    14.         }
    15.         if (collider.GetChild(ref hitColliderKey, out ChildCollider childCollider))
    16.         {
    17.             Debug.Log(childCollider);
    18.         }
    19.     }
    20. }
    But it feels as if there should be an easier way…

    P.S: After reading how child colliders are combined in one
    CompoundCollider
    in
    BuildCompoundCollidersConversionSystem#OnUpdate
    , I'm having serious doubts whether it's even possible to figure out the original collider from the
    ColliderKey
    . I'd be very happy if somebody from Unity Physics could point me in the right direction, if there is one.

    P.P.S: With this bit of unsafe code, I can get the collider, at least:

    Code (CSharp):
    1. // Extract collider data.
    2. var rigidBody = collisionWorld.Bodies[hit.RigidBodyIndex];
    3. var hitEntity = rigidBody.Entity;
    4. var hitColliderKey = hit.ColliderKey;
    5.  
    6. unsafe
    7. {
    8.     Collider.GetLeafCollider(rigidBody.Collider, rigidBody.WorldFromBody, hitColliderKey,
    9.         out ChildCollider leaf);
    10.     Debug.Log(leaf);
    11. }
    P.P.P.S: The field
    m_AllLeafCollidersByBody
    in
    BuildCompoundCollidersConversionSystem
    contains an Entity -> ColliderInstance mapping, but it's hard to access. Giving up for the moment.
     
    Last edited: Feb 17, 2020
  3. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Mapping back to an entity requires knowing how you built it in the first place. Code doing casting having to reason about that, that's really a leaky abstraction of the worst kind. Creating explicit mappings is the correct approach IMO.
     
    MNNoxMortem likes this.
  4. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    This is sadly not helpful, since it's not my code alone, but I'm trying to figure out the intentions of Unity's Physics team when using ray casts on compound collider.

    My question was what the easiest way was to know which entity of a hierarchical structure was hit, when looking at the raycast hit. The hit contains the ColliderKey which is exposed, so my assumption is that I can use it somehow to figure out the hit entity. Since I can get the ChildCollider, and they themselves cache an Entity -> ColliderInstance mapping, I believe I can somehow do it. But of course, I'd like the easiest – Unity Physics Team approved – way, especially since it is a preview package, and I don't like to use their internals and reflection if possible.
     
  5. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    Feeding in from a different thread.

    The following is sub-optimal but it did the job in my test. Its pretty brute force and ugly. There is also the caveat that it assumes the children are not sitting on exactly the same position as each other.

    On the Entity hit by a raycast, if it is a parent then it should have a DynamicBuffer<Child>.
    If the Collider of the Body hit by the raycast is of ColliderType.Compound, then you can loop through the child entities and check the Translation component value against the leaf.TransformFromChild.pos. Unless two child entities sit right on top of each other you'll find the child entity when the translations match.
    upload_2020-4-27_18-31-29.png

    The body in the screenshot is a compound. The green line is the ray, the white is the normal and the red cross it the pivot point of the child Entity.

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Mathematics;
    3. using Unity.Physics;
    4. using Unity.Physics.Systems;
    5. using Unity.Transforms;
    6. using UnityEngine;
    7. using RaycastHit = Unity.Physics.RaycastHit;
    8.  
    9. [GenerateAuthoringComponent]
    10. public struct HitChildCollider : IComponentData { }
    11.  
    12.  
    13. [UpdateAfter(typeof(BuildPhysicsWorld))]
    14. public class HitChildColliderSystem : SystemBase
    15. {
    16.     BuildPhysicsWorld m_BuildPhysicsWorld;
    17.  
    18.     protected override void OnCreate()
    19.     {
    20.         m_BuildPhysicsWorld = World.GetExistingSystem<BuildPhysicsWorld>();
    21.     }
    22.  
    23.     protected override void OnUpdate()
    24.     {
    25.         var physicsWorld = m_BuildPhysicsWorld.PhysicsWorld;
    26.         var raycastHit = new RaycastHit();
    27.  
    28.         Entities
    29.         .WithBurst()
    30.         .WithName("HitChildCollider")
    31.         .ForEach((ref Translation position, in Rotation rotation, in HitChildCollider hcc, in PhysicsCollider pc) =>
    32.         {
    33.             var input = new RaycastInput
    34.             {
    35.                 Start = float3.zero,
    36.                 End = position.Value,
    37.                 Filter = pc.Value.Value.Filter,
    38.             };
    39.             if (physicsWorld.CastRay(input, out raycastHit))
    40.             {
    41.                 int i = 0;
    42.                 i++;
    43.             }
    44.         }).Run();
    45.  
    46.         if (raycastHit.Entity == Entity.Null) return;
    47.  
    48.         Debug.DrawLine(float3.zero, raycastHit.Position, Color.green);
    49.         Debug.DrawRay(raycastHit.Position, raycastHit.SurfaceNormal, Color.white);
    50.  
    51.         var collider = physicsWorld.Bodies[raycastHit.RigidBodyIndex].Collider;
    52.         if (collider.Value.Type == ColliderType.Compound)
    53.         {
    54.             collider.Value.GetLeaf(raycastHit.ColliderKey, out ChildCollider leaf);
    55.  
    56.             var translations = GetComponentDataFromEntity<Translation>(true);
    57.             var rotations = GetComponentDataFromEntity<Rotation>(true);
    58.             var childEntities = GetBufferFromEntity<Child>(true);
    59.  
    60.             var childEntity = Entity.Null;
    61.             if (childEntities.Exists(raycastHit.Entity))
    62.             {
    63.                 var entityChildren = childEntities[raycastHit.Entity];
    64.                 for (int i = 0; i < entityChildren.Length; i++)
    65.                 {
    66.                     var translation = translations[entityChildren[i].Value].Value;
    67.                     if (translation.Equals(leaf.TransformFromChild.pos))
    68.                     {
    69.                         childEntity = entityChildren[i].Value;
    70.                         break;
    71.                     }
    72.                 }
    73.             }
    74.             var position = translations[raycastHit.Entity].Value;
    75.             if (!childEntity.Equals(Entity.Null))
    76.             {
    77.                 position += math.rotate(rotations[raycastHit.Entity].Value, translations[childEntity].Value);
    78.             }
    79.  
    80.             // Draw hit entities pivot point
    81.             {
    82.                 var size = 0.1f;
    83.                 var color = Color.red;
    84.                 var xline = new float3(size, 0, 0);
    85.                 var yline = new float3(0, size, 0);
    86.                 var zline = new float3(0, 0, size);
    87.                 Debug.DrawLine(position - xline, position + xline, color);
    88.                 Debug.DrawLine(position - yline, position + yline, color);
    89.                 Debug.DrawLine(position - zline, position + zline, color);
    90.             }
    91.         }
    92.     }
    93. }
    94.  

    Generally, the philosophy has been to add core functionality to the Unity Physics package and don't get in the way of folk needing to do their own thing. Extra mappings between child entities and colliders could be good in one case, and a waste of memory in another. We're totally open to hearing how the community would like to push this sort of use case forward though.

    The specific tank use-case is interesting. You are adding a bunch of detail into the physical representation of the tank that may or may not be necessary for the simulation (though necessary for the damage calculation). Sometimes, something like a tank can be made up from a single convex hull (maybe 2 if the turret is separate, or 4 if you need tracks separate). The simulation of the dynamic tank rigid body can then be pretty cheap, and easier to manipulate with physical impulses, and less likely to get stuck on detailed geometry.
    A separate more detailed, static tank collider can also be setup that is not necessarily even part of the simulation. If a raycast hits the simple tank representation during simulation, then the raycast can be transformed into the space of the high rez version of the tank and cast against it to get any more detailed hit information only when necessary.
     
    florianhanke likes this.
  6. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    Thanks so much for your answer! I'll have to try to rewrite tomorrow – also, your hint about having a separate high-res structure is very interesting, and I probably could use one just per tank type, and instantiate armor parts falling off when needed. Then, each instance of tank could refer back to that high-res structure for damage calculation, as you write.
     
    steveeHavok likes this.
  7. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    P.S: A mapping child collider key -> child entity could be helpful, but I could not find an obvious spot in the compound collider conversion process to hook that in.
     
    steveeHavok likes this.
  8. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    @steveeHavok Thanks again for your solution – works fine, but I'm worrying a bit about performance since I'll be having quite a few bullets flying around. Some feedback…

    I have two use cases:
    1. Get the (child) entity that was hit.
    2. Get the (child) collider that was hit (and its belongsTo).

    So, some feedback and wishes:

    1. Allow for hit.entity, returning the child entity if hit on a compound collider & hit.rootEntity (formerly hit.entity).
    2. Allow to easily get a Collider instead of a ChildCollider. Currently what I am doing is (to see what the collider belongs to):
    Code (CSharp):
    1. unsafe {
    2.     collider.Value.GetLeaf(hit.ColliderKey, out ChildCollider leaf);
    3.     var leafCollider = *leaf.Collider;
    4.     var belongsTo = leafCollider.Filter.BelongsTo;
    5.     // …
    6. }
    Which of course is not great.
    The above 1. and 2. would be excellent, but if that's too much, it would be great to have:
    3. Callbacks that I could provide so that when the compound collider is created I could store mappings: ColliderKey -> child Entity and ColliderKey -> BelongsTo.
    I'm very happy to do my own thing, but I'd also be happy if the Physics library made that as easy as possible to do :)
     
    Last edited: May 12, 2020
  9. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    While this works often, I found that the translation is sometimes off by ~1/10000th of a unit, which makes the algorithm fail:
    Screenshot 2020-05-22 at 14.03.28.png
    Screenshot 2020-05-22 at 13.59.09.png
    I'm running my collision system after the BuildPhysics system finishes and before the StepPhysics system. When testing, the object with the compound collider constantly had a minimal amount of PhysicsVelocity, which could cause the small differences in values?

    On the other hand, I assumed that the translations and leaf.TransformFromChild.pos-s would not change when they were in a fixed parent-child structure, and would stay in sync if connected to the main body via a hinge, for example. Am I completely wrong?

    P.S: It's possible it hit one of the rear wheels which are not connected via hinge – I'll have to recheck when I can if the hinge-connected wheels are even checked via this algorithm.
     
    Last edited: May 25, 2020
    LudiKha and cultureulterior like this.