/*******************************************************************************
* 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;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.view.orbit.BasicOrbitView;
import gov.nasa.worldwind.view.orbit.OrbitView;
import gov.nasa.worldwind.view.orbit.OrbitViewCollisionSupport;
import gov.nasa.worldwind.view.orbit.OrbitViewInputSupport;
/**
* Same as {@link OrbitViewCollisionSupport}, but replacing any references to
* {@link BasicOrbitView} with the {@link OrbitView} interface.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
@SuppressWarnings("nls")
public class BaseOrbitViewCollisionSupport
{
private double collisionThreshold;
private int numIterations;
public BaseOrbitViewCollisionSupport()
{
setNumIterations(1);
}
public double getCollisionThreshold()
{
return this.collisionThreshold;
}
public void setCollisionThreshold(double collisionThreshold)
{
if (collisionThreshold < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", collisionThreshold);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.collisionThreshold = collisionThreshold;
}
public int getNumIterations()
{
return this.numIterations;
}
public void setNumIterations(int numIterations)
{
if (numIterations < 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", numIterations);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.numIterations = numIterations;
}
public boolean isColliding(OrbitView orbitView, double nearDistance, DrawContext dc)
{
if (orbitView == null)
{
String message = Logging.getMessage("nullValue.OrbitViewIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (nearDistance < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", nearDistance);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Globe globe = dc.getGlobe();
if (globe == null)
{
String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Matrix modelviewInv = getModelviewInverse(globe,
orbitView.getCenterPosition(), orbitView.getHeading(), orbitView.getPitch(), orbitView.getRoll(),
orbitView.getZoom());
if (modelviewInv != null)
{
// OrbitView is colliding when its eye point is below the collision threshold.
double heightAboveSurface = computeViewHeightAboveSurface(dc, modelviewInv,
orbitView.getFieldOfView(), orbitView.getViewport(), nearDistance);
return heightAboveSurface < this.collisionThreshold;
}
return false;
}
public Position computeCenterPositionToResolveCollision(OrbitView orbitView, double nearDistance,
DrawContext dc)
{
if (orbitView == null)
{
String message = Logging.getMessage("nullValue.OrbitViewIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (nearDistance < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", nearDistance);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Globe globe = dc.getGlobe();
if (globe == null)
{
String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Position newCenter = null;
for (int i = 0; i < this.numIterations; i++)
{
Matrix modelviewInv = getModelviewInverse(globe,
newCenter != null ? newCenter : orbitView.getCenterPosition(),
orbitView.getHeading(), orbitView.getPitch(), orbitView.getRoll(), orbitView.getZoom());
if (modelviewInv != null)
{
double heightAboveSurface = computeViewHeightAboveSurface(dc, modelviewInv,
orbitView.getFieldOfView(), orbitView.getViewport(), nearDistance);
double adjustedHeight = heightAboveSurface - this.collisionThreshold;
if (adjustedHeight < 0)
{
newCenter =
new Position(
newCenter != null ? newCenter : orbitView.getCenterPosition(),
(newCenter != null ? newCenter.getElevation() : orbitView.getCenterPosition()
.getElevation())
- adjustedHeight);
}
}
}
return newCenter;
}
public Angle computePitchToResolveCollision(OrbitView orbitView, double nearDistance, DrawContext dc)
{
if (orbitView == null)
{
String message = Logging.getMessage("nullValue.OrbitViewIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (nearDistance < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", nearDistance);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Globe globe = dc.getGlobe();
if (globe == null)
{
String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Angle newPitch = null;
for (int i = 0; i < this.numIterations; i++)
{
Matrix modelviewInv = getModelviewInverse(globe,
orbitView.getCenterPosition(), orbitView.getHeading(),
newPitch != null ? newPitch : orbitView.getPitch(), orbitView.getRoll(),
orbitView.getZoom());
if (modelviewInv != null)
{
double heightAboveSurface = computeViewHeightAboveSurface(dc, modelviewInv,
orbitView.getFieldOfView(), orbitView.getViewport(), nearDistance);
double adjustedHeight = heightAboveSurface - this.collisionThreshold;
if (adjustedHeight < 0)
{
Vec4 eyePoint = getEyePoint(modelviewInv);
Vec4 centerPoint = globe.computePointFromPosition(orbitView.getCenterPosition());
if (eyePoint != null && centerPoint != null)
{
Position eyePos = globe.computePositionFromPoint(eyePoint);
// Compute the eye point required to resolve the collision.
Vec4 newEyePoint = globe.computePointFromPosition(eyePos.getLatitude(), eyePos.getLongitude(),
eyePos.getElevation() - adjustedHeight);
// Compute the pitch that corresponds with the elevation of the eye point
// (but not necessarily the latitude and longitude).
Vec4 normalAtCenter = globe.computeSurfaceNormalAtPoint(centerPoint);
Vec4 newEye_sub_center = newEyePoint.subtract3(centerPoint).normalize3();
double dot = normalAtCenter.dot3(newEye_sub_center);
if (dot >= -1 || dot <= 1)
{
double angle = Math.acos(dot);
newPitch = Angle.fromRadians(angle);
}
}
}
}
}
return newPitch;
}
private double computeViewHeightAboveSurface(DrawContext dc, Matrix modelviewInv,
Angle fieldOfView, java.awt.Rectangle viewport, double nearDistance)
{
double height = Double.POSITIVE_INFINITY;
if (dc != null && modelviewInv != null && fieldOfView != null && viewport != null && nearDistance >= 0)
{
Vec4 eyePoint = getEyePoint(modelviewInv);
if (eyePoint != null)
{
double eyeHeight = computePointHeightAboveSurface(dc, eyePoint);
if (eyeHeight < height)
{
height = eyeHeight;
}
}
Vec4 nearPoint = getPointOnNearPlane(modelviewInv, fieldOfView, viewport, nearDistance);
if (nearPoint != null)
{
double nearHeight = computePointHeightAboveSurface(dc, nearPoint);
if (nearHeight < height)
{
height = nearHeight;
}
}
}
return height;
}
private double computePointHeightAboveSurface(DrawContext dc, Vec4 point)
{
double height = Double.POSITIVE_INFINITY;
if (dc != null && dc.getGlobe() != null && point != null)
{
Globe globe = dc.getGlobe();
Position position = globe.computePositionFromPoint(point);
Position surfacePosition = null;
// Look for the surface geometry point at 'position'.
Vec4 pointOnGlobe = null;
try
{
pointOnGlobe = dc.getPointOnTerrain(position.getLatitude(), position.getLongitude());
}
catch (NullPointerException e)
{
//dc.getPointOnTerrain() can sometimes throw a NPE; ignore it
}
if (pointOnGlobe != null)
{
surfacePosition = globe.computePositionFromPoint(pointOnGlobe);
}
// Fallback to using globe elevation values.
if (surfacePosition == null)
{
surfacePosition =
new Position(position,
globe.getElevation(position.getLatitude(), position.getLongitude())
* dc.getVerticalExaggeration());
}
height = position.getElevation() - surfacePosition.getElevation();
}
return height;
}
private Matrix getModelviewInverse(Globe globe,
Position centerPosition, Angle heading, Angle pitch, Angle roll, double zoom)
{
if (globe != null && centerPosition != null && heading != null && pitch != null)
{
Matrix modelview = OrbitViewInputSupport.computeTransformMatrix(globe,
centerPosition, heading, pitch, roll, zoom);
if (modelview != null)
{
return modelview.getInverse();
}
}
return null;
}
private Vec4 getEyePoint(Matrix modelviewInv)
{
return modelviewInv != null ? Vec4.UNIT_W.transformBy4(modelviewInv) : null;
}
private Vec4 getPointOnNearPlane(Matrix modelviewInv, Angle fieldOfView, java.awt.Rectangle viewport,
double nearDistance)
{
if (modelviewInv != null && fieldOfView != null && viewport != null && nearDistance >= 0)
{
// If either either the viewport width or height is zero, then fall back to an aspect ratio of 1.
// Otherwise, compute the standard aspect ratio.
double aspect = (viewport.getWidth() <= 0 || viewport.getHeight() <= 0) ?
1d : (viewport.getHeight() / viewport.getWidth());
double nearClipHeight = 2 * aspect * nearDistance * fieldOfView.tanHalfAngle();
// Computes the point on the bottom center of the near clip plane.
Vec4 nearClipVec = new Vec4(0, -nearClipHeight / 2.0, -nearDistance, 1);
return nearClipVec.transformBy4(modelviewInv);
}
return null;
}
}