protected void onHorizontalTranslateRel(
      double forwardInput,
      double sideInput,
      double totalForwardInput,
      double totalSideInput,
      ViewInputAttributes.DeviceAttributes deviceAttributes,
      ViewInputAttributes.ActionAttributes actionAttributes) {
    Angle forwardChange;
    Angle sideChange;

    this.stopGoToAnimators();
    if (actionAttributes.getMouseActions() != null) {
      forwardChange =
          Angle.fromDegrees(
              -totalForwardInput * getScaleValueElevation(deviceAttributes, actionAttributes));
      sideChange =
          Angle.fromDegrees(
              totalSideInput * getScaleValueElevation(deviceAttributes, actionAttributes));
    } else {
      forwardChange =
          Angle.fromDegrees(
              forwardInput * speed * getScaleValueElevation(deviceAttributes, actionAttributes));
      sideChange =
          Angle.fromDegrees(
              sideInput * speed * getScaleValueElevation(deviceAttributes, actionAttributes));
    }
    onHorizontalTranslateRel(forwardChange, sideChange, actionAttributes);
  }
Пример #2
0
    private Info[] buildSurfaceShapes() {
      LatLon position = new LatLon(Angle.fromDegrees(38), Angle.fromDegrees(-105));

      ArrayList<LatLon> surfaceLinePositions = new ArrayList<LatLon>();
      //            surfaceLinePositions.add(LatLon.fromDegrees(37.8484, -119.9754));
      //            surfaceLinePositions.add(LatLon.fromDegrees(38.3540, -119.1526));

      //            surfaceLinePositions.add(new LatLon(Angle.fromDegrees(0),
      // Angle.fromDegrees(-150)));
      //            surfaceLinePositions.add(new LatLon(Angle.fromDegrees(60),
      // Angle.fromDegrees(0)));

      surfaceLinePositions.add(position);
      surfaceLinePositions.add(LatLon.fromDegrees(39, -104));
      surfaceLinePositions.add(LatLon.fromDegrees(39, -105));
      surfaceLinePositions.add(position);

      return new Info[] {
        new Info("Circle", new SurfaceCircle(position, 100e3)),
        new Info("Ellipse", new SurfaceEllipse(position, 100e3, 90e3, Angle.ZERO)),
        new Info("Square", new SurfaceSquare(position, 100e3)),
        new Info("Quad", new SurfaceQuad(position, 100e3, 60e3, Angle.ZERO)),
        new Info("Sector", new SurfaceSector(Sector.fromDegrees(38, 40, -105, -103))),
        new Info("Polygon", new SurfacePolygon(surfaceLinePositions)),
      };
    }
