/**
   * Compute the bounding screen extent of a rotated rectangle.
   *
   * @param rect Rectangle to rotate.
   * @param x X coordinate of the rotation point.
   * @param y Y coordinate of the rotation point.
   * @param rotation Rotation angle.
   * @return The smallest rectangle that completely contains {@code rect} when rotated by the
   *     specified angle.
   */
  protected Rectangle computeRotatedScreenExtent(Rectangle rect, int x, int y, Angle rotation) {
    Rectangle r = new Rectangle(rect);

    // Translate the rectangle to the rotation point.
    r.translate(-x, -y);

    // Compute corner points
    Vec4[] corners = {
      new Vec4(r.getMaxX(), r.getMaxY()),
      new Vec4(r.getMaxX(), r.getMinY()),
      new Vec4(r.getMinX(), r.getMaxY()),
      new Vec4(r.getMinX(), r.getMinY())
    };

    // Rotate the rectangle
    Matrix rotationMatrix = Matrix.fromRotationZ(rotation);
    for (int i = 0; i < corners.length; i++) {
      corners[i] = corners[i].transformBy3(rotationMatrix);
    }

    // Find the bounding rectangle of rotated points.
    int minX = Integer.MAX_VALUE;
    int minY = Integer.MAX_VALUE;
    int maxX = -Integer.MAX_VALUE;
    int maxY = -Integer.MAX_VALUE;

    for (Vec4 v : corners) {
      if (v.x > maxX) maxX = (int) v.x;

      if (v.x < minX) minX = (int) v.x;

      if (v.y > maxY) maxY = (int) v.y;

      if (v.y < minY) minY = (int) v.y;
    }

    // Set bounds and translate the rectangle back to where it started.
    r.setBounds(minX, minY, maxX - minX, maxY - minY);
    r.translate(x, y);

    return r;
  }
 @Override
 protected void applyModelviewTransform(DrawContext dc, SurfaceTileDrawContext sdc) {
   // Apply the geographic to surface tile coordinate transform.
   Matrix modelview = sdc.getModelviewMatrix();
   dc.getGL().glMultMatrixd(modelview.toArray(new double[16], 0, false), 0);
 }
  /**
   * Compute the label's screen position from its geographic position.
   *
   * @param dc Current draw context.
   */
  protected void computeGeometry(DrawContext dc) {
    // Project the label position onto the viewport
    Position pos = this.getPosition();
    if (pos == null) return;

    this.placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
    this.screenPlacePoint = dc.getView().project(this.placePoint);

    this.eyeDistance = this.placePoint.distanceTo3(dc.getView().getEyePoint());

    boolean orientationReversed = false;
    if (this.orientationPosition != null) {
      // Project the orientation point onto the screen
      Vec4 orientationPlacePoint =
          dc.computeTerrainPoint(
              this.orientationPosition.getLatitude(), this.orientationPosition.getLongitude(), 0);
      Vec4 orientationScreenPoint = dc.getView().project(orientationPlacePoint);

      this.rotation = this.computeRotation(this.screenPlacePoint, orientationScreenPoint);

      // The orientation is reversed if the orientation point falls to the right of the screen
      // point. Text is
      // never drawn upside down, so when the orientation is reversed the text flips vertically to
      // keep the text
      // right side up.
      orientationReversed = (orientationScreenPoint.x <= this.screenPlacePoint.x);
    }

    this.computeBoundsIfNeeded(dc);

    Offset offset = this.getOffset();
    Point2D offsetPoint =
        offset.computeOffset(this.bounds.getWidth(), this.bounds.getHeight(), null, null);

    // If a rotation is applied to the text, then rotate the offset as well. An offset in the x
    // direction
    // will move the text along the orientation line, and a offset in the y direction will move the
    // text
    // perpendicular to the orientation line.
    if (this.rotation != null) {
      double dy = offsetPoint.getY();

      // If the orientation is reversed we need to adjust the vertical offset to compensate for the
      // flipped
      // text. For example, if the offset normally aligns the top of the text with the place point
      // then without
      // this adjustment the bottom of the text would align with the place point when the
      // orientation is
      // reversed.
      if (orientationReversed) {
        dy = -(dy + this.bounds.getHeight());
      }

      Vec4 pOffset = new Vec4(offsetPoint.getX(), dy);
      Matrix rot = Matrix.fromRotationZ(this.rotation.multiply(-1));

      pOffset = pOffset.transformBy3(rot);

      offsetPoint = new Point((int) pOffset.getX(), (int) pOffset.getY());
    }

    int x = (int) (this.screenPlacePoint.x + offsetPoint.getX());
    int y = (int) (this.screenPlacePoint.y - offsetPoint.getY());

    this.screenPoint = new Point(x, y);
    this.screenExtent = this.computeTextExtent(x, y, this.rotation);
  }
  /**
   * Indicates the transform matrix applied to this document.
   *
   * @return Transform matrix.
   */
  protected Matrix getMatrix() {
    // If the matrix has already been computed then just return the cached value.
    if (this.matrix != null) return this.matrix;

    Matrix m = Matrix.IDENTITY;

    if (this.heading != null)
      m = m.multiply(Matrix.fromRotationZ(Angle.POS360.subtract(this.heading)));

    if (this.pitch != null) m = m.multiply(Matrix.fromRotationX(this.pitch));

    if (this.roll != null) m = m.multiply(Matrix.fromRotationY(this.roll));

    // Apply scaling factor to convert file units to meters.
    double scale = this.getScale();
    m = m.multiply(Matrix.fromScale(scale));

    if (this.modelScale != null) m = m.multiply(Matrix.fromScale(this.modelScale));

    this.matrix = m;
    return m;
  }