/******************************************************************************* * 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.orbit; 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.globes.Globe; /** * Basic implementation of {@link IViewState}. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class ViewState implements IViewState { //state protected Position center = Position.ZERO; protected Angle heading = Angle.ZERO; protected Angle pitch = Angle.ZERO; protected Angle roll = Angle.ZERO; protected double zoom = 1.0; //cached derived values protected Globe lastGlobe; protected Vec4 lastUp; protected Vec4 lastForward; protected Vec4 lastSide; protected Matrix lastTransform; protected Matrix lastRotation; protected Matrix lastRotationInverse; protected Vec4 lastCenterPoint; protected Vec4 lastEyePoint; protected Position lastEye; //semaphore for cached values protected final Object cacheSemaphore = new Object(); protected void clearCachedValues() { synchronized (cacheSemaphore) { lastUp = null; lastForward = null; lastSide = null; lastTransform = null; lastRotation = null; lastRotationInverse = null; lastCenterPoint = null; lastEyePoint = null; lastEye = null; } } protected void clearCachedValuesIfGlobeChanged(Globe globe) { if (lastGlobe != globe) { clearCachedValues(); lastGlobe = globe; } } @Override public Position getCenter() { return center; } @Override public void setCenter(Position center) { this.center = center; clearCachedValues(); } @Override public Angle getHeading() { return heading; } @Override public void setHeading(Angle heading) { this.heading = heading.normalizedLongitude(); clearCachedValues(); } @Override public Angle getPitch() { return pitch; } @Override public void setPitch(Angle pitch) { this.pitch = pitch.normalizedLongitude(); clearCachedValues(); } @Override public Angle getRoll() { return roll; } @Override public void setRoll(Angle roll) { this.roll = roll.normalizedLongitude(); clearCachedValues(); } @Override public double getZoom() { return zoom; } @Override public void setZoom(double zoom) { this.zoom = zoom; clearCachedValues(); } @Override public Matrix getRotation(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastRotation == null) { Matrix rotationInverse = getRotationInverse(globe); if (rotationInverse == null) { rotationInverse = Matrix.IDENTITY; } lastRotation = rotationInverse.getInverse(); if (lastRotation == null) { //last resort if rotation couldn't be found lastRotation = Matrix.IDENTITY; } } return lastRotation; } } @Override public Matrix getRotationInverse(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastRotationInverse == null) { //compute heading/pitch/roll transform Matrix transform = Matrix.fromRotationZ(roll).multiply( Matrix.fromRotationXYZ(pitch, Angle.ZERO, heading.multiply(-1))); //compute rotation for current position on globe Vec4 up = globe.computeNorthPointingTangentAtLocation(center.latitude, center.longitude); Vec4 f = globe.computeSurfaceNormalAtLocation(center.latitude, center.longitude); Vec4 s = f.cross3(up).normalize3(); Vec4 u = s.cross3(f).normalize3(); Matrix rotation = new Matrix( s.x, s.y, s.z, 0.0, u.x, u.y, u.z, 0.0, -f.x, -f.y, -f.z, 0.0, 0.0, 0.0, 0.0, 1.0); //multiply the two rotations to get the center->eye rotation matrix lastRotationInverse = transform.multiply(rotation); } return lastRotationInverse; } } @Override public Vec4 getForward(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastForward == null) { Matrix rotation = getRotation(globe); lastForward = lastSide != null && lastUp != null ? lastSide.cross3(lastUp) : Vec4.UNIT_Z.transformBy3(rotation); } return lastForward; } } @Override public Vec4 getUp(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastUp == null) { Matrix rotation = getRotation(globe); lastUp = lastForward != null && lastSide != null ? lastForward.cross3(lastSide) : Vec4.UNIT_Y.transformBy3(rotation); } return lastUp; } } @Override public Vec4 getSide(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastSide == null) { Matrix rotation = getRotation(globe); lastSide = lastUp != null && lastForward != null ? lastUp.cross3(lastForward) : Vec4.UNIT_X.transformBy3(rotation); } return lastSide; } } @Override public Matrix getTransform(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastTransform == null) { //gluLookAt defines s as (f x u), but getSide returns (u x f), so negate it during matrix creation Vec4 s = getSide(globe); Vec4 u = getUp(globe); Vec4 f = getForward(globe); Vec4 eye = getEyePoint(globe); Matrix mAxes = new Matrix( -s.x, -s.y, -s.z, 0.0, u.x, u.y, u.z, 0.0, -f.x, -f.y, -f.z, 0.0, 0.0, 0.0, 0.0, 1.0); Matrix mEye = Matrix.fromTranslation(-eye.x, -eye.y, -eye.z); lastTransform = mAxes.multiply(mEye); } return lastTransform; } } @Override public Vec4 getCenterPoint(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastCenterPoint == null) { lastCenterPoint = globe.computePointFromPosition(center); } return lastCenterPoint; } } @Override public Vec4 getEyePoint(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastEyePoint == null) { Vec4 center = getCenterPoint(globe); Vec4 forward = getForward(globe); lastEyePoint = center.add3(forward.multiply3(-zoom)); } return lastEyePoint; } } @Override public Position getEye(Globe globe) { synchronized (cacheSemaphore) { clearCachedValuesIfGlobeChanged(globe); if (lastEye == null) { lastEye = globe.computePositionFromPoint(getEyePoint(globe)); } return lastEye; } } @Override public void setEye(Position eye, Globe globe) { Vec4 centerPoint = getCenterPoint(globe); Vec4 eyePoint = globe.computePointFromPosition(eye); Vec4 normal = globe.computeSurfaceNormalAtPoint(centerPoint); Vec4 lookAtPoint = centerPoint.subtract3(normal); Vec4 north = globe.computeNorthPointingTangentAtLocation(center.getLatitude(), center.getLongitude()); Matrix centerTransform = Matrix.fromViewLookAt(centerPoint, lookAtPoint, north); Matrix centerTransformInv = centerTransform.getInverse(); if (centerTransformInv != null) { //if eye lat/lon != center lat/lon, then the surface normal will be a good value for the up direction Vec4 up = normal; //otherwise, estimate the up direction by using the *current* heading with the new center position Vec4 forward = centerPoint.subtract3(eyePoint).normalize3(); if (forward.cross3(up).getLength3() < 0.001) { Matrix modelview = Matrix.fromTranslation(0, 0, -1) .multiply(Matrix.fromRotationZ(heading)).multiply(centerTransform); Matrix modelviewInv = modelview.getInverse(); if (modelviewInv != null) { up = Vec4.UNIT_Y.transformBy4(modelviewInv); } } Matrix modelTransform = Matrix.fromViewLookAt(eyePoint, centerPoint, up); Matrix hpzTransform = modelTransform.multiply(centerTransformInv); Angle heading = hpzTransform.getRotationZ(); Angle pitch = hpzTransform.getRotationX(); Vec4 translation = hpzTransform.getTranslation(); if (heading != null && pitch != null && translation != null) { //success! setHeading(heading); setPitch(pitch.multiply(-1)); setZoom(translation.getLength3()); return; } } //center transform wasn't invertable, or couldn't find heading/pitch/zoom from transform, so just reset the view setPitch(Angle.ZERO); setHeading(Angle.ZERO); setZoom(eye.elevation); setCenter(new Position(eye, 0)); } }