Пример #3
0
  private void applyModify() {
    layer.setName(layerNameTextField.getText());
    Color choosenColor = colorBtn.getBackground();
    color = new Color(choosenColor.getRed(), choosenColor.getGreen(), choosenColor.getBlue());
    List<Position> positions = new ArrayList<Position>();
    for (int i = 0; i < latTexts.size(); i++) {
      Position p = layer.getPositions().get(i);
      double lat = Double.valueOf(latTexts.get(i).getText());
      double lng = Double.valueOf(lngTexts.get(i).getText());
      Position newP =
          new Position(Angle.fromDegrees(lat), Angle.fromDegrees(lng), p.getElevation());
      positions.add(newP);
    }
    layer.setPositions(positions);
    path.setPositions(positions);

    // Create and set an attribute bundle.
    ShapeAttributes attrs = new BasicShapeAttributes();
    Material material = new Material(color);
    attrs.setOutlineMaterial(material);
    attrs.setOutlineWidth(Double.valueOf(sizeTextField.getText()));
    attrs.setOutlineOpacity(Double.valueOf(opacityTextField.getText()));
    //		path.setColor(color);
    //		path.setLineWidth(Double.valueOf(sizeTextField.getText()));
    path.setAttributes(attrs);

    layer.refresh();
    frame.getLayerPanelDialog().getLayerPanel().update();
  }
  /**
   * Apply the model's position, orientation, and scale to a COLLADA root.
   *
   * @param root COLLADA root to configure.
   */
  protected void configureColladaRoot(ColladaRoot root) {
    root.setResourceResolver(this);

    Position refPosition = this.model.getLocation().getPosition();
    root.setPosition(refPosition);
    root.setAltitudeMode(KMLUtil.convertAltitudeMode(this.model.getAltitudeMode()));

    KMLOrientation orientation = this.model.getOrientation();
    if (orientation != null) {
      Double d = orientation.getHeading();
      if (d != null) root.setHeading(Angle.fromDegrees(d));

      d = orientation.getTilt();
      if (d != null) root.setPitch(Angle.fromDegrees(-d));

      d = orientation.getRoll();
      if (d != null) root.setRoll(Angle.fromDegrees(-d));
    }

    KMLScale scale = this.model.getScale();
    if (scale != null) {
      Double x = scale.getX();
      Double y = scale.getY();
      Double z = scale.getZ();

      Vec4 modelScale = new Vec4(x != null ? x : 1.0, y != null ? y : 1.0, z != null ? z : 1.0);

      root.setModelScale(modelScale);
    }
  }
 /**
  * Returns the latitude and longitude of the sector's angular center: (minimum latitude + maximum
  * latitude) / 2, (minimum longitude + maximum longitude) / 2.
  *
  * @return The latitude and longitude of the sector's angular center
  */
 public LatLon getCentroid() {
   Angle la =
       Angle.fromDegrees(0.5 * (this.getMaxLatitude().degrees + this.getMinLatitude().degrees));
   Angle lo =
       Angle.fromDegrees(0.5 * (this.getMaxLongitude().degrees + this.getMinLongitude().degrees));
   return new LatLon(la, lo);
 }
 /**
  * Creates a new <code>Sector</code> and initializes it to the specified angles. The angles are
  * assumed to be normalized to +/- 90 degrees latitude and +/- 180 degrees longitude, but this
  * method does not verify that.
  *
  * @param minLatitude the sector's minimum latitude in degrees.
  * @param maxLatitude the sector's maximum latitude in degrees.
  * @param minLongitude the sector's minimum longitude in degrees.
  * @param maxLongitude the sector's maximum longitude in degrees.
  * @return the new <code>Sector</code>
  */
 public static Sector fromDegrees(
     double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
   return new Sector(
       Angle.fromDegrees(minLatitude),
       Angle.fromDegrees(maxLatitude),
       Angle.fromDegrees(minLongitude),
       Angle.fromDegrees(maxLongitude));
 }
  public Sector(Sector sector) {
    if (sector == null) {
      throw new IllegalArgumentException("Sector Is Null");
    }

    this.minLatitude = new Angle(sector.getMinLatitude());
    this.maxLatitude = new Angle(sector.getMaxLatitude());
    this.minLongitude = new Angle(sector.getMinLongitude());
    this.maxLongitude = new Angle(sector.getMaxLongitude());
    this.deltaLat = Angle.fromDegrees(this.maxLatitude.degrees - this.minLatitude.degrees);
    this.deltaLon = Angle.fromDegrees(this.maxLongitude.degrees - this.minLongitude.degrees);
  }
  @Override
  protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) {
    super.doRestoreState(rs, context);

    Double la = rs.getStateValueAsDouble(context, "leftAzimuthDegrees");
    if (la == null) la = this.leftAzimuth.degrees;

    Double ra = rs.getStateValueAsDouble(context, "rightAzimuthDegrees");
    if (ra == null) ra = this.rightAzimuth.degrees;

    this.setAzimuths(Angle.fromDegrees(la), Angle.fromDegrees(ra));
  }
  /**
   * Creates a new <code>Sector</code> and initializes it to the specified angles. The angles are
   * assumed to be normalized to +/- 90 degrees latitude and +/- 180 degrees longitude, but this
   * method does not verify that.
   *
   * @param minLatitude the sector's minimum latitude.
   * @param maxLatitude the sector's maximum latitude.
   * @param minLongitude the sector's minimum longitude.
   * @param maxLongitude the sector's maximum longitude.
   * @throws IllegalArgumentException if any of the angles are null
   */
  public Sector(Angle minLatitude, Angle maxLatitude, Angle minLongitude, Angle maxLongitude) {
    if (minLatitude == null
        || maxLatitude == null
        || minLongitude == null
        || maxLongitude == null) {
      throw new IllegalArgumentException("Input Angles Null");
    }

    this.minLatitude = minLatitude;
    this.maxLatitude = maxLatitude;
    this.minLongitude = minLongitude;
    this.maxLongitude = maxLongitude;
    this.deltaLat = Angle.fromDegrees(this.maxLatitude.degrees - this.minLatitude.degrees);
    this.deltaLon = Angle.fromDegrees(this.maxLongitude.degrees - this.minLongitude.degrees);
  }
