Search code examples
javaandroidraycastingarcoresceneform

How to place a Renderable to a HitTestResult getPoint() location


I want to place a small sphere renderable to the location where an already placed model was tapped and also anchor it to that location. Can that be achieved and if so, how?

private void tryPlaceModel(MotionEvent motionEvent, Frame frame)
{
   if (isModelPlaced)
   {
      return;
   }

   if (motionEvent != null && frame.getCamera().getTrackingState() == TrackingState.TRACKING)
   {
    for (HitResult hit : frame.hitTest(motionEvent))
    {
    Trackable trackable = hit.getTrackable();

    if (trackable instanceof Plane && ((Plane) trackable).isPoseInPolygon(hit.getHitPose()))
    {
       // Create the Anchor.
       Anchor anchor = hit.createAnchor();
       AnchorNode anchorNode = new AnchorNode(anchor);
       anchorNode.setParent(arSceneView.getScene());

       TransformableNode model = new TransformableNode(arFragment.getTransformationSystem());
       model.setParent(anchorNode);
       model.setRenderable(andyRenderable);
       model.select();

       isModelPlaced = true;

       model.setOnTapListener((hitTestResult, motionEvent1) ->
       {
          Frame frame1 = arSceneView.getArFrame();

          if (motionEvent1 != null && frame1.getCamera().getTrackingState() == TrackingState.TRACKING)
          {
             com.google.ar.sceneform.rendering.Color color = new com.google.ar.sceneform.rendering.Color();
             color.set(Color.RED);

             MaterialFactory.makeOpaqueWithColor(this, color)
                   .thenAccept(
                         material ->
                         {
                            Pose pose = Pose.makeTranslation(hitTestResult.getPoint().x, hitTestResult.getPoint().y, hitTestResult.getPoint().z);

                            Anchor anchor1 = session.createAnchor(pose);

                            AnchorNode anchorNode1 = new AnchorNode(anchor1);
                            anchorNode1.setParent(hitTestResult.getNode());

                            Renderable sphere = ShapeFactory.makeSphere(0.05f,
                                  /* WRONG LOCATION */anchorNode1.getLocalPosition(), material);

                            Node indicatorModel = new Node();
                            indicatorModel.setParent(anchorNode1);
                            indicatorModel.setRenderable(sphere);
                         }
                   );
          }
       });
    }
 }
 }

Here's where I am now. The code entirely works, except the location where I want the spheres to appear. I also tried with AnchorNode.getWorldPosition, but it's wrong too.

The spheres are appearing to the back of the model. Is this because the getPoint() method returns the point where the ray hit the plane (although it is literally written that it returns the point the ray hits the model/node), or I am just doing something wrong?


Solution

  • The Node.OnTapListener is called when a tap event hits a node and different than the plane listener which responds to a hittest on a plane, not the node.

    The HitTestResult has the node that hit and the world coordinates of the point hit on the renderable. With these, you can create a node, set the parent to the node hit, and the world position to the hittest result point:

      public void addTapHandler(Node node) {
        node.setOnTapListener((HitTestResult hitTestResult, MotionEvent motionEvent) -> {
          // Hit test point will be where the ray from the screen point intersects the model.
    
          // Create a sphere and attach it to the point.
          Color color = new Color(.8f, 0, 0);
    
          // Note: you can make one material and one sphere and reuse them.
          MaterialFactory.makeOpaqueWithColor(this, color)
                  .thenAccept(material -> {
                    // The sphere is in local coordinate space, so make the center 0,0,0
                    Renderable sphere = ShapeFactory.makeSphere(0.05f, Vector3.zero(),
                                                                material);
    
                    Node indicatorModel = new Node();
                    indicatorModel.setParent(hitTestResult.getNode());
                    indicatorModel.setWorldPosition(hitTestResult.getPoint());
                    indicatorModel.setRenderable(sphere);
                  });
        });
      }