Get all geometry under raycast?

Hello, is there any way, I could get whole list of items, when I do RayCast on the viewport items? I need to pick geometry on click and currently MouseRayHit or RayCast, WorldRayCast, etc., returns just the first item it hits with it. Is there any way in engine to pick all the items, also underneath the first item picked by RayCast? Thank you.

All the ray-casting methods are indeed designed to only return first hit object. There are specific optimizations inside for this use-case, since it is the most common need.

To query everything, you can repeat the query, shifting RayOrigin (position at which the ray starts) each time. This is similar to what I described in this post: Convenient way to determine exact point under mouse - #4 by michalis , albeit that was for a bit different task (to avoid detecting hits on transparent pixels).

So:

  • Use MyViewport.CameraRayCollision(RayOrigin, RayDirection) . It returns TRayCollision, just like MouseRayHit (so it can be processed in the same way as above to detect the particular point). It takes as input ray position and direction.

  • Use it in a loop.

    • The initial RayOrigin, RayDirection can come from the mouse position – use MyViewport.PositionToRay.

    • Make a call to MyViewport.CameraRayCollision(RayOrigin, RayDirection). If something was hit, add it to the list, and make another call to MyViewport.CameraRayCollision(NewRayOrigin, RayDirection). The NewRayOrigin can be calculated like

    NewRayOrigin := HitPosition + RayDirection.AdjustToLength(SomeEpsilon)
    

Hello, thank you, it worked nicely, with just one thing - Pickable property needs to be set to false while raycasting, to prevent picking object multiple times depending on shape. But I found one issue, with TCastleAbstractPrimitives, whenever I create for example a TCastleBox in editor and load it into runtime app, this does not work. Box is registered correctly by raycast, but no Name given event if I had named it, and also another raycast for object behind or under it does not work either. For loaded TCastleScene models or created geometry via X3D it works perfect. Is there anything specific why it would not work on primitives like box, plane etc.? Thanks.

Indeed. If you don’t want multiple hits on the same object, you can toggle Pickable or Exsists on the object.

Ray collisions with primitives (TCastleAbstractPrimitive) should be detected OK.

But note that MyViewport.MouseRayHit will then be more complicated: the first item on MyViewport.MouseRayHit will be the internal TCastleScene instance (each TCastleAbstractPrimitives actually creates an internal TCastleScene that it manages). The 2nd item will be a reference to the actual TCastleAbstractPrimitive instance.

It that is the case, it is easiest to just use MyViewport.TransformUnderMouse. Or look at MyViewport.MouseRayHit <> nil, if not nil then look at MyViewport.MouseRayHit.Transform. Both these methods will return the first transform that is not internal. (they detect it by looking at csTransient in Transform.ComponentStyle).

It is possible we’ll change MouseRayHit at some point to avoid showing these internal instances at all. I know they can be confusing. These are also discussed in Tiled maps | Manual | Castle Game Engine .

Let me know if this fixes your problem with TCastleAbstractPrimitive. If the problem will remain, please submit a testcase to reproduce the problem.

Hello, so it looks like I did not thoroughly tested, because I have found a strange issue. It does not work completely I figured now, but I do not understand why. Basically I have saved user interface file which I edit in the editor where I do have viewport as root, some background and then in the Items, I do have Camera, DirLight and all models as TCastleScene. One of them I do have generated as custom geometry through X3DNodes and is saved as X3D and loaded into scene (there I need to differentiate per shape with help of triangle, so I need extended collision result). The thing is, that when I move around and get mouse cursor over object like e.g. building there, and box behind it, it only registers the building and then proceeds to return nil as WorldRay result even if I previously make the building Pickable property false. But when there is that custom geometry behind building (but should be irelevant as it is loaded as X3D model), it does register both objects correctly. Where could possibly be a problem hidden here? Here is the code for method which I call to get all geometry in Viewport’s OnMotion procedure.

function CGEForm.AllItemsHitByRay(pos: TVector2; depth: integer): TList<TPair<TShape, TCastleTransform>>;
var
  RayHit: TRayCollision;
  RayOrigin, RayDirection: TVector3;
  Item: TPair<TShape, TCastleTransform>;
  Shape: TShape;
  rayColNode: TRayCollisionNode;
  rayColPos: Integer;
begin
  Result := nil;
  depth := Max(depth, 1);
  fCGEViewport.PositionToRay(pos, true, RayOrigin, RayDirection);
  RayHit := fCGEViewport.Items.WorldRay(RayOrigin, RayDirection);
  if RayHit <> nil then
  begin
    rayColPos := GetCorrectRayCollisionNodePos(rayHit);
    Result := TList<TPair<TShape, TCastleTransform>>.Create();
    while (RayHit <> nil) AND (rayColPos > -1) AND (depth > 0) do
    begin
      shape := nil;
      rayColNode := RayHit.Items[rayColPos];
      if (rayColNode.Item is TCastleScene) AND (rayColNode.Triangle <> nil) AND (rayColNode.Triangle.Shape <> nil) then
      begin
        if TCastleScene(rayColNode.Item).VerticesCount <> rayColNode.Triangle.Shape.VerticesCount then
          shape := rayColNode.Triangle.Shape;
      end;

      Result.Add(TPair<TShape, TCastleTransform>.Create(shape, rayColNode.Item));
      rayColNode.Item.Pickable := false;
      RayOrigin := rayColNode.Point + RayDirection.AdjustToLength(SingleEqualityEpsilon);
      FreeAndNil(RayHit);
      RayHit := fCGEViewport.Items.WorldRay(RayOrigin, RayDirection);
      rayColPos := GetCorrectRayCollisionNodePos(RayHit);
      Dec(depth);
    end;

    if RayHit <> nil then
      FreeAndNil(RayHit);

    for Item in Result do
    begin
      Item.Value.Pickable := true;
    end;
  end;
end;

Thanks for any further advice.

Hmm, I don’t know the solution from a first glance. Your code looks OK (in particular you restore the ...Pickable := true reliably, which was my first suspicion). I will need to have a complete testcase (code that compiles + data) to experiment and advise more on this. Can you post it (on this forum, or as GitHub issue Issues · castle-engine/castle-engine · GitHub ) ?

Hello, sorry for the inconvenience, I just found out that I changed a logic for printing out the names of objects under pick slightly and also quite poorly debugged probably, because now I have noticed the cause and the solution seems to work flawless as it is there. Also there is probably no need to computing vector from hitPoint when disabling pickable, thus saving a line and small operation cost maybe. Thanks for help.

1 Like