Пример #10
0
 public void actionPerformed(ActionEvent actionEvent) {
   if (pathPosition < path.size() - 1) {
     BasicOrbitView view = (BasicOrbitView) wwjPanel.getWwd().getView();
     view.setHeading(Angle.fromDegrees(90));
     view.addEyePositionAnimator(4000, view.getEyePosition(), path.get(++pathPosition));
   }
 }
  @Override
  protected void draw(DrawContext dc) {
    // Capture the capabilities actually in use.
    if (this.capabilities == null) {
      this.capabilities = dc.getGLContext().getGLDrawable().getChosenGLCapabilities();
      this.hardwareStereo = this.capabilities.getStereo();
      this.inStereo = this.isHardwareStereo() ? true : this.isInStereo();
    }

    // If stereo isn't to be applied, just draw and return.
    if (!isInStereo()) {
      super.draw(dc);
      return;
    }

    // Check if pitch is in correct range (50 - 90 degrees) for current stereo implementation to
    // work correctly (temporary hack)
    View dcView = dc.getView();
    Boolean pitchInRange =
        (dcView.getPitch().compareTo(Angle.fromDegrees(50)) > 0
            && dcView.getPitch().compareTo(Angle.POS90) < 0);

    if (AVKey.STEREO_MODE_DEVICE.equals(this.stereoMode) && this.isHardwareStereo() && pitchInRange)
      this.doDrawToStereoDevice(dc);
    else if (AVKey.STEREO_MODE_RED_BLUE.equals(this.stereoMode) && pitchInRange)
      this.doDrawStereoRedBlue(dc);
    else // AVKey.STEREO_MODE_NONE
    this.doDrawStereoNone(dc);
  }
