/**
  * Zooms the camera around the specified focus coordinates.
  *
  * @param focusX the x coordinate to focus on
  * @param focusY the y coordinate to focus on
  * @param factor the zoom will be scaled by this factor
  */
 public void zoom(double focusX, double focusY, double factor) {
   synchronized (mutex) {
     Transform focus = Transform.translation(toMetricCoordinates((int) focusX, (int) focusY));
     double zoom = RosMath.clamp(getZoom() * factor, MINIMUM_ZOOM, MAXIMUM_ZOOM) / getZoom();
     transform = transform.multiply(focus).scale(zoom).multiply(focus.invert());
   }
 }
 @Override
 public boolean onTouchEvent(VisualizationView view, MotionEvent event) {
   if (visible) {
     Preconditions.checkNotNull(pose);
     if (event.getAction() == MotionEvent.ACTION_MOVE) {
       Vector3 orientationVector =
           camera
               .toWorldCoordinates(new Point((int) event.getX(), (int) event.getY()))
               .subtract(pose.getTranslation());
       if (orientationVector.length() > 0) {
         pose.setRotation(
             Quaternion.rotationBetweenVectors(new Vector3(1, 0, 0), orientationVector));
       } else {
         pose.setRotation(Quaternion.newIdentityQuaternion());
       }
       shape.setTransform(pose);
       requestRender();
       return true;
     } else if (event.getAction() == MotionEvent.ACTION_UP) {
       posePublisher.publish(
           pose.toPoseStampedMessage(
               camera.getFixedFrame(),
               connectedNode.getCurrentTime(),
               posePublisher.newMessage()));
       visible = false;
       requestRender();
       return true;
     }
   }
   gestureDetector.onTouchEvent(event);
   return false;
 }
 /**
  * Rotates the camera round the specified coordinates.
  *
  * @param focusX the x coordinate to focus on
  * @param focusY the y coordinate to focus on
  * @param deltaAngle the camera will be rotated by {@code deltaAngle} radians
  */
 public void rotate(double focusX, double focusY, double deltaAngle) {
   synchronized (mutex) {
     Transform focus = Transform.translation(toMetricCoordinates((int) focusX, (int) focusY));
     transform =
         transform
             .multiply(focus)
             .multiply(Transform.zRotation(deltaAngle))
             .multiply(focus.invert());
   }
 }
 /**
  * Changes the camera frame to the specified frame and aligns the camera with the new frame.
  *
  * @param frame the new camera frame
  */
 public void jumpToFrame(GraphName frame) {
   synchronized (mutex) {
     this.frame = frame;
     double zoom = getZoom();
     resetTransform();
     transform = transform.scale(zoom / getZoom());
   }
 }
 /**
  * Changes the camera frame to the specified frame.
  *
  * <p>If possible, the camera will avoid jumping on the next frame.
  *
  * @param frame the new camera frame
  */
 public void setFrame(GraphName frame) {
   Preconditions.checkNotNull(frame);
   synchronized (mutex) {
     if (this.frame != null && this.frame != frame) {
       FrameTransform frameTransform = frameTransformTree.transform(frame, this.frame);
       if (frameTransform != null) {
         // Best effort to prevent the camera from jumping. If we don't have
         // the transform yet, we can't help matters.
         transform = transform.multiply(frameTransform.getTransform());
       }
     }
     this.frame = frame;
   }
 }
 /**
  * Translates the camera.
  *
  * @param deltaX distance to move in x in pixels
  * @param deltaY distance to move in y in pixels
  */
 public void translate(double deltaX, double deltaY) {
   synchronized (mutex) {
     transform = Transform.translation(deltaX, deltaY, 0).multiply(transform);
   }
 }
 private void resetTransform() {
   // Rotate coordinate system to match ROS standard (x is forward, y is left).
   transform = Transform.zRotation(Math.PI / 2).scale(DEFAULT_ZOOM);
 }
 /**
  * @return the metric coordinates of the provided pixel coordinates where the origin is the top
  *     left corner of the view
  */
 public Vector3 toMetricCoordinates(int x, int y) {
   double centeredX = x - viewport.getWidth() / 2.0d;
   double centeredY = viewport.getHeight() / 2.0d - y;
   return transform.invert().apply(new Vector3(centeredX, centeredY, 0));
 }
 /** @return the current zoom factor */
 public double getZoom() {
   return transform.getScale();
 }