Learn how to use the [Instant Placement API](https://developers.google.com/ar/develop/java/instant-placement/overview)
in your own apps.

## Prerequisites

Make sure that you understand [fundamental AR concepts](https://developers.google.com/ar/develop/fundamentals)
and how to [configure an ARCore session](https://developers.google.com/ar/develop/java/session-config) before proceeding.

## Configure a new session with Instant Placement

In a new ARCore session, enable [Instant Placement mode](https://developers.google.com/ar/reference/java/com/google/ar/core/Config.InstantPlacementMode).

### Java

    // Create the ARCore session.
    public void createSession() {
      session = new Session(applicationContext);
      Config config = new Config(session);
      // Set the Instant Placement mode.
      config.setInstantPlacementMode(InstantPlacementMode.LOCAL_Y_UP);
      session.configure(config);
    }

### Kotlin

    // Create the ARCore session.
    fun createSession() {
      session = Session(applicationContext);
      val config = Config(session)
      // Set the Instant Placement mode.
      config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
      session.configure(config)
    }

## Place an object

Use [`Frame.hitTestInstantPlacement()`](https://developers.google.com/ar/reference/java/com/google/ar/core/Frame#hitTestInstantPlacement-xPx-yPx-approximateDistanceMeters)
to create a trackable Instant Placement point given a screen tap position.
Retrieve the current pose with the [`getPose()`](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint#getPose-) method.

### Java

    private placementIsDone = false;

    public void onDrawFrame(GL10 gl) {
      Frame frame = session.update();

      // Place an object on tap.
      if (!placementIsDone && didUserTap()) {
        // Use estimated distance from the user's device to the real world, based
        // on expected user interaction and behavior.
        float approximateDistanceMeters = 2.0f;
        // Performs a ray cast given a screen tap position.
        List<HitResult> results =
          frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
        if (!results.isEmpty()) {
          InstantPlacementPoint point = (InstantPlacementPoint) results.get(0).getTrackable();
          // Create an Anchor from the point's pose.
          Anchor anchor = point.createAnchor(point.getPose());
          placementIsDone = true;
          disableInstantPlacement();
        }
      }
    }

### Kotlin

    var placementIsDone = false;

    fun onDrawFrame(gl: GL10) {
      val frame = session.update();

      // Place an object on tap.
      if (!placementIsDone && didUserTap()) {
        // Use estimated distance from the user's device to the real world, based
        // on expected user interaction and behavior.
        val approximateDistanceMeters = 2.0f;
        // Performs a ray cast given a screen tap position.
        val results = frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters)
        if (results.isNotEmpty()) {
          val point = results[0].trackable as InstantPlacementPoint
          // Create an Anchor from the point's pose.
          val anchor = point.createAnchor(point.pose)
          placementIsDone = true
          disableInstantPlacement()
        }
      }
    }

Instant Placement supports
[screen space tracking with approximate distance](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint.TrackingMethod#screenspace_with_approximate_distance),
automatically switching to full tracking once the Instant Placement point is
anchored in the real world. Retrieve the current pose with
[`getPose()`](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint#getPose()).
Get the current tracking method with
[`getTrackingMethod()`](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint#getTrackingMethod-).

Although ARCore can perform Instant Placement hit tests against surfaces of any
orientation, hit results will always return a pose with +Y up, against the
direction of gravity. On horizontal surfaces, hit tests return accurate
positions much faster.

## Smooth the tracking method transition

When true depth becomes available, ARCore will change a `InstantPlacementPoint`'s [tracking method](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint#getTrackingMethod-) from
[`SCREENSPACE_WITH_APPROXIMATE_DISTANCE`](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint.TrackingMethod#screenspace_with_approximate_distance) to [`FULL_TRACKING`](https://developers.google.com/ar/reference/java/com/google/ar/core/InstantPlacementPoint.TrackingMethod#full_tracking).
The point's pose will also change to reflect true depth.
This may lead to the object appearing to suddenly grow or shrink.
To avoid this sudden change, add an `InstantPlacementPoint` wrapper.

### Java

    // Wrapper class to track state to reduce sudden visual changes in object size
    class WrappedInstantPlacement {
      public InstantPlacementPoint point;
      public InstantPlacementPoint.TrackingMethod previousTrackingMethod;
      public float previousDistanceToCamera;
      public float scaleFactor = 1.0f;

      public WrappedInstantPlacement(
          InstantPlacementPoint point,
          InstantPlacementPoint.TrackingMethod previousTrackingMethod,
          float previousDistanceToCamera) {
        this.point = point;
        this.previousTrackingMethod = previousTrackingMethod;
        this.previousDistanceToCamera = previousDistanceToCamera;
      }
    }

### Kotlin

    // Wrapper class to track state to reduce sudden visual changes in object size
    class WrappedInstantPlacement(
      var point: InstantPlacementPoint,
      var previousTrackingMethod: InstantPlacementPoint.TrackingMethod,
      var previousDistanceToCamera: Float,
      var scaleFactor: Float = 1.0f
    )

Then, add the following to your activity.

### Java

    List<WrappedInstantPlacement> wrappedPoints = new ArrayList<>();

    public void onDrawFrame(GL10 gl) {
      Frame frame = session.update();
      Camera camera = frame.getCamera();

      // Place an object on tap.
      if (didUserTap()) {
        // Instant Placement should only be applied if no results are available through hitTest.
        List<HitResult> results = frame.hitTest(tapX, tapY);
        if (results.isEmpty()) {
          // Use the estimated distance from the user's device to the closest
          // available surface, based on expected user interaction and behavior.
          float approximateDistanceMeters = 2.0f;

          // Returns a single result if the hit test was successful.
          results =
              frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
          if (!results.isEmpty()) {
            // Instant placement was successful.
            InstantPlacementPoint point = (InstantPlacementPoint) results.get(0).getTrackable();
            wrappedPoints.add(new WrappedInstantPlacement(point, point.getTrackingMethod(),
              distance(camera.getPose(), point.getPose())));
          }
        } else {
          // results contain valid hit tests which can be used directly, so instant placement is not required.
        }
      }

      for (WrappedInstantPlacement wrappedPoint : wrappedPoints) {
        InstantPlacementPoint point = wrappedPoint.point;
        if (point.getTrackingState() == TrackingState.STOPPED) {
          wrappedPoints.remove(wrappedPoint);
          continue;
        }
        if (point.getTrackingState() == TrackingState.PAUSED) {
          continue;
        }

        if (point.getTrackingMethod() == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE) {
           // Continue to use the estimated depth and pose. Record the distance to
           // the camera for use in the next frame if the transition to full
           // tracking happens.
           wrappedPoint.previousDistanceToCamera = distance(point.getPose(), camera.getPose());
        }
        else if (point.getTrackingMethod() == TrackingMethod.FULL_TRACKING) {
          if (wrappedPoint.previousTrackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE) {
            // Change from the estimated pose to the accurate pose. Adjust the
            // object scale to counteract the apparent change due to pose jump.
            wrappedPoint.scaleFactor = distance(point.getPose(), camera.getPose()) /
                wrappedPoint.previousDistanceToCamera;
            // Apply the scale factor to the model.
            // ...
            wrappedPoint.previousTrackingMethod = TrackingMethod.FULL_TRACKING;
          }
        }
      }
    }

    float distance(Pose p, Pose q) {
      return Math.sqrt(Math.pow(p.tx() - q.tx(), 2) + Math.pow(p.ty() - q.ty(), 2) + Math.pow(p.tz() - q.tz(), 2));
    }

### Kotlin

    var wrappedPoints = mutableListOf<WrappedInstantPlacement>()

    fun onDrawFrame(gl: GL10?) {
      val frame = session.update()
      val camera = frame.camera

      // Place an object on tap.
      if (didUserTap()) {
        // Instant Placement should only be applied if no results are available through hitTest.
        var results = frame.hitTest(tapX, tapY);
        if (results.isEmpty()) {
          // Use the estimated distance from the user's device to the closest
          // available surface, based on expected user interaction and behavior.
          val approximateDistanceMeters = 2.0f;

          // Returns a single result if the hit test was successful.
          results = frame.hitTestInstantPlacement(tapX, tapY, approximateDistanceMeters);
          if (results.isNotEmpty()) {
            // Instant placement was successful.
            val point = results[0].trackable as InstantPlacementPoint
            val wrapped = WrappedInstantPlacement(point, point.trackingMethod, point.pose.distance(camera.pose))
            wrappedPoints.add(wrapped)
          }
        } else {
          // Results contain valid hit tests which can be used directly, so Instant Placement 
          // is not required.
        }
      }

      loop@ for (wrappedPoint in wrappedPoints) {
        val point = wrappedPoint.point
        when {
          point.trackingState == TrackingState.STOPPED -> {
            wrappedPoints.remove(wrappedPoint)
            continue@loop
          }
          point.trackingState == TrackingState.PAUSED -> continue@loop
          point.trackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE -> {
            // Continue to use the estimated depth and pose. Record the distance to
            // the camera for use in the next frame if the transition to full
            // tracking happens.
            wrappedPoint.previousDistanceToCamera = point.pose.distance(camera.pose)
          }
          wrappedPoint.previousTrackingMethod == TrackingMethod.SCREENSPACE_WITH_APPROXIMATE_DISTANCE &&
          point.trackingMethod == TrackingMethod.FULL_TRACKING -> {
            // Change from the estimated pose to the accurate pose. Adjust the
            // object scale to counteract the apparent change due to pose jump.
            wrappedPoint.scaleFactor =
              point.pose.distance(camera.pose) / wrappedPoint.previousDistanceToCamera
            // Apply the scale factor to the model.
            // ...
            wrappedPoint.previousTrackingMethod = TrackingMethod.FULL_TRACKING
          }
        }
      }
    }

    fun Pose.distance(other: Pose) = sqrt(
      (tx() - other.tx()).pow(2.0f) + (ty() - other.ty()).pow(2.0f) + (tz() - other.tz()).pow(2.0f)
    )

## Increase efficiency after object placement

Disable Instant Placement when the object is correctly placed to save CPU cycles
and power.

### Java

    void disableInstantPlacement() {
      Config config = new Config(session);
      config.setInstantPlacementMode(Config.InstantPlacementMode.DISABLED);
      session.configure(config);
    }

### Kotlin

    fun disableInstantPlacement() {
      val config = Config(session)
      // Set the Instant Placement mode.
      config.instantPlacementMode = Config.InstantPlacementMode.DISABLED
      session.configure(config)
    }