Пример #12
0
 protected static Angle clampAngle(Angle a, Angle min, Angle max) {
   double degrees = a.degrees;
   double minDegrees = min.degrees;
   double maxDegrees = max.degrees;
   return Angle.fromDegrees(
       degrees < minDegrees ? minDegrees : (degrees > maxDegrees ? maxDegrees : degrees));
 }
  protected Angle computePanAmount(
      Globe globe, OrbitView view, ScreenAnnotation control, double panStep) {
    // Compute last pick point distance relative to pan control center
    double size = control.getAttributes().getSize().width * control.getAttributes().getScale();
    Vec4 center = new Vec4(control.getScreenPoint().x, control.getScreenPoint().y + size / 2, 0);
    double px = lastPickPoint.x - center.x;
    double py = view.getViewport().getHeight() - lastPickPoint.y - center.y;
    double pickDistance = Math.sqrt(px * px + py * py);
    double pickDistanceFactor = Math.min(pickDistance / 10, 5);

    // Compute globe angular distance depending on eye altitude
    Position eyePos = view.getEyePosition();
    double radius = globe.getRadiusAt(eyePos);
    double minValue = 0.5 * (180.0 / (Math.PI * radius)); // Minimum change ~0.5 meters
    double maxValue = 1.0; // Maximum change ~1 degree

    // Compute an interpolated value between minValue and maxValue, using (eye altitude)/(globe
    // radius) as
    // the interpolant. Interpolation is performed on an exponential curve, to keep the value from
    // increasing too quickly as eye altitude increases.
    double a = eyePos.getElevation() / radius;
    a = (a < 0 ? 0 : (a > 1 ? 1 : a));
    double expBase = 2.0; // Exponential curve parameter.
    double value =
        minValue + (maxValue - minValue) * ((Math.pow(expBase, a) - 1.0) / (expBase - 1.0));

    return Angle.fromDegrees(value * pickDistanceFactor * panStep);
  }
  protected void onHorizontalTranslateRel(
      Angle forwardChange, Angle sideChange, ViewInputAttributes.ActionAttributes actionAttribs) {
    View view = this.getView();
    if (view == null) // include this test to ensure any derived implementation performs it
    {
      return;
    }

    if (forwardChange.equals(Angle.ZERO) && sideChange.equals(Angle.ZERO)) {
      return;
    }

    if (view instanceof BasicFlyView) {

      Vec4 forward = view.getForwardVector();
      Vec4 up = view.getUpVector();
      Vec4 side = forward.transformBy3(Matrix.fromAxisAngle(Angle.fromDegrees(90), up));

      forward = forward.multiply3(forwardChange.getDegrees());
      side = side.multiply3(sideChange.getDegrees());
      Vec4 eyePoint = view.getEyePoint();
      eyePoint = eyePoint.add3(forward.add3(side));
      Position newPosition = view.getGlobe().computePositionFromPoint(eyePoint);

      this.setEyePosition(this.uiAnimControl, view, newPosition, actionAttribs);
      view.firePropertyChange(AVKey.VIEW, null, view);
    }
  }
  /**
   * Select the visible grid elements
   *
   * @param dc the current <code>DrawContext</code>.
   */
  protected void selectRenderables(DrawContext dc) {
    if (dc == null) {
      String message = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    Sector vs = dc.getVisibleSector();
    OrbitView view = (OrbitView) dc.getView();
    // Compute labels offset from view center
    Position centerPos = view.getCenterPosition();
    Double pixelSizeDegrees =
        Angle.fromRadians(
                view.computePixelSizeAtDistance(view.getZoom())
                    / dc.getGlobe().getEquatorialRadius())
            .degrees;
    Double labelOffsetDegrees = pixelSizeDegrees * view.getViewport().getWidth() / 4;
    Position labelPos =
        Position.fromDegrees(
            centerPos.getLatitude().degrees - labelOffsetDegrees,
            centerPos.getLongitude().degrees - labelOffsetDegrees,
            0);
    Double labelLatDegrees = labelPos.getLatitude().normalizedLatitude().degrees;
    labelLatDegrees = Math.min(Math.max(labelLatDegrees, -76), 78);
    labelPos =
        new Position(
            Angle.fromDegrees(labelLatDegrees), labelPos.getLongitude().normalizedLongitude(), 0);

    if (vs != null) {
      for (GridElement ge : this.gridElements) {
        if (ge.isInView(dc)) {
          if (ge.renderable instanceof GeographicText) {
            GeographicText gt = (GeographicText) ge.renderable;
            if (labelPos.getLatitude().degrees < 72
                || "*32*34*36*".indexOf("*" + gt.getText() + "*") == -1) {
              // Adjust label position according to eye position
              Position pos = gt.getPosition();
              if (ge.type.equals(GridElement.TYPE_LATITUDE_LABEL))
                pos =
                    Position.fromDegrees(
                        pos.getLatitude().degrees,
                        labelPos.getLongitude().degrees,
                        pos.getElevation());
              else if (ge.type.equals(GridElement.TYPE_LONGITUDE_LABEL))
                pos =
                    Position.fromDegrees(
                        labelPos.getLatitude().degrees,
                        pos.getLongitude().degrees,
                        pos.getElevation());

              gt.setPosition(pos);
            }
          }

          this.graticuleSupport.addRenderable(ge.renderable, GRATICULE_UTM);
        }
      }
      // System.out.println("Total elements: " + count + " visible sector: " + vs);
    }
  }
Пример #16
0
 static Angle computeColumnLongitude(int column, Angle delta) {
   if (delta == null) {
     String msg = Logging.getMessage("nullValue.AngleIsNull");
     Logging.logger().severe(msg);
     throw new IllegalArgumentException(msg);
   }
   return Angle.fromDegrees(-180 + delta.getDegrees() * column);
 }
Пример #17
0
 static Angle computeRowLatitude(int row, Angle delta) {
   if (delta == null) {
     String msg = Logging.getMessage("nullValue.AngleIsNull");
     Logging.logger().severe(msg);
     throw new IllegalArgumentException(msg);
   }
   return Angle.fromDegrees(-90d + delta.getDegrees() * row);
 }
Пример #18
0
  private static LevelSet makeLevels() {
    AVList params = new AVListImpl();

    params.setValue(AVKey.TILE_WIDTH, 256);
    params.setValue(AVKey.TILE_HEIGHT, 256);
    params.setValue(AVKey.DATA_CACHE_NAME, "NearMap");
    params.setValue(AVKey.SERVICE, "http://www.nearmap.com/maps/");
    params.setValue(AVKey.DATASET_NAME, "Vert");
    params.setValue(AVKey.FORMAT_SUFFIX, ".jpg");
    params.setValue(AVKey.NUM_LEVELS, 31 - 3);
    params.setValue(AVKey.NUM_EMPTY_LEVELS, 0);
    params.setValue(
        AVKey.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(22.5d), Angle.fromDegrees(45d)));
    params.setValue(AVKey.SECTOR, new MercatorSector(-1.0, 1.0, Angle.NEG180, Angle.POS180));
    params.setValue(AVKey.TILE_URL_BUILDER, new URLBuilder());

    return new LevelSet(params);
  }
