How to detect a on-clicked node from the scene graph?

Hi there,

after a lot of time in experimenting and researching, I still can’t find a good solution to the following problem:

Assume your scene has two different transform nodes which share the same shape node (and same geometry node, let it be a 2d rectangle). If both rectangles are shown in the viewport and I click on one of these, I can’t figure out which is the underlying (parent) transform node. From the viewport’s triangle hit test function I get the triangle and the associated TShape structure and the TShapeNode. As they hold both transform nodes as parents, I do not know how to proceed to find the actual transform node?

The code shows the event evaluation:

if Event.IsMouseButton(buttonLeft) then
begin
  Triangle := Viewport.TriangleHit;
  if (Triangle <> nil) then
  begin
    if  Triangle^.ShapeNode.ParentFieldsCount > 0 then
    begin
      for I := 0 to Triangle^.ShapeNode.ParentFieldsCount-1 do
      begin
        if Triangle^.ShapeNode.ParentFieldsNode[I] is TTransformNode then
        begin
          // Do something to determine the transform node here?
        end
      end;
    end;
  end; 
end;

I wonder, if the approach is completely wrong and I should rather use the bounding box method?

Any hint is welcome
Kobalt

Ah, the TShapeNode references in this case are indeed not unique enough. Both cases will share the same TShapeNode reference.

  1. You can look at Triangle^.Shape (instance of TShape, not to be mistaken with TShapeNode). Different instance of the node will have a different TShape reference, even though they share equal TShapeNode.

    And Triangle^.Shape has GeometryParentNodeName, GeometryGrandParentNodeName, GeometryGrandGrandParentNodeName (see Castle Game Engine: CastleShapes: Class TShape ) . GeometryParentNodeName is probably not very useful (the “parent” is the TShapeNode in this case) but GeometryGrandParentNodeName and GeometryGrandGrandParentNodeName may be useful.

    I’m afraid these isn’t more information that could help you. TShape could but doesn’t store more information about parents (like all references to X3D parent nodes). It’s a very rare use-case that you need to differentiate between clicks on 2 reused shapes.

  2. A complicated solution: You could derive your own TMyCastleScene (descendant of TCastleScene), and TMyShape (your own TGLShape descendant). In TMyCastleScene you would override CreateShape to create instance of TMyShape. And then you can override TMyShape.Create constructor, that gets ParentInfo with detailed links to all parents:

constructor TShape.Create(const AParentScene: TX3DEventsEngine;
  const AOriginalGeometry: TAbstractGeometryNode;
  const AOriginalState: TX3DGraphTraverseState;
  const ParentInfo: PTraversingInfo);
  1. Depending on your use-case, but the simplest solution (if AD 1 is not enough and AD 2 seems to complicated) may be to just avoid such constructions in your files.

Depends on what you need. If all you want is a bounding box, then sure, just use TShape.BoundingBox.

Thanks for your reply!

To elaborate on my project to understand the question: I’m working on a tile board converter. The created x3d scene contains a lot of transform nodes (one for each tile), but for efficiency (see reply to AD 3 below), tile nodes share the same shape node if they a look alike.

Unfortunately the name strings are empty.

Unfortunately this is, as you stated, a little too complicated for my use case. I am just looking for a simple way to detect the clicked-on tile-transform node for a short demo program. Please see also answer to AD 3.

I wonder about this statement: Lets assume you have 100 transform nodes, shouldn’t it be much more memory efficient to have them share 1 shape node (if they all look alike) instead of creating 100 shape nodes which are configured identically. - If this is not true, it would be even simpler solution to just create an individual shape for each transform node, although they are configured identically.

Best regards

.

In such case your optimization makes total sense. Keep them shared, this is better, allows to reuse all GPU resources on the engine side (without any extra work to “detect if some shapes are equal” – we don’t need to detect it when we know they have equal references).

Hm, so let’s try to make a solution that is simple but also uses your data.

Would changing existing node names (strings):

    property GeometryParentNodeName: string read FGeometryParentNodeName;
    property GeometryGrandParentNodeName: string read FGeometryGrandParentNodeName;
    property GeometryGrandGrandParentNodeName: string read FGeometryGrandGrandParentNodeName;

into references to nodes, like this:

    property GeometryParentNode: TX3DNode ...;
    property GeometryGrandParentNode: TX3DNode ...;
    property GeometryGrandGrandParentNode: TX3DNode ...;

be enough for your case?

I would then make XxxNodeName a deprecated functions that just access XxxNode.X3DName.

Just dropping 2 cents (and maybe that’s not a good idea, because it’s less generic), but if aiming at efficiency I’d avoid raycasting against multiple colliders/bounding boxes. I’d rather create a (maybe invisible) container plane (which would correspond to span of the map), detect point of ray collision only with this plane and based on collision coordinates convert those to 2D coordinates on this plane and eventually calculate the tile that was clicked.

This method has it’s limitations (e.g. if the tiles can be elevated or have variable height + viewed on oblique angle), but for most usecases I’m aware of it may be more efficient to do coordinates conversion twice-thrice, than enumerate all the objects in the scene.

If the Geometry(Grand)(Grand)ParentNode functions will just return the one grand parent node considering the transformation state, this would be perfectly sufficient.

I like the idea. Though, with the demo program I just want to demonstrate the possibility to load and use the scene in the most basic way. If you plan on developing a real project from this, there could be more efficient implementations (with regard of your concern about the sensor detection efficiency) to use the tile map scene depending on your use-case (as the one proposed by you).

Yes exactly. Done in TShape.GeometryParentNode, GeometryGrandParentNode, · castle-engine/castle-engine@81927e0 · GitHub , check it out :slight_smile:

Thank you! Works like a charm now! - With minimal effort the underlying transform node is found now.

1 Like