/*******************************************************************************
* 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.rotate;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.ViewInputAttributes.ActionAttributes;
import gov.nasa.worldwind.awt.ViewInputAttributes.DeviceAttributes;
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;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.view.ViewUtil;
import gov.nasa.worldwind.view.orbit.BasicOrbitView;
import gov.nasa.worldwind.view.orbit.OrbitView;
import gov.nasa.worldwind.view.orbit.OrbitViewInputHandler;
import gov.nasa.worldwind.view.orbit.OrbitViewInputSupport;
import gov.nasa.worldwind.view.orbit.OrbitViewInputSupport.OrbitViewState;
import au.gov.ga.earthsci.worldwind.common.view.delegate.DelegateOrbitViewInputHandler;
import au.gov.ga.earthsci.worldwind.common.view.delegate.IDelegateView;
/**
* {@link OrbitViewInputHandler} subclass that adds support for free rotation of
* the globe (without it being fixed around the north-south axis).
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class FreeRotateOrbitViewInputHandler extends DelegateOrbitViewInputHandler
{
/**
* Rotate the globe by an amount in a given direction.
*
* @param direction
* Direction to rotate
* @param amount
* Amount to rotate
* @param deviceAttributes
* @param actionAttributes
*/
public void onRotateFree(Angle direction, Angle amount, DeviceAttributes deviceAttributes,
ActionAttributes actionAttributes)
{
if (!(getView() instanceof OrbitView))
{
String message = "View must be an instance of OrbitView";
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
amount = Angle.fromDegrees(amount.degrees * getScaleValueHorizTransRel(deviceAttributes, actionAttributes));
OrbitView view = getView();
//if this view hasn't been applied yet, its globe will be null
if (view.getGlobe() == null)
{
return;
}
//backup the pitch (set it later so it isn't changed by this method)
Angle pitch = view.getPitch();
//get the eye point and normalize it
Vec4 centerPoint = view.getCenterPoint();
Vec4 centerPointNormalized = centerPoint.normalize3();
//find the current left vector
Matrix modelview = view.getModelviewMatrix();
Matrix modelviewInv = modelview.getInverse();
Vec4 left = Vec4.UNIT_X.transformBy4(modelviewInv);
//rotate the left vector around the forward vector
Matrix translationRotation = Matrix.fromAxisAngle(direction, centerPointNormalized);
Vec4 leftRotated = left.transformBy4(translationRotation);
//calculate the new eye point by rotating it around the rotated left vector
Matrix rotation = Matrix.fromAxisAngle(amount, leftRotated);
Vec4 newCenterPoint = centerPoint.transformBy4(rotation);
//calculate the new eye position
Position newCenterPosition = view.getGlobe().computePositionFromPoint(newCenterPoint);
view.setCenterPosition(newCenterPosition);
//compute the new heading
if (view instanceof IDelegateView)
{
modelview = ((IDelegateView) view).getPretransformedModelView();
}
Matrix newModelview = modelview.multiply(rotation);
Angle newHeading = calculateHeading(view, newModelview);
view.setHeading(newHeading);
view.setPitch(pitch);
if (view instanceof BasicOrbitView)
{
((BasicOrbitView) view).computeAndSetViewCenter();
}
view.focusOnViewportCenter();
view.firePropertyChange(AVKey.VIEW, null, view);
}
protected Angle calculateHeading(OrbitView view, Matrix modelview)
{
Globe globe = view.getGlobe();
Position center = view.getCenterPosition();
Vec4 centerPoint = globe.computePointFromPosition(center);
Vec4 normal = globe.computeSurfaceNormalAtLocation(center.getLatitude(), center.getLongitude());
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)
{
String message = Logging.getMessage("generic.NoninvertibleMatrix");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
Matrix hpzTransform = modelview.multiply(centerTransformInv);
return ViewUtil.computeHeading(hpzTransform);
}
/**
* Change the altitude of the eye point, without changing the center (look
* at) point or the heading. Only the zoom and pitch change.
*
* @param amount
* @param deviceAttributes
* @param actionAttributes
*/
public void onAltitudeFree(double amount, DeviceAttributes deviceAttributes, ActionAttributes actionAttributes)
{
if (!(getView() instanceof OrbitView))
{
String message = "View must be an instance of OrbitView";
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
amount *= getScaleValueHorizTransRel(deviceAttributes, actionAttributes);
OrbitView view = getView();
//if this view hasn't been applied yet, its globe will be null
if (view.getGlobe() == null)
{
return;
}
Globe globe = view.getGlobe();
//get the eye point and normalize it
Position centerPosition = view.getCenterPosition();
Vec4 centerPoint = view.getCenterPoint();
Position eyePosition = view.getEyePosition();
Position newEyePosition = new Position(eyePosition, eyePosition.elevation + amount);
Vec4 newEyePoint = globe.computePointFromPosition(newEyePosition);
Vec4 north =
globe.computeNorthPointingTangentAtLocation(centerPosition.getLatitude(), centerPosition.getLongitude());
Angle heading = view.getHeading();
Matrix headingMatrix = Matrix.fromRotationZ(heading);
Vec4 up = north.transformBy4(headingMatrix);
OrbitViewState state = OrbitViewInputSupport.computeOrbitViewState(globe, newEyePoint, centerPoint, up);
double pitchDegrees = Math.min(90.0, Math.abs(state.getPitch().degrees));
Angle pitch = Angle.fromDegrees(pitchDegrees);
view.setZoom(state.getZoom());
view.setPitch(pitch);
}
}