package org.apache.lucene.spatial3d.geom;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
class GeoComplexPolygon extends GeoBasePolygon {
private final Tree xTree;
private final Tree yTree;
private final Tree zTree;
private final List<List<GeoPoint>> pointsList;
private final boolean testPoint1InSet;
private final GeoPoint testPoint1;
private final Plane testPoint1FixedYPlane;
private final Plane testPoint1FixedYAbovePlane;
private final Plane testPoint1FixedYBelowPlane;
private final Plane testPoint1FixedXPlane;
private final Plane testPoint1FixedXAbovePlane;
private final Plane testPoint1FixedXBelowPlane;
private final Plane testPoint1FixedZPlane;
private final Plane testPoint1FixedZAbovePlane;
private final Plane testPoint1FixedZBelowPlane;
private final GeoPoint[] edgePoints;
private final Edge[] shapeStartEdges;
private final static double NEAR_EDGE_CUTOFF = -Vector.MINIMUM_RESOLUTION * 10000.0;
public GeoComplexPolygon(final PlanetModel planetModel, final List<List<GeoPoint>> pointsList, final GeoPoint testPoint, final boolean testPointInSet) {
super(planetModel);
assert planetModel.pointOnSurface(testPoint.x, testPoint.y, testPoint.z) : "Test point is not on the ellipsoid surface";
this.pointsList = pointsList;
this.edgePoints = new GeoPoint[pointsList.size()];
this.shapeStartEdges = new Edge[pointsList.size()];
final ArrayList<Edge> allEdges = new ArrayList<>();
int edgePointIndex = 0;
for (final List<GeoPoint> shapePoints : pointsList) {
allEdges.ensureCapacity(allEdges.size() + shapePoints.size());
GeoPoint lastGeoPoint = shapePoints.get(shapePoints.size()-1);
edgePoints[edgePointIndex] = lastGeoPoint;
Edge lastEdge = null;
Edge firstEdge = null;
for (final GeoPoint thisGeoPoint : shapePoints) {
assert planetModel.pointOnSurface(thisGeoPoint) : "Polygon edge point must be on surface; "+thisGeoPoint+" is not";
final Edge edge = new Edge(planetModel, lastGeoPoint, thisGeoPoint);
if (edge.isWithin(testPoint.x, testPoint.y, testPoint.z)) {
throw new IllegalArgumentException("Test point is on polygon edge: not allowed");
}
allEdges.add(edge);
if (firstEdge == null) {
firstEdge = edge;
}
if (lastEdge != null) {
lastEdge.next = edge;
edge.previous = lastEdge;
}
lastEdge = edge;
lastGeoPoint = thisGeoPoint;
}
firstEdge.previous = lastEdge;
lastEdge.next = firstEdge;
shapeStartEdges[edgePointIndex] = firstEdge;
edgePointIndex++;
}
xTree = new XTree(allEdges);
yTree = new YTree(allEdges);
zTree = new ZTree(allEdges);
this.testPoint1 = testPoint;
this.testPoint1FixedYPlane = new Plane(0.0, 1.0, 0.0, -testPoint1.y);
this.testPoint1FixedXPlane = new Plane(1.0, 0.0, 0.0, -testPoint1.x);
this.testPoint1FixedZPlane = new Plane(0.0, 0.0, 1.0, -testPoint1.z);
Plane testPoint1FixedYAbovePlane = new Plane(testPoint1FixedYPlane, true);
if (-testPoint1FixedYAbovePlane.D - planetModel.getMaximumYValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumYValue() + testPoint1FixedYAbovePlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedYAbovePlane = null;
}
this.testPoint1FixedYAbovePlane = testPoint1FixedYAbovePlane;
Plane testPoint1FixedYBelowPlane = new Plane(testPoint1FixedYPlane, false);
if (-testPoint1FixedYBelowPlane.D - planetModel.getMaximumYValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumYValue() + testPoint1FixedYBelowPlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedYBelowPlane = null;
}
this.testPoint1FixedYBelowPlane = testPoint1FixedYBelowPlane;
Plane testPoint1FixedXAbovePlane = new Plane(testPoint1FixedXPlane, true);
if (-testPoint1FixedXAbovePlane.D - planetModel.getMaximumXValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumXValue() + testPoint1FixedXAbovePlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedXAbovePlane = null;
}
this.testPoint1FixedXAbovePlane = testPoint1FixedXAbovePlane;
Plane testPoint1FixedXBelowPlane = new Plane(testPoint1FixedXPlane, false);
if (-testPoint1FixedXBelowPlane.D - planetModel.getMaximumXValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumXValue() + testPoint1FixedXBelowPlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedXBelowPlane = null;
}
this.testPoint1FixedXBelowPlane = testPoint1FixedXBelowPlane;
Plane testPoint1FixedZAbovePlane = new Plane(testPoint1FixedZPlane, true);
if (-testPoint1FixedZAbovePlane.D - planetModel.getMaximumZValue() > NEAR_EDGE_CUTOFF ||planetModel.getMinimumZValue() + testPoint1FixedZAbovePlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedZAbovePlane = null;
}
this.testPoint1FixedZAbovePlane = testPoint1FixedZAbovePlane;
Plane testPoint1FixedZBelowPlane = new Plane(testPoint1FixedZPlane, false);
if (-testPoint1FixedZBelowPlane.D - planetModel.getMaximumZValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumZValue() + testPoint1FixedZBelowPlane.D > NEAR_EDGE_CUTOFF) {
testPoint1FixedZBelowPlane = null;
}
this.testPoint1FixedZBelowPlane = testPoint1FixedZBelowPlane;
this.testPoint1InSet = testPointInSet;
}
public GeoComplexPolygon(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
this(planetModel,
readPointsList(planetModel, inputStream),
new GeoPoint(planetModel, inputStream),
SerializableObject.readBoolean(inputStream));
}
private static List<List<GeoPoint>> readPointsList(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
final int count = SerializableObject.readInt(inputStream);
final List<List<GeoPoint>> array = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
array.add(java.util.Arrays.asList(SerializableObject.readPointArray(planetModel, inputStream)));
}
return array;
}
@Override
public void write(final OutputStream outputStream) throws IOException {
writePointsList(outputStream, pointsList);
testPoint1.write(outputStream);
SerializableObject.writeBoolean(outputStream, testPoint1InSet);
}
private static void writePointsList(final OutputStream outputStream, final List<List<GeoPoint>> pointsList) throws IOException {
SerializableObject.writeInt(outputStream, pointsList.size());
for (final List<GeoPoint> points : pointsList) {
SerializableObject.writePointArray(outputStream, points);
}
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return isInSet(x, y, z,
testPoint1,
testPoint1InSet,
testPoint1FixedXPlane, testPoint1FixedXAbovePlane, testPoint1FixedXBelowPlane,
testPoint1FixedYPlane, testPoint1FixedYAbovePlane, testPoint1FixedYBelowPlane,
testPoint1FixedZPlane, testPoint1FixedZAbovePlane, testPoint1FixedZBelowPlane);
}
private boolean isInSet(final double x, final double y, final double z,
final GeoPoint testPoint,
final boolean testPointInSet,
final Plane testPointFixedXPlane, final Plane testPointFixedXAbovePlane, final Plane testPointFixedXBelowPlane,
final Plane testPointFixedYPlane, final Plane testPointFixedYAbovePlane, final Plane testPointFixedYBelowPlane,
final Plane testPointFixedZPlane, final Plane testPointFixedZAbovePlane, final Plane testPointFixedZBelowPlane) {
if (testPoint.isNumericallyIdentical(x, y, z)) {
return testPointInSet;
}
if (testPointFixedYAbovePlane != null && testPointFixedYBelowPlane != null && testPointFixedYPlane.evaluateIsZero(x, y, z)) {
final CountingEdgeIterator crossingEdgeIterator = createLinearCrossingEdgeIterator(testPoint, testPointFixedYPlane, testPointFixedYAbovePlane, testPointFixedYBelowPlane, x, y, z);
yTree.traverse(crossingEdgeIterator, testPoint.y);
return crossingEdgeIterator.isOnEdge() || (((crossingEdgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet);
} else if (testPointFixedXAbovePlane != null && testPointFixedXBelowPlane != null && testPointFixedXPlane.evaluateIsZero(x, y, z)) {
final CountingEdgeIterator crossingEdgeIterator = createLinearCrossingEdgeIterator(testPoint, testPointFixedXPlane, testPointFixedXAbovePlane, testPointFixedXBelowPlane, x, y, z);
xTree.traverse(crossingEdgeIterator, testPoint.x);
return crossingEdgeIterator.isOnEdge() || (((crossingEdgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet);
} else if (testPointFixedZAbovePlane != null && testPointFixedZBelowPlane != null && testPointFixedZPlane.evaluateIsZero(x, y, z)) {
final CountingEdgeIterator crossingEdgeIterator = createLinearCrossingEdgeIterator(testPoint, testPointFixedZPlane, testPointFixedZAbovePlane, testPointFixedZBelowPlane, x, y, z);
zTree.traverse(crossingEdgeIterator, testPoint.z);
return crossingEdgeIterator.isOnEdge() || (((crossingEdgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet);
} else {
final Plane travelPlaneFixedX = new Plane(1.0, 0.0, 0.0, -x);
final Plane travelPlaneFixedY = new Plane(0.0, 1.0, 0.0, -y);
final Plane travelPlaneFixedZ = new Plane(0.0, 0.0, 1.0, -z);
Plane fixedYAbovePlane = new Plane(travelPlaneFixedY, true);
if (-fixedYAbovePlane.D - planetModel.getMaximumYValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumYValue() + fixedYAbovePlane.D > NEAR_EDGE_CUTOFF) {
fixedYAbovePlane = null;
}
Plane fixedYBelowPlane = new Plane(travelPlaneFixedY, false);
if (-fixedYBelowPlane.D - planetModel.getMaximumYValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumYValue() + fixedYBelowPlane.D > NEAR_EDGE_CUTOFF) {
fixedYBelowPlane = null;
}
Plane fixedXAbovePlane = new Plane(travelPlaneFixedX, true);
if (-fixedXAbovePlane.D - planetModel.getMaximumXValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumXValue() + fixedXAbovePlane.D > NEAR_EDGE_CUTOFF) {
fixedXAbovePlane = null;
}
Plane fixedXBelowPlane = new Plane(travelPlaneFixedX, false);
if (-fixedXBelowPlane.D - planetModel.getMaximumXValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumXValue() + fixedXBelowPlane.D > NEAR_EDGE_CUTOFF) {
fixedXBelowPlane = null;
}
Plane fixedZAbovePlane = new Plane(travelPlaneFixedZ, true);
if (-fixedZAbovePlane.D - planetModel.getMaximumZValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumZValue() + fixedZAbovePlane.D > NEAR_EDGE_CUTOFF) {
fixedZAbovePlane = null;
}
Plane fixedZBelowPlane = new Plane(travelPlaneFixedZ, false);
if (-fixedZBelowPlane.D - planetModel.getMaximumZValue() > NEAR_EDGE_CUTOFF || planetModel.getMinimumZValue() + fixedZBelowPlane.D > NEAR_EDGE_CUTOFF) {
fixedZBelowPlane = null;
}
final List<TraversalStrategy> traversalStrategies = new ArrayList<>(12);
if (testPointFixedYAbovePlane != null && testPointFixedYBelowPlane != null && fixedXAbovePlane != null && fixedXBelowPlane != null) {
final double checkAbove = 4.0 * (fixedXAbovePlane.D * fixedXAbovePlane.D * planetModel.inverseAbSquared + testPointFixedYAbovePlane.D * testPointFixedYAbovePlane.D * planetModel.inverseAbSquared - 1.0);
final double checkBelow = 4.0 * (fixedXBelowPlane.D * fixedXBelowPlane.D * planetModel.inverseAbSquared + testPointFixedYBelowPlane.D * testPointFixedYBelowPlane.D * planetModel.inverseAbSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] XIntersectionsY = travelPlaneFixedX.findIntersections(planetModel, testPointFixedYPlane);
for (final GeoPoint p : XIntersectionsY) {
final double tpDelta1 = testPoint.x - p.x;
final double tpDelta2 = testPoint.z - p.z;
final double cpDelta1 = y - p.y;
final double cpDelta2 = z - p.z;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.y, x,
testPointFixedYPlane, testPointFixedYAbovePlane, testPointFixedYBelowPlane,
travelPlaneFixedX, fixedXAbovePlane, fixedXBelowPlane,
yTree, xTree, p));
}
}
}
if (testPointFixedZAbovePlane != null && testPointFixedZBelowPlane != null && fixedXAbovePlane != null && fixedXBelowPlane != null) {
final double checkAbove = 4.0 * (fixedXAbovePlane.D * fixedXAbovePlane.D * planetModel.inverseAbSquared + testPointFixedZAbovePlane.D * testPointFixedZAbovePlane.D * planetModel.inverseCSquared - 1.0);
final double checkBelow = 4.0 * (fixedXBelowPlane.D * fixedXBelowPlane.D * planetModel.inverseAbSquared + testPointFixedZBelowPlane.D * testPointFixedZBelowPlane.D * planetModel.inverseCSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] XIntersectionsZ = travelPlaneFixedX.findIntersections(planetModel, testPointFixedZPlane);
for (final GeoPoint p : XIntersectionsZ) {
final double tpDelta1 = testPoint.x - p.x;
final double tpDelta2 = testPoint.y - p.y;
final double cpDelta1 = y - p.y;
final double cpDelta2 = z - p.z;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.z, x,
testPointFixedZPlane, testPointFixedZAbovePlane, testPointFixedZBelowPlane,
travelPlaneFixedX, fixedXAbovePlane, fixedXBelowPlane,
zTree, xTree, p));
}
}
}
if (testPointFixedXAbovePlane != null && testPointFixedXBelowPlane != null && fixedYAbovePlane != null && fixedYBelowPlane != null) {
final double checkAbove = 4.0 * (testPointFixedXAbovePlane.D * testPointFixedXAbovePlane.D * planetModel.inverseAbSquared + fixedYAbovePlane.D * fixedYAbovePlane.D * planetModel.inverseAbSquared - 1.0);
final double checkBelow = 4.0 * (testPointFixedXBelowPlane.D * testPointFixedXBelowPlane.D * planetModel.inverseAbSquared + fixedYBelowPlane.D * fixedYBelowPlane.D * planetModel.inverseAbSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] YIntersectionsX = travelPlaneFixedY.findIntersections(planetModel, testPointFixedXPlane);
for (final GeoPoint p : YIntersectionsX) {
final double tpDelta1 = testPoint.y - p.y;
final double tpDelta2 = testPoint.z - p.z;
final double cpDelta1 = x - p.x;
final double cpDelta2 = z - p.z;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.x, y,
testPointFixedXPlane, testPointFixedXAbovePlane, testPointFixedXBelowPlane,
travelPlaneFixedY, fixedYAbovePlane, fixedYBelowPlane,
xTree, yTree, p));
}
}
}
if (testPointFixedZAbovePlane != null && testPointFixedZBelowPlane != null && fixedYAbovePlane != null && fixedYBelowPlane != null) {
final double checkAbove = 4.0 * (testPointFixedZAbovePlane.D * testPointFixedZAbovePlane.D * planetModel.inverseCSquared + fixedYAbovePlane.D * fixedYAbovePlane.D * planetModel.inverseAbSquared - 1.0);
final double checkBelow = 4.0 * (testPointFixedZBelowPlane.D * testPointFixedZBelowPlane.D * planetModel.inverseCSquared + fixedYBelowPlane.D * fixedYBelowPlane.D * planetModel.inverseAbSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] YIntersectionsZ = travelPlaneFixedY.findIntersections(planetModel, testPointFixedZPlane);
for (final GeoPoint p : YIntersectionsZ) {
final double tpDelta1 = testPoint.x - p.x;
final double tpDelta2 = testPoint.y - p.y;
final double cpDelta1 = x - p.x;
final double cpDelta2 = z - p.z;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.z, y,
testPointFixedZPlane, testPointFixedZAbovePlane, testPointFixedZBelowPlane,
travelPlaneFixedY, fixedYAbovePlane, fixedYBelowPlane,
zTree, yTree, p));
}
}
}
if (testPointFixedXAbovePlane != null && testPointFixedXBelowPlane != null && fixedZAbovePlane != null && fixedZBelowPlane != null) {
final double checkAbove = 4.0 * (testPointFixedXAbovePlane.D * testPointFixedXAbovePlane.D * planetModel.inverseAbSquared + fixedZAbovePlane.D * fixedZAbovePlane.D * planetModel.inverseCSquared - 1.0);
final double checkBelow = 4.0 * (testPointFixedXBelowPlane.D * testPointFixedXBelowPlane.D * planetModel.inverseAbSquared + fixedZBelowPlane.D * fixedZBelowPlane.D * planetModel.inverseCSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] ZIntersectionsX = travelPlaneFixedZ.findIntersections(planetModel, testPointFixedXPlane);
for (final GeoPoint p : ZIntersectionsX) {
final double tpDelta1 = testPoint.y - p.y;
final double tpDelta2 = testPoint.z - p.z;
final double cpDelta1 = y - p.y;
final double cpDelta2 = x - p.x;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.x, z,
testPointFixedXPlane, testPointFixedXAbovePlane, testPointFixedXBelowPlane,
travelPlaneFixedZ, fixedZAbovePlane, fixedZBelowPlane,
xTree, zTree, p));
}
}
}
if (testPointFixedYAbovePlane != null && testPointFixedYBelowPlane != null && fixedZAbovePlane != null && fixedZBelowPlane != null) {
final double checkAbove = 4.0 * (testPointFixedYAbovePlane.D * testPointFixedYAbovePlane.D * planetModel.inverseAbSquared + fixedZAbovePlane.D * fixedZAbovePlane.D * planetModel.inverseCSquared - 1.0);
final double checkBelow = 4.0 * (testPointFixedYBelowPlane.D * testPointFixedYBelowPlane.D * planetModel.inverseAbSquared + fixedZBelowPlane.D * fixedZBelowPlane.D * planetModel.inverseCSquared - 1.0);
if (checkAbove < Vector.MINIMUM_RESOLUTION_SQUARED && checkBelow < Vector.MINIMUM_RESOLUTION_SQUARED) {
final GeoPoint[] ZIntersectionsY = travelPlaneFixedZ.findIntersections(planetModel, testPointFixedYPlane);
for (final GeoPoint p : ZIntersectionsY) {
final double tpDelta1 = testPoint.x - p.x;
final double tpDelta2 = testPoint.z - p.z;
final double cpDelta1 = y - p.y;
final double cpDelta2 = x - p.x;
final double newDistance = tpDelta1 * tpDelta1 + tpDelta2 * tpDelta2 + cpDelta1 * cpDelta1 + cpDelta2 * cpDelta2;
traversalStrategies.add(new TraversalStrategy(newDistance, testPoint.y, z,
testPointFixedYPlane, testPointFixedYAbovePlane, testPointFixedYBelowPlane,
travelPlaneFixedZ, fixedZAbovePlane, fixedZBelowPlane,
yTree, zTree, p));
}
}
}
Collections.sort(traversalStrategies);
if (traversalStrategies.size() == 0) {
throw new IllegalArgumentException("No dual-plane travel strategies were found");
}
for (final TraversalStrategy ts : traversalStrategies) {
try {
return ts.apply(testPoint, testPointInSet, x, y, z);
} catch (IllegalArgumentException e) {
}
}
throw new IllegalArgumentException("Exhausted all traversal strategies");
}
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
final EdgeIterator intersector = new IntersectorEdgeIterator(p, notablePoints, bounds);
final XYZBounds xyzBounds = new XYZBounds();
p.recordBounds(planetModel, xyzBounds, bounds);
for (final GeoPoint point : notablePoints) {
xyzBounds.addPoint(point);
}
if (xyzBounds.getMaximumX() == null || xyzBounds.getMinimumX() == null ||
xyzBounds.getMaximumY() == null || xyzBounds.getMinimumY() == null ||
xyzBounds.getMaximumZ() == null || xyzBounds.getMinimumZ() == null) {
return false;
}
final double xDelta = xyzBounds.getMaximumX() - xyzBounds.getMinimumX();
final double yDelta = xyzBounds.getMaximumY() - xyzBounds.getMinimumY();
final double zDelta = xyzBounds.getMaximumZ() - xyzBounds.getMinimumZ();
if (xDelta <= yDelta && xDelta <= zDelta) {
return !xTree.traverse(intersector, xyzBounds.getMinimumX(), xyzBounds.getMaximumX());
} else if (yDelta <= xDelta && yDelta <= zDelta) {
return !yTree.traverse(intersector, xyzBounds.getMinimumY(), xyzBounds.getMaximumY());
} else if (zDelta <= xDelta && zDelta <= yDelta) {
return !zTree.traverse(intersector, xyzBounds.getMinimumZ(), xyzBounds.getMaximumZ());
}
return true;
}
@Override
public boolean intersects(GeoShape geoShape) {
final EdgeIterator intersector = new IntersectorShapeIterator(geoShape);
final XYZBounds xyzBounds = new XYZBounds();
geoShape.getBounds(xyzBounds);
final double xDelta = xyzBounds.getMaximumX() - xyzBounds.getMinimumX();
final double yDelta = xyzBounds.getMaximumY() - xyzBounds.getMinimumY();
final double zDelta = xyzBounds.getMaximumZ() - xyzBounds.getMinimumZ();
if (xDelta <= yDelta && xDelta <= zDelta) {
return !xTree.traverse(intersector, xyzBounds.getMinimumX(), xyzBounds.getMaximumX());
} else if (yDelta <= xDelta && yDelta <= zDelta) {
return !yTree.traverse(intersector, xyzBounds.getMinimumY(), xyzBounds.getMaximumY());
} else if (zDelta <= xDelta && zDelta <= yDelta) {
return !zTree.traverse(intersector, xyzBounds.getMinimumZ(), xyzBounds.getMaximumZ());
}
return true;
}
@Override
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
for (final Edge startEdge : shapeStartEdges) {
Edge currentEdge = startEdge;
while (true) {
bounds.addPoint(currentEdge.startPoint);
bounds.addPlane(this.planetModel, currentEdge.plane, currentEdge.startPlane, currentEdge.endPlane);
currentEdge = currentEdge.next;
if (currentEdge == startEdge) {
break;
}
}
}
}
@Override
protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double minimumDistance = Double.POSITIVE_INFINITY;
for (final Edge shapeStartEdge : shapeStartEdges) {
Edge shapeEdge = shapeStartEdge;
while (true) {
final double newDist = distanceStyle.computeDistance(shapeEdge.startPoint, x, y, z);
if (newDist < minimumDistance) {
minimumDistance = newDist;
}
final double newPlaneDist = distanceStyle.computeDistance(planetModel, shapeEdge.plane, x, y, z, shapeEdge.startPlane, shapeEdge.endPlane);
if (newPlaneDist < minimumDistance) {
minimumDistance = newPlaneDist;
}
shapeEdge = shapeEdge.next;
if (shapeEdge == shapeStartEdge) {
break;
}
}
}
return minimumDistance;
}
private CountingEdgeIterator createLinearCrossingEdgeIterator(final GeoPoint testPoint,
final Plane plane, final Plane abovePlane, final Plane belowPlane,
final double thePointX, final double thePointY, final double thePointZ) {
try {
return new SectorLinearCrossingEdgeIterator(testPoint, plane, abovePlane, belowPlane, thePointX, thePointY, thePointZ);
} catch (IllegalArgumentException e) {
return new FullLinearCrossingEdgeIterator(testPoint, plane, abovePlane, belowPlane, thePointX, thePointY, thePointZ);
}
}
private final static double[] halfProportions = new double[]{0.5};
private static class Edge {
public final GeoPoint startPoint;
public final GeoPoint endPoint;
public final GeoPoint[] notablePoints;
public final SidedPlane startPlane;
public final SidedPlane endPlane;
public final SidedPlane backingPlane;
public final Plane plane;
public final XYZBounds planeBounds;
public Edge previous = null;
public Edge next = null;
public Edge(final PlanetModel pm, final GeoPoint startPoint, final GeoPoint endPoint) {
this.startPoint = startPoint;
this.endPoint = endPoint;
this.notablePoints = new GeoPoint[]{startPoint, endPoint};
this.plane = new Plane(startPoint, endPoint);
this.startPlane = new SidedPlane(endPoint, plane, startPoint);
this.endPlane = new SidedPlane(startPoint, plane, endPoint);
final GeoPoint interpolationPoint = plane.interpolate(pm, startPoint, endPoint, halfProportions)[0];
this.backingPlane = new SidedPlane(interpolationPoint, interpolationPoint, 0.0);
this.planeBounds = new XYZBounds();
this.planeBounds.addPoint(startPoint);
this.planeBounds.addPoint(endPoint);
this.planeBounds.addPlane(pm, this.plane, this.startPlane, this.endPlane, this.backingPlane);
}
public boolean isWithin(final double thePointX, final double thePointY, final double thePointZ) {
return plane.evaluateIsZero(thePointX, thePointY, thePointZ) && startPlane.isWithin(thePointX, thePointY, thePointZ) && endPlane.isWithin(thePointX, thePointY, thePointZ) && backingPlane.isWithin(thePointX, thePointY, thePointZ);
}
}
private class TraversalStrategy implements Comparable<TraversalStrategy> {
private final double traversalDistance;
private final double firstLegValue;
private final double secondLegValue;
private final Plane firstLegPlane;
private final Plane firstLegAbovePlane;
private final Plane firstLegBelowPlane;
private final Plane secondLegPlane;
private final Plane secondLegAbovePlane;
private final Plane secondLegBelowPlane;
private final Tree firstLegTree;
private final Tree secondLegTree;
private final GeoPoint intersectionPoint;
public TraversalStrategy(final double traversalDistance, final double firstLegValue, final double secondLegValue,
final Plane firstLegPlane, final Plane firstLegAbovePlane, final Plane firstLegBelowPlane,
final Plane secondLegPlane, final Plane secondLegAbovePlane, final Plane secondLegBelowPlane,
final Tree firstLegTree, final Tree secondLegTree,
final GeoPoint intersectionPoint) {
this.traversalDistance = traversalDistance;
this.firstLegValue = firstLegValue;
this.secondLegValue = secondLegValue;
this.firstLegPlane = firstLegPlane;
this.firstLegAbovePlane = firstLegAbovePlane;
this.firstLegBelowPlane = firstLegBelowPlane;
this.secondLegPlane = secondLegPlane;
this.secondLegAbovePlane = secondLegAbovePlane;
this.secondLegBelowPlane = secondLegBelowPlane;
this.firstLegTree = firstLegTree;
this.secondLegTree = secondLegTree;
this.intersectionPoint = intersectionPoint;
}
public boolean apply(final GeoPoint testPoint, final boolean testPointInSet,
final double x, final double y, final double z) {
try {
final CountingEdgeIterator testPointEdgeIterator = createLinearCrossingEdgeIterator(testPoint,
firstLegPlane, firstLegAbovePlane, firstLegBelowPlane,
intersectionPoint.x, intersectionPoint.y, intersectionPoint.z);
firstLegTree.traverse(testPointEdgeIterator, firstLegValue);
final boolean intersectionPointOnEdge = testPointEdgeIterator.isOnEdge();
if (intersectionPointOnEdge) {
throw new IllegalArgumentException("Intersection point landed on an edge -- illegal path");
}
final boolean intersectionPointInSet = intersectionPointOnEdge || (((testPointEdgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet);
final CountingEdgeIterator travelEdgeIterator = createLinearCrossingEdgeIterator(intersectionPoint,
secondLegPlane, secondLegAbovePlane, secondLegBelowPlane,
x, y, z);
secondLegTree.traverse(travelEdgeIterator, secondLegValue);
final boolean rval = travelEdgeIterator.isOnEdge() || (((travelEdgeIterator.getCrossingCount() & 1) == 0)?intersectionPointInSet:!intersectionPointInSet);
return rval;
} catch (IllegalArgumentException e) {
final CountingEdgeIterator edgeIterator = new DualCrossingEdgeIterator(testPoint,
firstLegPlane, firstLegAbovePlane, firstLegBelowPlane,
secondLegPlane, secondLegAbovePlane, secondLegBelowPlane,
x, y, z, intersectionPoint);
firstLegTree.traverse(edgeIterator, firstLegValue);
if (edgeIterator.isOnEdge()) {
return true;
}
secondLegTree.traverse(edgeIterator, secondLegValue);
return edgeIterator.isOnEdge() || (((edgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet);
}
}
@Override
public String toString() {
return "{firstLegValue="+firstLegValue+"; secondLegValue="+secondLegValue+"; firstLegPlane="+firstLegPlane+"; secondLegPlane="+secondLegPlane+"; intersectionPoint="+intersectionPoint+"}";
}
@Override
public int compareTo(final TraversalStrategy other) {
if (traversalDistance < other.traversalDistance) {
return -1;
} else if (traversalDistance > other.traversalDistance) {
return 1;
}
return 0;
}
}
private static interface EdgeIterator {
public boolean matches(final Edge edge);
}
private static interface CountingEdgeIterator extends EdgeIterator {
public int getCrossingCount();
public boolean isOnEdge();
}
private static class Node {
public final Edge edge;
public final double low;
public final double high;
public Node left = null;
public Node right = null;
public double max;
public Node(final Edge edge, final double minimumValue, final double maximumValue) {
this.edge = edge;
this.low = minimumValue;
this.high = maximumValue;
this.max = maximumValue;
}
public boolean traverse(final EdgeIterator edgeIterator, final double minValue, final double maxValue) {
if (minValue <= max) {
if (minValue <= high && maxValue >= low) {
if (edgeIterator.matches(edge) == false) {
return false;
}
}
if (left != null && left.traverse(edgeIterator, minValue, maxValue) == false) {
return false;
}
if (right != null && maxValue >= low && right.traverse(edgeIterator, minValue, maxValue) == false) {
return false;
}
}
return true;
}
}
private static abstract class Tree {
private final Node rootNode;
protected static final Edge[] EMPTY_ARRAY = new Edge[0];
public Tree(final List<Edge> allEdges) {
final Node[] edges = new Node[allEdges.size()];
int i = 0;
for (final Edge edge : allEdges) {
edges[i++] = new Node(edge, getMinimum(edge), getMaximum(edge));
}
Arrays.sort(edges, (left, right) -> {
int ret = Double.compare(left.low, right.low);
if (ret == 0) {
ret = Double.compare(left.max, right.max);
}
return ret;
});
rootNode = createTree(edges, 0, edges.length - 1);
}
private static Node createTree(final Node[] edges, final int low, final int high) {
if (low > high) {
return null;
}
int mid = (low + high) >>> 1;
final Node newNode = edges[mid];
newNode.left = createTree(edges, low, mid - 1);
newNode.right = createTree(edges, mid + 1, high);
if (newNode.left != null) {
newNode.max = Math.max(newNode.max, newNode.left.max);
}
if (newNode.right != null) {
newNode.max = Math.max(newNode.max, newNode.right.max);
}
return newNode;
}
protected abstract double getMinimum(final Edge edge);
protected abstract double getMaximum(final Edge edge);
public boolean traverse(final EdgeIterator edgeIterator, final double value) {
return traverse(edgeIterator, value, value);
}
public boolean traverse(final EdgeIterator edgeIterator, final double minValue, final double maxValue) {
if (rootNode == null) {
return true;
}
return rootNode.traverse(edgeIterator, minValue, maxValue);
}
}
private static class ZTree extends Tree {
public Node rootNode = null;
public ZTree(final List<Edge> allEdges) {
super(allEdges);
}
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumZ();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumZ();
}
}
private static class YTree extends Tree {
public YTree(final List<Edge> allEdges) {
super(allEdges);
}
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumY();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumY();
}
}
private static class XTree extends Tree {
public XTree(final List<Edge> allEdges) {
super(allEdges);
}
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumX();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumX();
}
}
private class IntersectorEdgeIterator implements EdgeIterator {
private final Plane plane;
private final GeoPoint[] notablePoints;
private final Membership[] bounds;
public IntersectorEdgeIterator(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
this.plane = plane;
this.notablePoints = notablePoints;
this.bounds = bounds;
}
@Override
public boolean matches(final Edge edge) {
return !plane.intersects(planetModel, edge.plane, notablePoints, edge.notablePoints, bounds, edge.startPlane, edge.endPlane);
}
}
private class IntersectorShapeIterator implements EdgeIterator {
private final GeoShape shape;
public IntersectorShapeIterator(final GeoShape shape) {
this.shape = shape;
}
@Override
public boolean matches(final Edge edge) {
return !shape.intersects(edge.plane, edge.notablePoints, edge.startPlane, edge.endPlane);
}
}
private class FullLinearCrossingEdgeIterator implements CountingEdgeIterator {
private final GeoPoint testPoint;
private final Plane plane;
private final Plane abovePlane;
private final Plane belowPlane;
private final Membership bound;
private final double thePointX;
private final double thePointY;
private final double thePointZ;
private boolean onEdge = false;
private int aboveCrossingCount = 0;
private int belowCrossingCount = 0;
public FullLinearCrossingEdgeIterator(final GeoPoint testPoint,
final Plane plane, final Plane abovePlane, final Plane belowPlane,
final double thePointX, final double thePointY, final double thePointZ) {
assert plane.evaluateIsZero(thePointX, thePointY, thePointZ) : "Check point is not on travel plane";
assert plane.evaluateIsZero(testPoint) : "Test point is not on travel plane";
this.testPoint = testPoint;
this.plane = plane;
this.abovePlane = abovePlane;
this.belowPlane = belowPlane;
if (plane.isNumericallyIdentical(testPoint)) {
throw new IllegalArgumentException("Plane vector identical to testpoint vector");
}
this.bound = new SidedPlane(plane, testPoint);
this.thePointX = thePointX;
this.thePointY = thePointY;
this.thePointZ = thePointZ;
}
@Override
public int getCrossingCount() {
return Math.min(aboveCrossingCount, belowCrossingCount);
}
@Override
public boolean isOnEdge() {
return onEdge;
}
@Override
public boolean matches(final Edge edge) {
if (edge.isWithin(thePointX, thePointY, thePointZ)) {
onEdge = true;
return false;
}
final GeoPoint[] planeCrossings = plane.findIntersections(planetModel, edge.plane, bound, edge.startPlane, edge.endPlane);
if (planeCrossings != null && planeCrossings.length == 0) {
if (!plane.evaluateIsZero(edge.startPoint) && !plane.evaluateIsZero(edge.endPoint)) {
return true;
}
}
final int aboveCrossings = countCrossings(edge, abovePlane, bound);
aboveCrossingCount += aboveCrossings;
final int belowCrossings = countCrossings(edge, belowPlane, bound);
belowCrossingCount += belowCrossings;
return true;
}
private int countCrossings(final Edge edge,
final Plane envelopePlane, final Membership envelopeBound) {
final GeoPoint[] intersections = edge.plane.findIntersections(planetModel, envelopePlane, envelopeBound);
int crossings = 0;
if (intersections != null) {
for (final GeoPoint intersection : intersections) {
if (edge.startPlane.strictlyWithin(intersection) && edge.endPlane.strictlyWithin(intersection)) {
crossings += edgeCrossesEnvelope(edge.plane, intersection, envelopePlane)?1:0;
}
}
}
return crossings;
}
private boolean edgeCrossesEnvelope(final Plane edgePlane, final GeoPoint intersectionPoint, final Plane envelopePlane) {
final GeoPoint[] adjoiningPoints = findAdjoiningPoints(edgePlane, intersectionPoint, envelopePlane);
if (adjoiningPoints == null) {
return true;
}
int withinCount = 0;
for (final GeoPoint adjoining : adjoiningPoints) {
if (plane.evaluateIsZero(adjoining) && bound.isWithin(adjoining)) {
withinCount++;
}
}
return (withinCount & 1) != 0;
}
}
private class SectorLinearCrossingEdgeIterator implements CountingEdgeIterator {
private final GeoPoint testPoint;
private final Plane plane;
private final Plane abovePlane;
private final Plane belowPlane;
private final Membership bound1;
private final Membership bound2;
private final double thePointX;
private final double thePointY;
private final double thePointZ;
private boolean onEdge = false;
private int aboveCrossingCount = 0;
private int belowCrossingCount = 0;
public SectorLinearCrossingEdgeIterator(final GeoPoint testPoint,
final Plane plane, final Plane abovePlane, final Plane belowPlane,
final double thePointX, final double thePointY, final double thePointZ) {
assert plane.evaluateIsZero(thePointX, thePointY, thePointZ) : "Check point is not on travel plane";
assert plane.evaluateIsZero(testPoint) : "Test point is not on travel plane";
this.testPoint = testPoint;
this.plane = plane;
this.abovePlane = abovePlane;
this.belowPlane = belowPlane;
final SidedPlane bound1Plane = new SidedPlane(thePointX, thePointY, thePointZ, plane, testPoint);
final SidedPlane bound2Plane = new SidedPlane(testPoint, plane, thePointX, thePointY, thePointZ);
if (bound1Plane.isNumericallyIdentical(bound2Plane)) {
throw new IllegalArgumentException("Sector iterator unreliable when bounds planes are numerically identical");
}
this.bound1 = bound1Plane;
this.bound2 = bound2Plane;
this.thePointX = thePointX;
this.thePointY = thePointY;
this.thePointZ = thePointZ;
}
@Override
public int getCrossingCount() {
return Math.min(aboveCrossingCount, belowCrossingCount);
}
@Override
public boolean isOnEdge() {
return onEdge;
}
@Override
public boolean matches(final Edge edge) {
if (edge.isWithin(thePointX, thePointY, thePointZ)) {
onEdge = true;
return false;
}
final GeoPoint[] planeCrossings = plane.findIntersections(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
if (planeCrossings == null) {
} else if (planeCrossings.length == 0) {
if (!plane.evaluateIsZero(edge.startPoint) && !plane.evaluateIsZero(edge.endPoint)) {
return true;
} else {
}
} else {
}
final int aboveCrossings = countCrossings(edge, abovePlane, bound1, bound2);
aboveCrossingCount += aboveCrossings;
final int belowCrossings = countCrossings(edge, belowPlane, bound1, bound2);
belowCrossingCount += belowCrossings;
return true;
}
private int countCrossings(final Edge edge,
final Plane envelopePlane, final Membership envelopeBound1, final Membership envelopeBound2) {
final GeoPoint[] intersections = edge.plane.findIntersections(planetModel, envelopePlane, envelopeBound1, envelopeBound2);
int crossings = 0;
if (intersections != null) {
for (final GeoPoint intersection : intersections) {
if (edge.startPlane.strictlyWithin(intersection) && edge.endPlane.strictlyWithin(intersection)) {
final int counter = edgeCrossesEnvelope(edge.plane, intersection, envelopePlane)?1:0;
crossings += counter;
}
}
} else {
}
return crossings;
}
private boolean edgeCrossesEnvelope(final Plane edgePlane, final GeoPoint intersectionPoint, final Plane envelopePlane) {
final GeoPoint[] adjoiningPoints = findAdjoiningPoints(edgePlane, intersectionPoint, envelopePlane);
if (adjoiningPoints == null) {
return true;
}
int withinCount = 0;
for (final GeoPoint adjoining : adjoiningPoints) {
if (plane.evaluateIsZero(adjoining) && bound1.isWithin(adjoining) && bound2.isWithin(adjoining)) {
withinCount++;
} else {
}
}
return (withinCount & 1) != 0;
}
}
private class DualCrossingEdgeIterator implements CountingEdgeIterator {
private Set<Edge> seenEdges = null;
private final GeoPoint testPoint;
private final Plane testPointPlane;
private final Plane testPointAbovePlane;
private final Plane testPointBelowPlane;
private final Plane travelPlane;
private final Plane travelAbovePlane;
private final Plane travelBelowPlane;
private final double thePointX;
private final double thePointY;
private final double thePointZ;
private final GeoPoint intersectionPoint;
private final SidedPlane testPointCutoffPlane;
private final SidedPlane checkPointCutoffPlane;
private final SidedPlane testPointOtherCutoffPlane;
private final SidedPlane checkPointOtherCutoffPlane;
private boolean computedInsideOutside = false;
private Plane testPointInsidePlane;
private Plane testPointOutsidePlane;
private Plane travelInsidePlane;
private Plane travelOutsidePlane;
private SidedPlane insideTestPointCutoffPlane;
private SidedPlane insideTravelCutoffPlane;
private SidedPlane outsideTestPointCutoffPlane;
private SidedPlane outsideTravelCutoffPlane;
private boolean onEdge = false;
private int innerCrossingCount = 0;
private int outerCrossingCount = 0;
public DualCrossingEdgeIterator(final GeoPoint testPoint,
final Plane testPointPlane, final Plane testPointAbovePlane, final Plane testPointBelowPlane,
final Plane travelPlane, final Plane travelAbovePlane, final Plane travelBelowPlane,
final double thePointX, final double thePointY, final double thePointZ, final GeoPoint intersectionPoint) {
this.testPoint = testPoint;
this.testPointPlane = testPointPlane;
this.testPointAbovePlane = testPointAbovePlane;
this.testPointBelowPlane = testPointBelowPlane;
this.travelPlane = travelPlane;
this.travelAbovePlane = travelAbovePlane;
this.travelBelowPlane = travelBelowPlane;
this.thePointX = thePointX;
this.thePointY = thePointY;
this.thePointZ = thePointZ;
this.intersectionPoint = intersectionPoint;
assert travelPlane.evaluateIsZero(intersectionPoint) : "intersection point must be on travel plane";
assert testPointPlane.evaluateIsZero(intersectionPoint) : "intersection point must be on test point plane";
assert !testPoint.isNumericallyIdentical(intersectionPoint) : "test point is the same as intersection point";
assert !intersectionPoint.isNumericallyIdentical(thePointX, thePointY, thePointZ) : "check point is same as intersection point";
final SidedPlane testPointBound1 = new SidedPlane(intersectionPoint, testPointPlane, testPoint);
final SidedPlane testPointBound2 = new SidedPlane(testPoint, testPointPlane, intersectionPoint);
if (testPointBound1.isFunctionallyIdentical(testPointBound2)) {
throw new IllegalArgumentException("Dual iterator unreliable when bounds planes are functionally identical");
}
this.testPointCutoffPlane = testPointBound1;
this.testPointOtherCutoffPlane = testPointBound2;
final SidedPlane checkPointBound1 = new SidedPlane(intersectionPoint, travelPlane, thePointX, thePointY, thePointZ);
final SidedPlane checkPointBound2 = new SidedPlane(thePointX, thePointY, thePointZ, travelPlane, intersectionPoint);
if (checkPointBound1.isFunctionallyIdentical(checkPointBound2)) {
throw new IllegalArgumentException("Dual iterator unreliable when bounds planes are functionally identical");
}
this.checkPointCutoffPlane = checkPointBound1;
this.checkPointOtherCutoffPlane = checkPointBound2;
assert testPointCutoffPlane.isWithin(intersectionPoint) : "intersection must be within testPointCutoffPlane";
assert testPointOtherCutoffPlane.isWithin(intersectionPoint) : "intersection must be within testPointOtherCutoffPlane";
assert checkPointCutoffPlane.isWithin(intersectionPoint) : "intersection must be within checkPointCutoffPlane";
assert checkPointOtherCutoffPlane.isWithin(intersectionPoint) : "intersection must be within checkPointOtherCutoffPlane";
}
protected void computeInsideOutside() {
if (!computedInsideOutside) {
final Membership intersectionBound1 = new SidedPlane(testPoint, travelPlane, travelPlane.D);
final Membership intersectionBound2 = new SidedPlane(thePointX, thePointY, thePointZ, testPointPlane, testPointPlane.D);
assert intersectionBound1.isWithin(intersectionPoint) : "intersection must be within intersectionBound1";
assert intersectionBound2.isWithin(intersectionPoint) : "intersection must be within intersectionBound2";
final GeoPoint[] aboveAbove = travelAbovePlane.findIntersections(planetModel, testPointAbovePlane, intersectionBound1, intersectionBound2);
assert aboveAbove != null : "Above + above should not be coplanar";
final GeoPoint[] aboveBelow = travelAbovePlane.findIntersections(planetModel, testPointBelowPlane, intersectionBound1, intersectionBound2);
assert aboveBelow != null : "Above + below should not be coplanar";
final GeoPoint[] belowBelow = travelBelowPlane.findIntersections(planetModel, testPointBelowPlane, intersectionBound1, intersectionBound2);
assert belowBelow != null : "Below + below should not be coplanar";
final GeoPoint[] belowAbove = travelBelowPlane.findIntersections(planetModel, testPointAbovePlane, intersectionBound1, intersectionBound2);
assert belowAbove != null : "Below + above should not be coplanar";
assert ((aboveAbove.length > 0)?1:0) + ((aboveBelow.length > 0)?1:0) + ((belowBelow.length > 0)?1:0) + ((belowAbove.length > 0)?1:0) == 1 : "Can be exactly one inside point, instead was: aa="+aboveAbove.length+" ab=" + aboveBelow.length+" bb="+ belowBelow.length+" ba=" + belowAbove.length;
final GeoPoint[] insideInsidePoints;
if (aboveAbove.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointBelowPlane;
insideInsidePoints = aboveAbove;
} else if (aboveBelow.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointAbovePlane;
insideInsidePoints = aboveBelow;
} else if (belowBelow.length > 0) {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointAbovePlane;
insideInsidePoints = belowBelow;
} else if (belowAbove.length > 0) {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointBelowPlane;
insideInsidePoints = belowAbove;
} else {
throw new IllegalStateException("Can't find traversal intersection among: "+travelAbovePlane+", "+testPointAbovePlane+", "+travelBelowPlane+", "+testPointBelowPlane);
}
final GeoPoint insideInsidePoint = pickProximate(insideInsidePoints);
final GeoPoint[] outsideOutsidePoints = testPointOutsidePlane.findIntersections(planetModel, travelOutsidePlane);
final GeoPoint outsideOutsidePoint = pickProximate(outsideOutsidePoints);
insideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, travelInsidePlane, insideInsidePoint);
outsideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, travelInsidePlane, outsideOutsidePoint);
insideTestPointCutoffPlane = new SidedPlane(testPoint, testPointInsidePlane, insideInsidePoint);
outsideTestPointCutoffPlane = new SidedPlane(testPoint, testPointOutsidePlane, outsideOutsidePoint);
computedInsideOutside = true;
}
}
private GeoPoint pickProximate(final GeoPoint[] points) {
if (points.length == 0) {
throw new IllegalArgumentException("No off-plane intersection points were found; can't compute traversal");
} else if (points.length == 1) {
return points[0];
} else {
final double p1dist = computeSquaredDistance(points[0], intersectionPoint);
final double p2dist = computeSquaredDistance(points[1], intersectionPoint);
if (p1dist < p2dist) {
return points[0];
} else if (p2dist < p1dist) {
return points[1];
} else {
throw new IllegalArgumentException("Neither off-plane intersection point matched intersection point; intersection = "+intersectionPoint+"; offplane choice 0: "+points[0]+"; offplane choice 1: "+points[1]);
}
}
}
@Override
public int getCrossingCount() {
return Math.min(innerCrossingCount, outerCrossingCount);
}
@Override
public boolean isOnEdge() {
return onEdge;
}
@Override
public boolean matches(final Edge edge) {
if (edge.isWithin(thePointX, thePointY, thePointZ)) {
onEdge = true;
return false;
}
if (seenEdges != null && seenEdges.contains(edge)) {
return true;
}
if (seenEdges == null) {
seenEdges = new HashSet<>();
}
seenEdges.add(edge);
computeInsideOutside();
final GeoPoint[] travelCrossings = travelPlane.findIntersections(planetModel, edge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
if (travelCrossings != null && travelCrossings.length == 0) {
final GeoPoint[] testPointCrossings = testPointPlane.findIntersections(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
if (testPointCrossings != null && testPointCrossings.length == 0) {
if (!travelPlane.evaluateIsZero(edge.startPoint) && !travelPlane.evaluateIsZero(edge.endPoint) &&
!testPointPlane.evaluateIsZero(edge.startPoint) && !testPointPlane.evaluateIsZero(edge.endPoint)) {
return true;
} else {
}
} else {
}
} else {
}
innerCrossingCount += countCrossings(edge, travelInsidePlane, checkPointCutoffPlane, insideTravelCutoffPlane, testPointInsidePlane, testPointCutoffPlane, insideTestPointCutoffPlane);
outerCrossingCount += countCrossings(edge, travelOutsidePlane, checkPointCutoffPlane, outsideTravelCutoffPlane, testPointOutsidePlane, testPointCutoffPlane, outsideTestPointCutoffPlane);
return true;
}
private int countCrossings(final Edge edge,
final Plane travelEnvelopePlane, final Membership travelEnvelopeBound1, final Membership travelEnvelopeBound2,
final Plane testPointEnvelopePlane, final Membership testPointEnvelopeBound1, final Membership testPointEnvelopeBound2) {
final GeoPoint[] travelIntersections = edge.plane.findIntersections(planetModel, travelEnvelopePlane, travelEnvelopeBound1, travelEnvelopeBound2);
final GeoPoint[] testPointIntersections = edge.plane.findIntersections(planetModel, testPointEnvelopePlane, testPointEnvelopeBound1, testPointEnvelopeBound2);
int crossings = 0;
if (travelIntersections != null) {
for (final GeoPoint intersection : travelIntersections) {
if (edge.startPlane.strictlyWithin(intersection) && edge.endPlane.strictlyWithin(intersection)) {
boolean notDup = true;
if (testPointIntersections != null) {
for (final GeoPoint otherIntersection : testPointIntersections) {
if (edge.startPlane.strictlyWithin(otherIntersection) && edge.endPlane.strictlyWithin(otherIntersection) && intersection.isNumericallyIdentical(otherIntersection)) {
notDup = false;
break;
}
}
}
if (!notDup) {
continue;
}
crossings += edgeCrossesEnvelope(edge.plane, intersection, travelEnvelopePlane)?1:0;
}
}
}
if (testPointIntersections != null) {
for (final GeoPoint intersection : testPointIntersections) {
if (edge.startPlane.strictlyWithin(intersection) && edge.endPlane.strictlyWithin(intersection)) {
crossings += edgeCrossesEnvelope(edge.plane, intersection, testPointEnvelopePlane)?1:0;
}
}
}
return crossings;
}
private boolean edgeCrossesEnvelope(final Plane edgePlane, final GeoPoint intersectionPoint, final Plane envelopePlane) {
final GeoPoint[] adjoiningPoints = findAdjoiningPoints(edgePlane, intersectionPoint, envelopePlane);
if (adjoiningPoints == null) {
return true;
}
int withinCount = 0;
for (final GeoPoint adjoining : adjoiningPoints) {
if ((travelPlane.evaluateIsZero(adjoining) && checkPointCutoffPlane.isWithin(adjoining) && checkPointOtherCutoffPlane.isWithin(adjoining)) ||
(testPointPlane.evaluateIsZero(adjoining) && testPointCutoffPlane.isWithin(adjoining) && testPointOtherCutoffPlane.isWithin(adjoining))) {
withinCount++;
} else {
}
}
return (withinCount & 1) != 0;
}
}
private final static double DELTA_DISTANCE = Vector.MINIMUM_RESOLUTION;
private final static int MAX_ITERATIONS = 100;
private final static double OFF_PLANE_AMOUNT = Vector.MINIMUM_RESOLUTION * 0.1;
private GeoPoint[] findAdjoiningPoints(final Plane plane, final GeoPoint pointOnPlane, final Plane envelopePlane) {
final Vector perpendicular = new Vector(plane, pointOnPlane);
double distanceFactor = 0.0;
for (int i = 0; i < MAX_ITERATIONS; i++) {
distanceFactor += DELTA_DISTANCE;
final GeoPoint pointA = planetModel.createSurfacePoint(pointOnPlane.x + perpendicular.x * distanceFactor,
pointOnPlane.y + perpendicular.y * distanceFactor,
pointOnPlane.z + perpendicular.z * distanceFactor);
final GeoPoint pointB = planetModel.createSurfacePoint(pointOnPlane.x - perpendicular.x * distanceFactor,
pointOnPlane.y - perpendicular.y * distanceFactor,
pointOnPlane.z - perpendicular.z * distanceFactor);
if (Math.abs(envelopePlane.evaluate(pointA)) > OFF_PLANE_AMOUNT && Math.abs(envelopePlane.evaluate(pointB)) > OFF_PLANE_AMOUNT) {
return new GeoPoint[]{pointA, pointB};
}
}
return null;
}
private static double computeSquaredDistance(final GeoPoint checkPoint, final GeoPoint intersectionPoint) {
final double distanceX = checkPoint.x - intersectionPoint.x;
final double distanceY = checkPoint.y - intersectionPoint.y;
final double distanceZ = checkPoint.z - intersectionPoint.z;
return distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoComplexPolygon))
return false;
final GeoComplexPolygon other = (GeoComplexPolygon) o;
return super.equals(other) && testPoint1InSet == other.testPoint1InSet
&& testPoint1.equals(other.testPoint1)
&& pointsList.equals(other.pointsList);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Boolean.hashCode(testPoint1InSet);
result = 31 * result + testPoint1.hashCode();
result = 31 * result + pointsList.hashCode();
return result;
}
@Override
public String toString() {
final StringBuilder edgeDescription = new StringBuilder();
for (final Edge shapeStartEdge : shapeStartEdges) {
fillInEdgeDescription(edgeDescription, shapeStartEdge);
}
return "GeoComplexPolygon: {planetmodel=" + planetModel + ", number of shapes="+shapeStartEdges.length+", address="+ Integer.toHexString(hashCode())+", testPoint="+testPoint1+", testPointInSet="+testPoint1InSet+", shapes={"+edgeDescription+"}}";
}
private static void fillInEdgeDescription(final StringBuilder description, final Edge startEdge) {
description.append(" {");
Edge currentEdge = startEdge;
int edgeCounter = 0;
while (true) {
if (edgeCounter > 0) {
description.append(", ");
}
if (edgeCounter >= 20) {
description.append("...");
break;
}
description.append(currentEdge.startPoint);
currentEdge = currentEdge.next;
if (currentEdge == startEdge) {
break;
}
edgeCounter++;
}
}
}