/*
* Copyright (c) 2014 Oculus Info Inc.
* http://www.oculusinfo.com/
*
* Released under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oculusinfo.geometry.geodesic.tracks;
import com.oculusinfo.geometry.geodesic.Position;
import com.oculusinfo.geometry.geodesic.PositionCalculationParameters;
import com.oculusinfo.geometry.geodesic.PositionCalculationType;
import com.oculusinfo.geometry.geodesic.Track;
import com.oculusinfo.math.linearalgebra.Vector;
import java.util.ArrayList;
import java.util.List;
public class Cartesian3DTrack extends Track {
public Cartesian3DTrack (PositionCalculationParameters parameters,
Position... points) {
super(parameters, points);
fillInNeededPoints();
}
public Cartesian3DTrack (PositionCalculationParameters parameters,
List<Position> points) {
super(parameters, points);
fillInNeededPoints();
}
public Cartesian3DTrack (PositionCalculationParameters parameters,
List<Position> points,
List<Double> parameterization) {
super(parameters, points, parameterization);
fillInNeededPoints();
}
public Cartesian3DTrack (Track oldTrack) {
super(oldTrack,
new PositionCalculationParameters(PositionCalculationType.Cartesian3D,
oldTrack.getParameters().getAllowedError(),
oldTrack.getParameters().getPrecision(),
oldTrack.getParameters().ignoreDirection()));
fillInNeededPoints();
}
public Cartesian3DTrack (Track oldTrack, double allowedError, double precision, boolean ignoreDirection) {
super(oldTrack,
new PositionCalculationParameters(PositionCalculationType.Cartesian3D, allowedError, precision, ignoreDirection));
fillInNeededPoints();
}
private void fillInNeededPoints () {
List<Position> filledPoints = new ArrayList<Position>();
List<Double> filledParameterization = new ArrayList<Double>();
// Calculate the maximum chord length we're allowed with a given error
// For a chord of angular length theta, the distance from the sphere
// to the chord at the mid point is
// R (1 - cos(theta/2))
// whereas the length of the chord is
// R sin(theta/2)
// So the error ratio is
// (1 - cos(theta/2)) / sin(theta/2)
// say psi = theta/2
// (1 - cos(psi)) / sin(psi)
// we want to know the max psi for a given error ratio R
// R = (1 - cos(psi)) / sin(psi)
// a = cos(psi)
// = (1 - a) / (sqrt(1-a^2)
// R sqrt(1-a^2) = 1-a
// R^2 (1-a^2) = 1 - 2a + a^2
// R^2 - R^2 a^2 = 1 - 2a + a^2
// (1+R^2) a^2 - 2 a + (1-R^2) = 0
// a = (2 +/- sqrt(4 - (1+R^2) (1-R^2))) / (2*(1+R^2))
// = (2 +/- sqrt(4 - (4 - R^4))) / (2 + 2R^2)
// = (2 +/- sqrt(4 - 4 + R^4)) / 2(1+R^2)
// = (2 +/- R^2) / (2 + 2 R^2)
// A larger a yields a smaller psi, so we want the + branch
double R = getParameters().getAllowedError();
double a = (2+R*R)/(2+2*R*R);
double psi = Math.acos(a);
double theta = Math.toDegrees(2*psi);
Position lastPos = null;
double lastT = 0.0;
List<Position> points = getPoints();
List<Double> parameterization = getParameterization();
for (int p=0; p < points.size(); ++p) {
Position pos = points.get(p);
double t = parameterization.get(p);
if (null != lastPos) {
double angularDistance = pos.getAngularDistance(lastPos);
int minIncrements = (int) Math.ceil(angularDistance/theta);
if (minIncrements > 1) {
// We need to add points in between to maintain our minimum
// error. Just space them evenly.
double timeDistance = t-lastT;
double azimuth = lastPos.getAzimuth(pos);
for (int i=1; i<minIncrements; ++i) {
double incrementDistance = i*angularDistance/minIncrements;
Position incrementPosition = lastPos.offset(azimuth, incrementDistance);
filledPoints.add(incrementPosition);
filledParameterization.add(lastT+i*timeDistance/minIncrements);
}
}
}
filledPoints.add(pos);
filledParameterization.add(t);
lastPos = pos;
lastT = t;
}
updatePoints(filledPoints, filledParameterization);
}
@Override
protected double getSegmentDistance (Position start, Position end) {
return start.getCartesianDistance(end);
}
@Override
protected Position interpolate (Position start, Position end, double t) {
Vector sV = start.getAsCartesian();
Vector eV = end.getAsCartesian();
Vector cartesianResult = sV.scale(1 - t).add(eV.scale(t));
Position p = new Position(cartesianResult.coord(0), cartesianResult.coord(1),
cartesianResult.coord(2), !start.hasElevation());
p.setPrecision(start.getPrecision());
return p;
}
@Override
protected double getRelativeError (Position a, Position b, Position c) {
Vector vA = a.getAsCartesian();
Vector vB = b.getAsCartesian();
Vector vC = c.getAsCartesian();
Vector ac = vC.subtract(vA);
Vector ab = vB.subtract(vA);
double lac2 = ac.vectorLengthSquared();
Vector bPerp = ab.subtract(ac.scale(ab.dot(ac)/lac2));
double lbPerp = bPerp.vectorLength();
double lac = Math.sqrt(lac2);
return lbPerp/lac;
}
@Override
protected Track createTrack (List<Position> points) {
return new Cartesian3DTrack(getParameters(), points);
}
@Override
protected Track createTrack (List<Position> path,
List<Double> parameterization) {
return new Cartesian3DTrack(getParameters(), path, parameterization);
}
}