Search Unity

Resolved (Case 1287877) Raycasts not hitting anything - UnityEngine.Physics.Raycast does work!

Discussion in 'Physics for ECS' started by MNNoxMortem, Oct 25, 2020.

  1. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Edit: This thread describes a different problem than it's sibling, https://forum.unity.com/threads/ray...dphysicsworld-finaljobhandle-complete.897467/ but if you run into one of the two, you should also read the other, as they both describe easy-to-run into scenarios with Dots, Physics and procedural generated colliders and their race conditions.

    upload_2020-10-25_19-4-13.png
    The two raycasts orginate at the camera along the teal lines, butt they do not hit the grey boxes (which are the Physics Collider Boxes).

    I create the MeshColliders with
    Code (CSharp):
    1. BlobAssetReference<Collider> debugCreatedMc = MeshCollider.Create(naPositions, naIndices);
    and raycast against them with a RaycastSystem via collissionWorld.CastRay
    Code (CSharp):
    1. using Domain.Components;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Physics;
    5. using Unity.Physics.Systems;
    6.  
    7. namespace Domain.Systems
    8. {
    9.     // Always use SystemBase: https://forum.unity.com/threads/is-there-any-difference-between-systembase-jobcomponentsystem-and-componentsystem-anymore.892978/
    10.     [UpdateAfter(typeof(BuildPhysicsWorld))]
    11.     public class RaycastSystem : SystemBase
    12.     {
    13.         private BuildPhysicsWorld     buildPhysicsWorld;
    14.         private EndFramePhysicsSystem endFramePhysicsSystem;
    15.  
    16.         protected override void OnCreate()
    17.         {
    18.             buildPhysicsWorld     = World.GetOrCreateSystem<BuildPhysicsWorld>();
    19.             endFramePhysicsSystem = World.GetOrCreateSystem<EndFramePhysicsSystem>();
    20.         }
    21.  
    22.         protected override void OnUpdate()
    23.         {
    24.             CollisionWorld collisionWorld = buildPhysicsWorld.PhysicsWorld.CollisionWorld;
    25.             Dependency = JobHandle.CombineDependencies(Dependency, endFramePhysicsSystem.GetOutputDependency());
    26.          
    27.             Dependency = Entities.WithName(nameof(RaycastSystem))
    28.              .ForEach((ref RaycastRequest request) =>
    29.                 {
    30.                     if (request.Completed)
    31.                         return;
    32.                     if (!request.ConsideredBeforeBuildPhysicsWorld)
    33.                         return;
    34.  
    35.                     request.Hit           = collisionWorld.CastRay(request.RaycastInput, out RaycastHit closestHit);
    36.                     request.RaycastResult = closestHit;
    37.                     request.Completed     = true;
    38.                 })
    39.              .Schedule(Dependency);
    40.             Dependency.Complete(); // <- Require or will fail
    41.         }
    42.     }
    43. }
     
    Last edited: Nov 8, 2020
  2. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    You are getting a collision world from the buildPhysicsWorld system immediately OnUpdate(), where you don't have the guarantee that the collision world has been built. You need a dependency to the end of the buildPhysicsWorld, not the endFramePhysicsSystem.

    Not sure that's the only issue with this, but certainly is one. BuildPhysicsWorld is the one preparing the hierarchy for you to be able to perform ray casts.
     
    MNNoxMortem likes this.
  3. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    How do I actually do this? I may not use the buildPhysicsWorld within the lambda (does not compile) and just adding th dependency to the buildPhysicsWorld.GetOutputDependency() does to my understanding only change when the system is run, but not when the CollisionWorld is captured, isn't it like that?

    Code (CSharp):
    1. namespace Domain.Systems
    2. {
    3.     // Always use SystemBase: https://forum.unity.com/threads/is-there-any-difference-between-systembase-jobcomponentsystem-and-componentsystem-anymore.892978/
    4.     [UpdateAfter(typeof(BuildPhysicsWorld))]
    5.     public class RaycastSystem : SystemBase
    6.     {
    7.         private BuildPhysicsWorld     buildPhysicsWorld;
    8.        
    9.         protected override void OnCreate()
    10.         {
    11.             buildPhysicsWorld     = World.GetOrCreateSystem<BuildPhysicsWorld>();
    12.         }
    13.  
    14.         protected override void OnUpdate()
    15.         {
    16.            
    17.             Dependency = JobHandle.CombineDependencies(Dependency, buildPhysicsWorld.GetOutputDependency());
    18.             // ref PhysicsWorld physicsWorld = ref  this.buildPhysicsWorld.PhysicsWorld;
    19.             CollisionWorld collisionWorld =  buildPhysicsWorld.PhysicsWorld.CollisionWorld;
    20.            
    21.            
    22.             Dependency = Entities.WithName(nameof(RaycastSystem))
    23.              .WithReadOnly(collisionWorld)
    24.              .ForEach((ref RaycastRequest request) =>
    25.                 {
    26.  
    27.  
    28.                     if (request.Completed)
    29.                         return;
    30.                     if (!request.ConsideredBeforeBuildPhysicsWorld)
    31.                         return;
    32.  
    33.                     request.Hit           = collisionWorld.CastRay(request.RaycastInput, out RaycastHit closestHit);
    34.                     request.RaycastResult = closestHit;
    35.                     request.Completed     = true;
    36.                 })
    37.              .Schedule(Dependency);
    38.             Dependency.Complete(); // <- Require or will fail
    39.         }
    40.     }
    41. }
     
  4. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    Your comment in code says that it works if you put a Dependency.Complete() in there. Is that correct?

    One other thing you need to do for sure is to inform at least one system of this dependency. If you have game logic that should happen before physics, you should inform StepPhysicsWorld. If you want to go any time in the frame, inform the EndFramePhysicsSystem. Just use the AddInputDependency() call. That should help get rid of the Complete() call.

    To answer your question, it's fine to capture it like this, since it's all pointers internally, but if you immediately execute ray cast it will fail because you are not waiting for jobs that actually fill these pointers with useful data for ray casting.
     
    florianhanke likes this.
  5. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    No, but it will crash without it, as the request potentially runs over 4 frames, and then a new physics world seems to be build and Unity will complain with one of those:
    * Allocations older than 4 frames
    * Access to System BuildPhysicsWorld before ... (actually it happens afterward, just in frame N+1, after we started in frame N, but before the BuildPhysicsWorld of frame N+1)

    Thank you very much for the input, I will try it now without schedule but with the AddInputDependency.

    Code (CSharp):
    1.     // Dependency.Complete(); // <- Require or will fail
    2.             endFramePhysicsSystem.AddInputDependency(Dependency);
    3.         }
    Edit.. it crashes, sadly the error message is truncated (both in-ediitor, but also in the Editor.log)
    Code (CSharp):
    1. InvalidOperationException: The previously scheduled job RaycastSystem:<>c__DisplayClass_RaycastSystem reads from the Unity.Collections.NativeArray`1[Unity.Physics.CollisionFilter] <>c__DisplayClass_RaycastSystem.JobData.collisionWorld.Broadphase.m_StaticTree.BodyFilters. You are trying to schedule a new job Broadphase:PrepareStaticBodyDataJob, which writes to the same Unity.Collections.NativeArray`1[Unity.Physics.CollisionFilter] (via PrepareStaticBodyDataJob.FiltersOut). To guarantee safety, you must include RaycastSystem:<>c__Disp
    2.  
    3. InvalidOperationException: Adding/removing components or changing position/rotation/velocity/collider ECS data on dynamic entities during physics step
    4.  
    Also the second error message is super weird. Why does this system modify position, rotation or velocity?! that makes no sense at all, while the first error is pretty much the same as this one, which I got stuck with in June already (never managed to properly solve it...).
     
    Last edited: Oct 26, 2020
  6. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    The first error message is surprising to me, but just in case, could you link to the StepPhysicsWorld instead of EndFramePhysicsSystem, just to see what happens then?

    The second one means that you've written to the component data of an entity involved in the physics step between the BuildPhysicsWorld and ExportPhysicsWorld, and that is not allowed. Even the write to a non-physics component data can result in reorder of entities in chunks, and Build and Export rely on the same order.
     
  7. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Did it. Exact same error.

    Yes, but where do I write when all I do is collisionWorld.Raycast? The only other system I have is one that expliticly marks entities as "this one existed before BuildPhysicsWorld" as otherwise the raycast might run a frame too early

    Code (CSharp):
    1. [UpdateBefore(typeof(BuildPhysicsWorld)), UsedImplicitly]
    2.     public class ColliderConsideredBeforeBuildPhysicsWorld : SystemBase
    3.     {
    4.         protected override void OnUpdate() =>
    5.             Entities.WithName(nameof(ColliderConsideredBeforeBuildPhysicsWorld))
    6.              .WithBurst()
    7.              .ForEach((ref RaycastRequest request) => request.ConsideredBeforeBuildPhysicsWorld = true)
    8.              .ScheduleParallel();
    9.  
    10.         /*BeforeBuildPhysicsWorld = true*/
    11.     }
    I even create a very stupid test raycast now on the main thread, which does also not hit anything

    upload_2020-10-26_16-46-18.png
    Without any system, just to get anything working...

    Code (CSharp):
    1.  
    2.    Button.onClick.AddListener(AddRaycast);
    3. }
    4.  
    5. private void AddRaycast()
    6. {
    7.  
    8.     UnityEngine.Ray ray            = camera.ScreenPointToRay(Input.mousePosition);
    9.     Entity          raycastForward = entityManager.CreateEntity();
    10.     var input = new RaycastInput {
    11.                                      Start = ray.origin,
    12.                                      End   = ray.origin + ray.direction * 1e15f
    13.                                  };
    14.     Debug.DrawLine(input.Start, input.End, Color.cyan, 3f);
    15.     var raycastForwardRequest = new RaycastRequest {
    16.                                                        Completed                         = false,
    17.                                                        Hit                               = false,
    18.                                                        RaycastInput                      = input,
    19.                                                        ConsideredBeforeBuildPhysicsWorld = false,
    20.                                                        RaycastResult                     = default,
    21.                                                        Entity                            =  raycastForward
    22.                                                    };
    23.     raycastRequests.Add(raycastForwardRequest);
    24.     entityManager.AddComponent<RaycastRequest>(raycastForward);
    25.     entityManager.SetComponentData(raycastForward, raycastForwardRequest);
    26.     entityManager.SetName(raycastForward, $"Raycast {input.Start}-{input.End}");
    27.  
    28.     Entity raycastReverse = entityManager.CreateEntity();
    29.     var inputReverse = new RaycastInput {
    30.                                             Start = input.End,
    31.                                             End   = input.Start
    32.                                         };
    33.     Debug.DrawLine(input.End, input.Start, Color.magenta, 4f);
    34.     var raycastReverseRequest = new RaycastRequest {
    35.                                                        Completed                         = false,
    36.                                                        Hit                               = false,
    37.                                                        RaycastInput                      = inputReverse,
    38.                                                        ConsideredBeforeBuildPhysicsWorld = false,
    39.                                                        RaycastResult                     = default,
    40.                                                        Entity                            =  raycastReverse
    41.                                                    };
    42.     raycastRequests.Add(raycastReverseRequest);
    43.     entityManager.AddComponent<RaycastRequest>(raycastReverse);
    44.     entityManager.SetComponentData(raycastReverse, raycastReverseRequest);
    45.        
    46.    
    47.     Entity plane = entityManager.CreateEntity();
    48.     entityManager.SetName(plane, "Plane"+(planeCount++).ToString());
    49.     entityManager.AddComponent<PhysicsCollider>(plane);
    50.     var naPos = new NativeArray<float3>(4, Allocator.Temp);
    51.     naPos[0] = new float3(-1, 0, -1);
    52.     naPos[1] = new float3(1, 0, -1);
    53.     naPos[2] = new float3(-1,  0, 1);
    54.     naPos[3] = new float3(1,  0, 1);
    55.     var naIndices = new NativeArray<int3>(2, Allocator.Temp);
    56.     naIndices[0] = new int3(0,1,2);
    57.     naIndices[1] = new int3(1,2,3);
    58.     BlobAssetReference<Unity.Physics.Collider> planeCollider = Unity.Physics.MeshCollider.Create(naPos, naIndices, CollisionFilter.Default);
    59.     entityManager.SetComponentData(plane,new PhysicsCollider {
    60.                                                                  Value = planeCollider
    61.                                                              });
    62.    
    63.     var go = new GameObject();
    64.     go.name = "WhereIsMyPlane "+planeCount;
    65.     var mesh = new Mesh {
    66.                             vertices  = new [] {
    67.                                                    new Vector3(naPos[0].x,naPos[0].y,naPos[0].z),
    68.                                                    new Vector3(naPos[1].x,naPos[1].y,naPos[1].z),
    69.                                                    new Vector3(naPos[2].x,naPos[2].y,naPos[2].z),
    70.                                                    new Vector3(naPos[3].x,naPos[3].y,naPos[3].z)
    71.                                                },
    72.                             triangles = new[] {0, 2, 1, 2, 3, 1}
    73.                         };
    74.     var mf = go.AddComponent<MeshFilter>();
    75.     mf.mesh = mesh;
    76.     var mr = go.AddComponent<MeshRenderer>();
    77.     mr.material = material;
    78.     var mc = go.AddComponent<MeshCollider>();
    79.     mc.sharedMesh = mesh;
    80.        
    81.     naIndices.Dispose();
    82.     naPos.Dispose();
    83.  
    84.     var physicsWorldSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
    85.     physicsWorldSystem.GetOutputDependency().Complete();
    86.     CollisionWorld collisionWorld = physicsWorldSystem.PhysicsWorld.CollisionWorld;
    87.     Debug.DrawLine(new float3(0, 1, 0), new float3(0, -1, 0), Color.red, 4f);
    88.     if (collisionWorld.CastRay(new RaycastInput {
    89.                                                     Start  = new float3(0, 1, 0),
    90.                                                     End    = new float3(0, -1, 0),
    91.                                                     Filter = CollisionFilter.Default
    92.                                                 }, out Unity.Physics.RaycastHit  closestHit))
    93.         Debug.Log($"plane hit forward (true):{closestHit.Fraction>0}, entity:{closestHit.Entity}, {entityManager.GetName(closestHit.Entity)} position:{closestHit.Position}");
    94.     else
    95.         Debug.Log($"plane hit forward (false):{closestHit.Fraction>0}, entity:{closestHit.Entity}, position:{closestHit.Position}");
    96.     Debug.DrawLine(new float3(0, 1, 0), new float3(0, -1, 0), Color.blue, 4f);
    97.     if (collisionWorld.CastRay(new RaycastInput {
    98.                                                     Start  = new float3(0, -1, 0),
    99.                                                     End    = new float3(0, 1, 0),
    100.                                                     Filter = CollisionFilter.Default
    101.                                                 }, out closestHit))
    102.         Debug.Log($"plane hit backward (true):{closestHit.Fraction>0}, entity:{closestHit.Entity}, {entityManager.GetName(closestHit.Entity)} position:{closestHit.Position}");
    103.     else
    104.         Debug.Log($"plane hit backward (false):{closestHit.Fraction>0}, entity:{closestHit.Entity}, position:{closestHit.Position}");
    105. }
    106.  
    I am in the process of moving this out to an example project that I am going to share, as I am really not understanding how something as stupidly simple as this raycast does not work. Damn, I am even sure it worked at some point (as the problem in the linked bug report in june was that I had to enforce the complete() and not that the raycast did not hit anything...)
     
    Last edited: Oct 26, 2020
  8. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    You are also missing an UpdateBefore(EndFramePhysicsSystem) or whichever you add input dependency to.

    Regarding the write, the simple

    Entities.ForEach((ref RaycastRequest request)

    will mark RaycastRequest component data as written.
     
    MNNoxMortem likes this.
  9. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Extracted the code and added some visualization to show that it is not working:
    • Open SampleScene (there is only one)
    • Enter Play Mode
    • Click Button
    • Check Log
    Expected
    Every click creates a plane, both as GameObject (to show ordering etc, as well as an Entity with a PhysicsCollider) and as pure Entity. ColliderConsideredBeforeBuildPhysicsWorld marks all RaycastRequest as "seen before" UpdatePhysics and the RaycastSystem which then should before the raycast.

    None of the raycasts ever does hit any of the planes, not even those that should now exist for several frames after the first click.

    Is there no working tutorial for Raycasts from systems anywhere available? I have seen example for raycasts from Jobs, via RaycastCommand, ... but nothing with SystemBase and Entities.ForEach. The Samples project also does not do this - it does run the raycasts of QueryTester in a MonoBehaviour during OnDrawGizmos.
     

    Attached Files:

    Last edited: Oct 26, 2020
  10. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Would have edited the previous post instead of by accident bumping my own thread, but the forum says "Upload unavailable" when I try to attach the zip to the previous psot.
    Okay, but then the RaycastSystem must run either before or after both BuildPhysics/ExportPhysicsSystem as it does write the result to a component.

    What would be the recommended point to schedule a raycast system before/after?

    Now it is getting interesting. I am starting to gather all kinds of ways to do the raycast just to see any implementation working. So far ... none.

    There are 3 Buttons now:
    1. "SystemBase" - as per this thread (RaycastSystem: SystemBase)
    2. "MainThread" - as per UnityPhysicSamples.QueryTester.RaycastJob with Schedule().Complete()
    3. "Job" as per Unity.Physics documentation.
    Also I have added a cube primitive (incl. BoxCollider) with ConvertToEntity (ConvertAndInject) at 0,0,0 with size 1 and Debug.DrawLines to the main thread code. None of the methods does ever hit anything.

    The Raycast is always the same : 0,1,0 to 0,-1,0 or vice versa and the result also... nothing ever gets hit.

    Edit2: UnityEngine.Physics.Raycast does hit the HitConvertedCube at (0.0, 0.0, 0.0) - as expected.

    Edit3: Update attached project to _v3 which includes the UnityEngine.Physics.Raycast.
     

    Attached Files:

  11. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    You can also have your raycast system run and collect info, and have another system after export that just writes back the data.

    Regarding the sample, I'll take a look today and let you know.
     
  12. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    Alright, got it.

    There are minor issues with scheduling I fixed, but also a major one.

    The scheduling stuff: always add UpdateBefore and UpdateAfter, but only if you are in the same group. For example, your ColliderConsideredBeforeBuildPhysicsWorld wants to go before BuildPhysicsWorld, but it is in a SimulationSystemGroup which runs after that, you get a warning about this. Also, for each UpdateBefore, add the GetOutputDependency() of the previous system, and for UpdateAfter AddInputDependency() to the next system. Also, I wouldn't suggest going after EndFramePhysicsSystem, because then you don't have "next" in the pipeline. You can also move your entire ray casting system to SimulationSystemGroup, which runs after FixedStepSimulationSystemGroup, so you'll run after physics for sure. This is it regarding scheduling.

    Now the major thing and why ray isn't hitting - you planned to add a plane collider, but you did it in a strange way, by adding a box collider with scale 0 on the Y axis. That caused the ray to hit an edge case and report no hit because there is no volume (collider starts and ends at same position). You can use the PhysicsShapeAuthoring and set collider type to Plane to achieve the same thing. Or just have a really small Y axis scale if you really want box colliders.

    Let me know if any of this helps!
     
  13. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Thanks, I will test this. This could explain the BoxCollider with height 0, but not the PhysicsCollider with a MeshCollider created from the vertex and index information of a quad. The BoxCollider was there to verify the latter, and also display it for debugging purposes.

    Edit: For the BoxCollider height 0 you have been absolutely on point. That already helps a lot. I will now investigate the other scenarios.
     
    Last edited: Oct 31, 2020
    petarmHavok likes this.
  14. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Wait...did the EntityQuery for BuildPhysicsWorld change?
    upload_2020-11-2_19-50-51.png
    I do not know why, but I am somehow sure in the past this was sufficent, but the query for static colliders is defined as

    Code (CSharp):
    1.   StaticEntityGroup = GetEntityQuery(new EntityQueryDesc
    2.             {
    3.                 All = new ComponentType[]
    4.                 {
    5.                     typeof(PhysicsCollider)
    6.                 },
    7.                 Any = new ComponentType[]
    8.                 {
    9.                     typeof(LocalToWorld),
    10.                     typeof(Translation),
    11.                     typeof(Rotation)
    12.                 },
    13.                 None = new ComponentType[]
    14.                 {
    15.                     typeof(PhysicsExclude),
    16.                     typeof(PhysicsVelocity)
    17.                 }
    18.             });
    and the documentation clearly states
    Code (CSharp):
    1. Any = >>>At least one<<< of the component types in this array must exist in the archetype
    .

    ... and that was the solution
     

    Attached Files:

  15. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    I'm not sure I'm following. Are you saying that just adding one of the Translation/Rotation/LocalToWorld fixed the issue for you? I know we changed this at some point, but it was a while back.
     
  16. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Exactly, and the query inside the system explicitly states it must have at least one of the given components beside a PhysicsCollider :) Thank you for your help @petarmHavok !
     
    petarmHavok likes this.