/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.view.orbit; import gov.nasa.worldwind.Configuration; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.awt.ViewInputHandler; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.Frustum; import gov.nasa.worldwind.geom.Matrix; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.view.ViewPropertyLimits; import gov.nasa.worldwind.view.orbit.BasicOrbitViewLimits; import gov.nasa.worldwind.view.orbit.OrbitView; import gov.nasa.worldwind.view.orbit.OrbitViewLimits; import javax.media.opengl.GL; /** * Better {@link OrbitView} implementation. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class BaseOrbitView extends AbstractView implements OrbitView { protected final static double DEFAULT_MIN_ELEVATION = 0; protected final static double DEFAULT_MAX_ELEVATION = 4000000; protected final static Angle DEFAULT_MIN_PITCH = Angle.fromDegrees(0); protected final static Angle DEFAULT_MAX_PITCH = Angle.fromDegrees(120); protected final IViewState state; protected OrbitViewLimits viewLimits; protected boolean outOfFocus = false; protected final BaseOrbitViewCollisionSupport collisionSupport = new BaseOrbitViewCollisionSupport(); protected boolean resolvingCollisions; protected Position appliedEyePosition; protected Vec4 appliedEyePoint; protected Position unsetEyePosition; public BaseOrbitView() { this.state = createViewState(); this.viewInputHandler = createViewInputHandler(); this.viewLimits = createOrbitViewLimits(); this.collisionSupport.setCollisionThreshold(COLLISION_THRESHOLD); this.collisionSupport.setNumIterations(COLLISION_NUM_ITERATIONS); loadConfigurationValues(); } protected IViewState createViewState() { return new ViewState(); } protected ViewInputHandler createViewInputHandler() { return (ViewInputHandler) WorldWind.createConfigurationComponent(AVKey.VIEW_INPUT_HANDLER_CLASS_NAME); } protected OrbitViewLimits createOrbitViewLimits() { OrbitViewLimits viewLimits = new BasicOrbitViewLimits(); viewLimits.setPitchLimits(DEFAULT_MIN_PITCH, DEFAULT_MAX_PITCH); viewLimits.setEyeElevationLimits(DEFAULT_MIN_ELEVATION, DEFAULT_MAX_ELEVATION); return viewLimits; } protected void loadConfigurationValues() { Position center = getCenterPosition(); Double initLat = Configuration.getDoubleValue(AVKey.INITIAL_LATITUDE); Double initLon = Configuration.getDoubleValue(AVKey.INITIAL_LONGITUDE); double initElev = center.getElevation(); // Set center latitude and longitude. Do not change center elevation. if (initLat != null && initLon != null) { setCenterPosition(Position.fromDegrees(initLat, initLon, initElev)); } else if (initLat != null) { setCenterPosition(Position.fromDegrees(initLat, center.getLongitude().degrees, initElev)); } else if (initLon != null) { setCenterPosition(Position.fromDegrees(center.getLatitude().degrees, initLon, initElev)); } Double initHeading = Configuration.getDoubleValue(AVKey.INITIAL_HEADING); if (initHeading != null) { setHeading(Angle.fromDegrees(initHeading)); } Double initPitch = Configuration.getDoubleValue(AVKey.INITIAL_PITCH); if (initPitch != null) { setPitch(Angle.fromDegrees(initPitch)); } Double initAltitude = Configuration.getDoubleValue(AVKey.INITIAL_ALTITUDE); if (initAltitude != null) { setZoom(initAltitude); } Double initFov = Configuration.getDoubleValue(AVKey.FOV); if (initFov != null) { setFieldOfView(Angle.fromDegrees(initFov)); } } public IViewState getState() { return state; } @Override public Position getEyePosition() { if (appliedEyePosition != null) { return appliedEyePosition; } return getCurrentEyePosition(); } @Override public Vec4 getEyePoint() { if (appliedEyePoint != null) { return appliedEyePoint; } return getCurrentEyePoint(); } @Override public Vec4 getUpVector() { if (globe == null) { return Vec4.ZERO; } return state.getUp(globe); } @Override public Vec4 getForwardVector() { if (globe == null) { return Vec4.ZERO; } return state.getForward(globe); } @Override public Vec4 getCenterPoint() { if (globe == null) { return Vec4.ZERO; } return state.getCenterPoint(globe); } @Override public Position getCenterPosition() { return state.getCenter(); } @Override public void setCenterPosition(Position center) { state.setCenter(center); resolveCollisionsWithPitch(); markOutOfFocus(); } @Override public Vec4 getCurrentEyePoint() { if (globe == null) { return Vec4.ZERO; } return state.getEyePoint(globe); } @Override public Position getCurrentEyePosition() { if (globe == null) { if (unsetEyePosition != null) { return unsetEyePosition; } return Position.ZERO; } return state.getEye(globe); } @Override public void setOrientation(Position eyePosition, Position centerPosition) { setCenterPosition(centerPosition); setEyePosition(eyePosition); } @Override public void setEyePosition(Position eyePosition) { if (globe == null) { unsetEyePosition = eyePosition; return; } unsetEyePosition = null; state.setEye(eyePosition, globe); resolveCollisionsWithPitch(); markOutOfFocus(); } @Override public double getZoom() { return state.getZoom(); } @Override public void setZoom(double zoom) { state.setZoom(zoom); resolveCollisionsWithPitch(); markOutOfFocus(); } @Override public Angle getHeading() { return state.getHeading(); } @Override public void setHeading(Angle heading) { state.setHeading(heading); resolveCollisionsWithPitch(); focusOnViewportCenter(); } @Override public Angle getPitch() { return state.getPitch(); } @Override public void setPitch(Angle pitch) { state.setPitch(pitch); resolveCollisionsWithPitch(); focusOnViewportCenter(); } @Override public Angle getRoll() { return state.getRoll(); } @Override public void setRoll(Angle roll) { state.setRoll(roll); focusOnViewportCenter(); } protected void resolveCollisionsWithPitch() { if (this.dc == null) { return; } if (!isDetectCollisions() || resolvingCollisions) { return; } resolvingCollisions = true; // Compute the near distance corresponding to the current set of values. // If there is no collision, 'newPitch' will be null. Otherwise it will contain a value // that will resolve the collision. double nearDistance = this.computeNearDistance(this.getCurrentEyePosition()); Angle newPitch = this.collisionSupport.computePitchToResolveCollision(this, nearDistance, this.dc); if (newPitch != null) { setPitch(newPitch); flagHadCollisions(); } resolvingCollisions = false; } protected void flagHadCollisions() { this.hadCollisions = true; } @Override public ViewPropertyLimits getViewPropertyLimits() { return viewLimits; } @Override public OrbitViewLimits getOrbitViewLimits() { return viewLimits; } @Override public void setOrbitViewLimits(OrbitViewLimits limits) { this.viewLimits = limits; } protected void markOutOfFocus() { outOfFocus = true; } @Override public boolean canFocusOnViewportCenter() { if (this.dc == null || this.globe == null) { //cannot focus on viewport center until the view has been applied at least once return false; } if (this.isAnimating()) { //don't change the viewport center (rotation point) while the user is in the middle of changing the view return false; } if (Math.abs(this.getPitch().degrees) >= 90) { //don't try and focus on the viewport center if the user is pitched below the surface return false; } if (this.dc.getViewportCenterPosition() == null) { //cannot focus on a null point! return false; } return true; } @Override public void focusOnViewportCenter() { if (!canFocusOnViewportCenter()) { return; } if (!outOfFocus) { return; } outOfFocus = false; //calculate the center point in cartesian space Position viewportCenter = this.dc.getViewportCenterPosition(); double elevation = this.globe.getElevation(viewportCenter.latitude, viewportCenter.longitude); Position viewportExaggerated = new Position(viewportCenter, elevation * dc.getVerticalExaggeration()); Vec4 viewportCenterPoint = this.globe.computePointFromPosition(viewportExaggerated); //find a point along the forward vector so the view doesn't appear to change, only the distance from the center point Vec4 eyePoint = getCurrentEyePoint(); Vec4 forward = getForwardVector(); double distance = eyePoint.distanceTo3(viewportCenterPoint); Vec4 newCenterPoint = Vec4.fromLine3(eyePoint, distance, forward); state.setCenter(globe.computePositionFromPoint(newCenterPoint)); state.setZoom(distance); } @Override public void stopMovementOnCenter() { firePropertyChange(CENTER_STOPPED, null, null); } @Override protected void doApply(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); //$NON-NLS-1$ Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (dc.getGL() == null) { String message = Logging.getMessage("nullValue.DrawingContextGLIsNull"); //$NON-NLS-1$ Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (dc.getGlobe() == null) { String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull"); //$NON-NLS-1$ Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (unsetEyePosition != null) { setEyePosition(unsetEyePosition); } beforeComputeMatrices(); //========== modelview matrix state ==========// // Compute the current modelview matrix. this.modelview = computeModelView(); if (this.modelview == null) { this.modelview = Matrix.IDENTITY; } // Compute the current inverse-modelview matrix. this.modelviewInv = this.modelview.getInverse(); if (this.modelviewInv == null) { this.modelviewInv = Matrix.IDENTITY; } //========== projection matrix state ==========// // Get the current OpenGL viewport state. this.viewport = computeViewport(dc); // Compute the current clip plane distances. this.nearClipDistance = this.computeNearClipDistance(); this.farClipDistance = this.computeFarClipDistance(); // Compute the current projection matrix. this.projection = computeProjection(this.fieldOfView, this.nearClipDistance, this.farClipDistance); // Compute the current frustum. this.frustum = computeFrustum(this.nearClipDistance, this.farClipDistance); //========== load GL matrix state ==========// loadGLViewState(dc, this.modelview, this.projection); afterDoApply(); } protected void afterDoApply() { // Establish frame-specific values. this.horizonDistance = this.computeHorizonDistance(); this.appliedEyePosition = getCurrentEyePosition(); this.appliedEyePoint = getCurrentEyePoint(); // Clear cached computations. this.lastFrustumInModelCoords = null; } protected void beforeComputeMatrices() { } protected java.awt.Rectangle computeViewport(DrawContext dc) { int[] viewportArray = new int[4]; this.dc.getGL().glGetIntegerv(GL.GL_VIEWPORT, viewportArray, 0); return new java.awt.Rectangle(viewportArray[0], viewportArray[1], viewportArray[2], viewportArray[3]); } protected Matrix computeModelView() { if (globe == null) { return Matrix.IDENTITY; } return state.getTransform(globe); } protected Matrix computeProjection(Angle horizontalFieldOfView, double nearDistance, double farDistance) { double viewportWidth = this.viewport.width <= 0.0 ? 1.0 : this.viewport.width; double viewportHeight = this.viewport.height <= 0.0 ? 1.0 : this.viewport.height; return Matrix.fromPerspective(horizontalFieldOfView, viewportWidth, viewportHeight, nearDistance, farDistance); } protected Frustum computeFrustum(double nearDistance, double farDistance) { int viewportWidth = this.viewport.width <= 0.0 ? 1 : (int) this.viewport.width; int viewportHeight = this.viewport.height <= 0.0 ? 1 : (int) this.viewport.height; return Frustum.fromPerspective(this.fieldOfView, viewportWidth, viewportHeight, nearDistance, farDistance); } }