/**
  * 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());
   }
 }
 /**
  * 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.
  *
  * <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;
   }
 }