/******************************************************************************* * Copyright 2014 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.target; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.geom.Angle; 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.view.ViewUtil; import gov.nasa.worldwind.view.orbit.OrbitView; import java.awt.Point; import java.awt.Rectangle; import java.nio.FloatBuffer; import javax.media.opengl.GL2; import au.gov.ga.earthsci.worldwind.common.view.orbit.BaseOrbitView; /** * {@link OrbitView} implementation of {@link ITargetView}. * <p/> * Also draws an optional axis marker whenever the view changes to indicate the * current center of rotation. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class TargetOrbitView extends BaseOrbitView implements ITargetView { protected boolean targetMode = false; protected boolean prioritizeFarClipping = true; protected static final double MINIMUM_NEAR_DISTANCE = 1; protected static final double MAXIMUM_FAR_NEAR_RATIO = 10000; protected static final double MAXIMUM_NEAR_FAR_RATIO = 100000; protected boolean nonTargetModeDetectCollisions = true; protected boolean targetModeDetectCollisions = false; protected Angle nonTargetMaxPitch = DEFAULT_MAX_PITCH; protected Angle targetMaxPitch = Angle.fromDegrees(170); protected boolean drawAxisMarker = true; protected Vec4 lastEye = Vec4.ZERO; protected final AxisRenderable axisMarker = new AxisRenderable(); protected final EmptyScreenCredit viewScreenCredit = new EmptyScreenCredit() { @Override public void render(DrawContext dc) { TargetOrbitView.this.render(dc); } }; protected final FloatBuffer depthPixel = FloatBuffer.allocate(1); protected Position mousePosition; @Override public boolean isTargetMode() { return targetMode; } @Override public void setTargetMode(boolean targetMode) { if (this.targetMode == targetMode) { return; } this.targetMode = targetMode; Angle[] pitchLimits = this.viewLimits.getPitchLimits(); if (targetMode) { this.nonTargetMaxPitch = pitchLimits[1]; pitchLimits[1] = this.targetMaxPitch; this.nonTargetModeDetectCollisions = isDetectCollisions(); setDetectCollisions(this.targetModeDetectCollisions); } else { this.targetMaxPitch = pitchLimits[1]; pitchLimits[1] = this.nonTargetMaxPitch; this.targetModeDetectCollisions = isDetectCollisions(); setDetectCollisions(this.nonTargetModeDetectCollisions); } this.viewLimits.setPitchLimits(pitchLimits[0], pitchLimits[1]); } @Override public boolean isDrawAxisMarker() { return drawAxisMarker; } @Override public void setDrawAxisMarker(boolean drawAxisMarker) { this.drawAxisMarker = drawAxisMarker; } @Override public boolean isPrioritizeFarClipping() { return prioritizeFarClipping; } @Override public void setPrioritizeFarClipping(boolean prioritizeFarClipping) { this.prioritizeFarClipping = prioritizeFarClipping; firePropertyChange(AVKey.VIEW, null, this); } @Override public AxisRenderable getAxisMarker() { return axisMarker; } @Override public Position getMousePosition() { return mousePosition; } @Override public void focusOnViewportCenter() { if (isTargetMode()) { //if we are in target mode, the center point can be changed by the user, so don't change it automatically return; } super.focusOnViewportCenter(); } @Override protected void doApply(DrawContext dc) { super.doApply(dc); //the screen credits are stored in a map, so adding this each frame is not a problem dc.addScreenCredit(viewScreenCredit); if (isDrawAxisMarker()) { Vec4 eye = getEyePoint(); if (lastEye.distanceToSquared3(eye) > 10) { //view has changed, so show the axis marker axisMarker.trigger(); } lastEye = eye; } } /** * Render method is called during the rendering of the scene controller's * screen credits, but the {@link #viewScreenCredit}. * * @param dc */ protected void render(DrawContext dc) { if (isDrawAxisMarker()) { axisMarker.render(dc); } if (viewInputHandler instanceof TargetOrbitViewInputHandler) { //calculate mouse position in geographic coordinates Point mousePoint = ((TargetOrbitViewInputHandler) viewInputHandler).getMousePoint(); if (mousePoint == null) { mousePosition = null; return; } GL2 gl = dc.getGL().getGL2(); Rectangle viewport = getViewport(); int winX = mousePoint.x; int winY = viewport.height - mousePoint.y - 1; gl.glReadPixels(winX, winY, 1, 1, GL2.GL_DEPTH_COMPONENT, GL2.GL_FLOAT, depthPixel.rewind()); float winZ = depthPixel.get(0); //see gluUnproject: Matrix mvpi = projection.multiply(modelview).getInverse(); Vec4 screen = new Vec4( 2.0 * (winX - viewport.x) / viewport.width - 1.0, 2.0 * (winY - viewport.y) / viewport.height - 1.0, 2.0 * winZ - 1.0, 1.0); Vec4 model = screen.transformBy4(mvpi); model = model.divide3(model.w); mousePosition = globe.computePositionFromPoint(model); } } @Override protected double computeNearClipDistance() { double near = Math.max(super.computeNearClipDistance(), MINIMUM_NEAR_DISTANCE); if (shouldPrioritizeFarClipping()) { double far = computeFarClipDistance(); return Math.max(near, far / MAXIMUM_FAR_NEAR_RATIO); } return near; } @Override protected double computeFarClipDistance() { double elevation = getCurrentEyePosition().elevation; double far = elevation + globe.getDiameter(); if (!shouldPrioritizeFarClipping()) { far = super.computeFarClipDistance(); // double near = computeNearClipDistance(); // return Math.min(far, near * MAXIMUM_NEAR_FAR_RATIO); } return far; } protected boolean shouldPrioritizeFarClipping() { //always prioritize far clipping when below the earth's surface return isPrioritizeFarClipping() || isBelowSurface(); } protected boolean isBelowSurface() { if (dc == null) { return false; } Position eyePosition = getEyePosition(); double altitude = ViewUtil.computeElevationAboveSurface(dc, eyePosition); return altitude < 0; } }