Пример #19
0
  /**
   * Obtains the difference of these two <code>Angle</code>s. Does not accept a null argument. This
   * method is not commutative. Neither this <code>Angle</code> nor <code>angle</code> is changed,
   * instead the result is returned as a new <code>Angle</code>.
   *
   * @param angle the <code>Angle</code> to subtract from this <code>Angle</code>
   * @return a new <code>Angle</code> correpsonding to this <code>Angle</code>'s size minus <code>
   *     angle</code>'s size
   * @throws IllegalArgumentException if <code>angle</code> is null
   */
  public final Angle subtract(Angle angle) {
    if (angle == null) {
      String message = Logging.getMessage("nullValue.AngleIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    return Angle.fromDegrees(this.degrees - angle.degrees);
  }
Пример #20
0
  /**
   * Obtains the average of two <code>Angle</code>s. This method is commutative, so <code>
   * midAngle(m, n)</code> and <code>midAngle(n, m)</code> are equivalent.
   *
   * @param a1 the first <code>Angle</code>
   * @param a2 the second <code>Angle</code>
   * @return the average of <code>a1</code> and <code>a2</code> throws IllegalArgumentException if
   *     either angle is null
   */
  public static Angle midAngle(Angle a1, Angle a2) {
    if (a1 == null || a2 == null) {
      String message = Logging.getMessage("nullValue.AngleIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    return Angle.fromDegrees(0.5 * (a1.degrees + a2.degrees));
  }
Пример #21
0
  /**
   * Obtains the average of three <code>Angle</code>s. The order of parameters does not matter.
   *
   * @param a the first <code>Angle</code>
   * @param b the second <code>Angle</code>
   * @param c the third <code>Angle</code>
   * @return the average of <code>a1</code>, <code>a2</code> and <code>a3</code>
   * @throws IllegalArgumentException if <code>a</code>, <code>b</code> or <code>c</code> is null
   */
  public static Angle average(Angle a, Angle b, Angle c) {
    if (a == null || b == null || c == null) {
      String message = Logging.getMessage("nullValue.AngleIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    return Angle.fromDegrees((a.degrees + b.degrees + c.degrees) / 3);
  }
Пример #22
0
  public static Angle normalizedLongitude(Angle unnormalizedAngle) {
    if (unnormalizedAngle == null) {
      String msg = Logging.getMessage("nullValue.AngleIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    return Angle.fromDegrees(normalizedDegreesLongitude(unnormalizedAngle.degrees));
  }
Пример #23
0
  public static Angle centralMeridianForZone(int zone) {
    if (zone < 1 || zone > 60) {
      String message = Logging.getMessage("UTM.InvalidZone", zone);
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    return Angle.fromDegrees((3 + (zone - 1) * 6) - (zone > 30 ? 360 : 0));
  }
  protected void onRotateView(
      double headingInput,
      double pitchInput,
      double totalHeadingInput,
      double totalPitchInput,
      ViewInputAttributes.DeviceAttributes deviceAttributes,
      ViewInputAttributes.ActionAttributes actionAttributes) {

    Angle headingChange;
    Angle pitchChange;
    this.stopGoToAnimators();
    headingChange =
        Angle.fromDegrees(
            totalHeadingInput * getScaleValueElevation(deviceAttributes, actionAttributes));
    pitchChange =
        Angle.fromDegrees(
            totalPitchInput * getScaleValueElevation(deviceAttributes, actionAttributes));
    onRotateView(headingChange, pitchChange, actionAttributes);
  }
Пример #25
0
  @Override
  public void doDefaultAction(final IGlobeApplication application) {
    if (isEnabled()) {
      final Sector sector = getExtent();

      final double altitude = application.calculateAltitudeForZooming(sector);
      application.goTo(
          new Position(sector.getCentroid(), 0), Angle.ZERO, Angle.fromDegrees(75), altitude);
    }
  }
Пример #26
0
 /**
  * Computes the lat/lon of the pickPoint over the world map
  *
  * @param dc the current <code>DrawContext</code>
  * @param locationSW the screen location of the bottom left corner of the map
  * @param mapSize the world map screen dimension in pixels
  * @return the picked Position
  */
 protected Position computePickPosition(DrawContext dc, Vec4 locationSW, Dimension mapSize) {
   Position pickPosition = null;
   Point pickPoint = dc.getPickPoint();
   if (pickPoint != null) {
     Rectangle viewport = dc.getView().getViewport();
     // Check if pickpoint is inside the map
     if (pickPoint.getX() >= locationSW.getX()
         && pickPoint.getX() < locationSW.getX() + mapSize.width
         && viewport.height - pickPoint.getY() >= locationSW.getY()
         && viewport.height - pickPoint.getY() < locationSW.getY() + mapSize.height) {
       double lon = (pickPoint.getX() - locationSW.getX()) / mapSize.width * 360 - 180;
       double lat =
           (viewport.height - pickPoint.getY() - locationSW.getY()) / mapSize.height * 180 - 90;
       double pickAltitude = 1000e3;
       pickPosition = new Position(Angle.fromDegrees(lat), Angle.fromDegrees(lon), pickAltitude);
     }
   }
   return pickPosition;
 }
 protected Angle computeLookPitch(OrbitView view, ScreenAnnotation control, double pitchStep) {
   // Compute last pick point 'pitch' relative to look control center on y
   double size = control.getAttributes().getSize().width * control.getAttributes().getScale();
   Vec4 center = new Vec4(control.getScreenPoint().x, control.getScreenPoint().y + size / 2, 0);
   double py = view.getViewport().getHeight() - lastPickPoint.y - center.y;
   double pickDistanceFactor = Math.min(Math.abs(py) / 3000, 5) * Math.signum(py);
   // New pitch
   Angle pitch = view.getPitch().add(Angle.fromRadians(pitchStep * pickDistanceFactor));
   pitch = pitch.degrees >= 0 ? (pitch.degrees <= 90 ? pitch : Angle.fromDegrees(90)) : Angle.ZERO;
   return pitch;
 }
  @Override
  public void beforeComputeMatrices(IDelegateView view) {
    if (shouldRenderForHMD()) {
      // double hfovRadians = Math.atan(eyeFov[eye].LeftTan) + Math.atan(eyeFov[eye].RightTan);
      // double vfovRadians = Math.atan(eyeFov[eye].UpTan) + Math.atan(eyeFov[eye].DownTan);
      // view.setFieldOfView(Angle.fromRadians(hfovRadians));

      // TODO hmmm, something about the reported Oculus FOV above and the World Wind's View FOV
      // don't mix very well, so set it manually to a high FOV to ensure all tiles are rendered:
      view.setFieldOfView(Angle.fromDegrees(130));
    }
  }
  protected Angle normalizedAzimuth(Angle azimuth) {
    if (azimuth == null) {
      String message = "nullValue.AzimuthIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    double degrees = azimuth.degrees;
    double normalizedDegrees =
        degrees < 0.0 ? degrees + 360.0 : (degrees >= 360.0 ? degrees - 360.0 : degrees);
    return Angle.fromDegrees(normalizedDegrees);
  }
  /**
   * Called when the roll changes due to user input.
   *
   * @param rollInput Change in roll.
   * @param deviceAttributes Attributes of the input device.
   * @param actionAttributes Action that caused the change.
   */
  protected void onRoll(
      double rollInput,
      ViewInputAttributes.DeviceAttributes deviceAttributes,
      ViewInputAttributes.ActionAttributes actionAttributes) {
    Angle rollChange;
    this.stopGoToAnimators();

    rollChange =
        Angle.fromDegrees(rollInput * getScaleValueElevation(deviceAttributes, actionAttributes));

    this.onRoll(rollChange, actionAttributes);
  }