/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.lucene.spatial3d.geom;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
/**
* GeoComplexPolygon objects are structures designed to handle very large numbers of edges.
* They perform very well in this case compared to the alternatives, which all have O(N) evaluation
* and O(N^2) setup times. Complex polygons have O(N) setup times and best case O(log(N))
* evaluation times.
*
* The tradeoff is that these objects perform object creation when evaluating intersects() and
* isWithin().
*
* @lucene.internal
*/
class GeoComplexPolygon extends GeoBasePolygon {
private final Tree xTree;
private final Tree yTree;
private final Tree zTree;
private final boolean testPointInSet;
private final GeoPoint testPoint;
private final Plane testPointFixedYPlane;
private final Plane testPointFixedYAbovePlane;
private final Plane testPointFixedYBelowPlane;
private final Plane testPointFixedXPlane;
private final Plane testPointFixedXAbovePlane;
private final Plane testPointFixedXBelowPlane;
private final Plane testPointFixedZPlane;
private final Plane testPointFixedZAbovePlane;
private final Plane testPointFixedZBelowPlane;
private final GeoPoint[] edgePoints;
private final Edge[] shapeStartEdges;
/**
* Create a complex polygon from multiple lists of points, and a single point which is known to be in or out of
* set.
*@param planetModel is the planet model.
*@param pointsList is the list of lists of edge points. The edge points describe edges, and have an implied
* return boundary, so that N edges require N points. These points have furthermore been filtered so that
* no adjacent points are identical (within the bounds of the definition used by this package). It is assumed
* that no edges intersect, but the structure can contain both outer rings as well as holes.
*@param testPoint is the point whose in/out of setness is known.
*@param testPointInSet is true if the test point is considered "within" the polygon.
*/
public GeoComplexPolygon(final PlanetModel planetModel, final List<List<GeoPoint>> pointsList, final GeoPoint testPoint, final boolean testPointInSet) {
super(planetModel);
this.testPointInSet = testPointInSet;
this.testPoint = testPoint;
this.testPointFixedYPlane = new Plane(0.0, 1.0, 0.0, -testPoint.y);
this.testPointFixedXPlane = new Plane(1.0, 0.0, 0.0, -testPoint.x);
this.testPointFixedZPlane = new Plane(0.0, 0.0, 1.0, -testPoint.z);
this.testPointFixedYAbovePlane = new Plane(testPointFixedYPlane, true);
this.testPointFixedYBelowPlane = new Plane(testPointFixedYPlane, false);
this.testPointFixedXAbovePlane = new Plane(testPointFixedXPlane, true);
this.testPointFixedXBelowPlane = new Plane(testPointFixedXPlane, false);
this.testPointFixedZAbovePlane = new Plane(testPointFixedZPlane, true);
this.testPointFixedZBelowPlane = new Plane(testPointFixedZPlane, false);
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) {
final Edge edge = new Edge(planetModel, lastGeoPoint, thisGeoPoint);
allEdges.add(edge);
// Now, link
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);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
// If we're right on top of the point, we know the answer.
if (testPoint.isNumericallyIdentical(x, y, z)) {
return testPointInSet;
}
// If we're right on top of any of the test planes, we navigate solely on that plane.
if (testPointFixedYPlane.evaluateIsZero(x, y, z)) {
// Use the XZ plane exclusively.
final LinearCrossingEdgeIterator crossingEdgeIterator = new LinearCrossingEdgeIterator(testPointFixedYPlane, testPointFixedYAbovePlane, testPointFixedYBelowPlane, x, y, z);
// Traverse our way from the test point to the check point. Use the y tree because that's fixed.
if (!yTree.traverse(crossingEdgeIterator, testPoint.y)) {
// Endpoint is on edge
return true;
}
return ((crossingEdgeIterator.crossingCount & 1) == 0)?testPointInSet:!testPointInSet;
} else if (testPointFixedXPlane.evaluateIsZero(x, y, z)) {
// Use the YZ plane exclusively.
final LinearCrossingEdgeIterator crossingEdgeIterator = new LinearCrossingEdgeIterator(testPointFixedXPlane, testPointFixedXAbovePlane, testPointFixedXBelowPlane, x, y, z);
// Traverse our way from the test point to the check point. Use the x tree because that's fixed.
if (!xTree.traverse(crossingEdgeIterator, testPoint.x)) {
// Endpoint is on edge
return true;
}
return ((crossingEdgeIterator.crossingCount & 1) == 0)?testPointInSet:!testPointInSet;
} else if (testPointFixedZPlane.evaluateIsZero(x, y, z)) {
// Use the XY plane exclusively.
final LinearCrossingEdgeIterator crossingEdgeIterator = new LinearCrossingEdgeIterator(testPointFixedZPlane, testPointFixedZAbovePlane, testPointFixedZBelowPlane, x, y, z);
// Traverse our way from the test point to the check point. Use the z tree because that's fixed.
if (!zTree.traverse(crossingEdgeIterator, testPoint.z)) {
// Endpoint is on edge
return true;
}
return ((crossingEdgeIterator.crossingCount & 1) == 0)?testPointInSet:!testPointInSet;
} else {
// This is the expensive part!!
// Changing the code below has an enormous impact on the queries per second we see with the benchmark.
// We need to use two planes to get there. We don't know which two planes will do it but we can figure it out.
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);
// Find the intersection points for each one of these and the complementary test point planes.
final GeoPoint[] XIntersectionsY = travelPlaneFixedX.findIntersections(planetModel, testPointFixedYPlane);
final GeoPoint[] XIntersectionsZ = travelPlaneFixedX.findIntersections(planetModel, testPointFixedZPlane);
final GeoPoint[] YIntersectionsX = travelPlaneFixedY.findIntersections(planetModel, testPointFixedXPlane);
final GeoPoint[] YIntersectionsZ = travelPlaneFixedY.findIntersections(planetModel, testPointFixedZPlane);
final GeoPoint[] ZIntersectionsX = travelPlaneFixedZ.findIntersections(planetModel, testPointFixedXPlane);
final GeoPoint[] ZIntersectionsY = travelPlaneFixedZ.findIntersections(planetModel, testPointFixedYPlane);
// There will be multiple intersection points found. We choose the one that has the lowest total distance, as measured in delta X, delta Y, and delta Z.
double bestDistance = Double.POSITIVE_INFINITY;
double firstLegValue = 0.0;
double secondLegValue = 0.0;
Plane firstLegPlane = null;
Plane firstLegAbovePlane = null;
Plane firstLegBelowPlane = null;
Plane secondLegPlane = null;
Tree firstLegTree = null;
Tree secondLegTree = null;
GeoPoint intersectionPoint = null;
for (final GeoPoint p : XIntersectionsY) {
// Travel would be in YZ plane (fixed x) then in XZ (fixed y)
// We compute distance we need to travel as a placeholder for the number of intersections we might encounter.
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.x - p.x) * (testPoint.x - p.x) + (testPoint.z - p.z) * (testPoint.z - p.z) + (thePoint.y - p.y) * (thePoint.y - p.y) + (thePoint.z - p.z) * (thePoint.z - p.z);
//final double newDistance = Math.abs(testPoint.x - p.x) + Math.abs(thePoint.y - p.y);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.y;
secondLegValue = x;
firstLegPlane = testPointFixedYPlane;
firstLegAbovePlane = testPointFixedYAbovePlane;
firstLegBelowPlane = testPointFixedYBelowPlane;
secondLegPlane = travelPlaneFixedX;
firstLegTree = yTree;
secondLegTree = xTree;
intersectionPoint = p;
}
}
for (final GeoPoint p : XIntersectionsZ) {
// Travel would be in YZ plane (fixed x) then in XY (fixed z)
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.x - p.x) * (testPoint.x - p.x) + (testPoint.y - p.y) * (testPoint.y - p.y) + (thePoint.y - p.y) * (thePoint.y - p.y) + (thePoint.z - p.z) * (thePoint.z - p.z);
//final double newDistance = Math.abs(testPoint.x - p.x) + Math.abs(thePoint.z - p.z);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.z;
secondLegValue = x;
firstLegPlane = testPointFixedZPlane;
firstLegAbovePlane = testPointFixedZAbovePlane;
firstLegBelowPlane = testPointFixedZBelowPlane;
secondLegPlane = travelPlaneFixedX;
firstLegTree = zTree;
secondLegTree = xTree;
intersectionPoint = p;
}
}
for (final GeoPoint p : YIntersectionsX) {
// Travel would be in XZ plane (fixed y) then in YZ (fixed x)
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.y - p.y) * (testPoint.y - p.y) + (testPoint.z - p.z) * (testPoint.z - p.z) + (thePoint.x - p.x) * (thePoint.x - p.x) + (thePoint.z - p.z) * (thePoint.z - p.z);
//final double newDistance = Math.abs(testPoint.y - p.y) + Math.abs(thePoint.x - p.x);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.x;
secondLegValue = y;
firstLegPlane = testPointFixedXPlane;
firstLegAbovePlane = testPointFixedXAbovePlane;
firstLegBelowPlane = testPointFixedXBelowPlane;
secondLegPlane = travelPlaneFixedY;
firstLegTree = xTree;
secondLegTree = yTree;
intersectionPoint = p;
}
}
for (final GeoPoint p : YIntersectionsZ) {
// Travel would be in XZ plane (fixed y) then in XY (fixed z)
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.x - p.x) * (testPoint.x - p.x) + (testPoint.y - p.y) * (testPoint.y - p.y) + (thePoint.x - p.x) * (thePoint.x - p.x) + (thePoint.z - p.z) * (thePoint.z - p.z);
//final double newDistance = Math.abs(testPoint.y - p.y) + Math.abs(thePoint.z - p.z);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.z;
secondLegValue = y;
firstLegPlane = testPointFixedZPlane;
firstLegAbovePlane = testPointFixedZAbovePlane;
firstLegBelowPlane = testPointFixedZBelowPlane;
secondLegPlane = travelPlaneFixedY;
firstLegTree = zTree;
secondLegTree = yTree;
intersectionPoint = p;
}
}
for (final GeoPoint p : ZIntersectionsX) {
// Travel would be in XY plane (fixed z) then in YZ (fixed x)
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.y - p.y) * (testPoint.y - p.y) + (testPoint.z - p.z) * (testPoint.z - p.z) + (thePoint.y - p.y) * (thePoint.y - p.y) + (thePoint.x - p.x) * (thePoint.x - p.x);
//final double newDistance = Math.abs(testPoint.z - p.z) + Math.abs(thePoint.x - p.x);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.x;
secondLegValue = z;
firstLegPlane = testPointFixedXPlane;
firstLegAbovePlane = testPointFixedXAbovePlane;
firstLegBelowPlane = testPointFixedXBelowPlane;
secondLegPlane = travelPlaneFixedZ;
firstLegTree = xTree;
secondLegTree = zTree;
intersectionPoint = p;
}
}
for (final GeoPoint p : ZIntersectionsY) {
// Travel would be in XY plane (fixed z) then in XZ (fixed y)
//final double newDistance = p.arcDistance(testPoint) + p.arcDistance(thePoint);
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;
//final double newDistance = (testPoint.x - p.x) * (testPoint.x - p.x) + (testPoint.z - p.z) * (testPoint.z - p.z) + (thePoint.y - p.y) * (thePoint.y - p.y) + (thePoint.x - p.x) * (thePoint.x - p.x);
//final double newDistance = Math.abs(testPoint.z - p.z) + Math.abs(thePoint.y - p.y);
if (newDistance < bestDistance) {
bestDistance = newDistance;
firstLegValue = testPoint.y;
secondLegValue = z;
firstLegPlane = testPointFixedYPlane;
firstLegAbovePlane = testPointFixedYAbovePlane;
firstLegBelowPlane = testPointFixedYBelowPlane;
secondLegPlane = travelPlaneFixedZ;
firstLegTree = yTree;
secondLegTree = zTree;
intersectionPoint = p;
}
}
assert bestDistance > 0.0 : "Best distance should not be zero unless on single plane";
assert bestDistance < Double.POSITIVE_INFINITY : "Couldn't find an intersection point of any kind";
final DualCrossingEdgeIterator edgeIterator = new DualCrossingEdgeIterator(firstLegPlane, firstLegAbovePlane, firstLegBelowPlane, secondLegPlane, x, y, z, intersectionPoint);
if (!firstLegTree.traverse(edgeIterator, firstLegValue)) {
return true;
}
edgeIterator.setSecondLeg();
if (!secondLegTree.traverse(edgeIterator, secondLegValue)) {
return true;
}
return ((edgeIterator.crossingCount & 1) == 0)?testPointInSet:!testPointInSet;
}
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
// Create the intersector
final EdgeIterator intersector = new IntersectorEdgeIterator(p, notablePoints, bounds);
// First, compute the bounds for the the plane
final XYZBounds xyzBounds = new XYZBounds();
p.recordBounds(planetModel, xyzBounds, bounds);
for (final GeoPoint point : notablePoints) {
xyzBounds.addPoint(point);
}
// Figure out which tree likely works best
final double xDelta = xyzBounds.getMaximumX() - xyzBounds.getMinimumX();
final double yDelta = xyzBounds.getMaximumY() - xyzBounds.getMinimumY();
final double zDelta = xyzBounds.getMaximumZ() - xyzBounds.getMinimumZ();
// Select the smallest range
if (xDelta <= yDelta && xDelta <= zDelta) {
// Drill down in x
return !xTree.traverse(intersector, xyzBounds.getMinimumX(), xyzBounds.getMaximumX());
} else if (yDelta <= xDelta && yDelta <= zDelta) {
// Drill down in y
return !yTree.traverse(intersector, xyzBounds.getMinimumY(), xyzBounds.getMaximumY());
} else if (zDelta <= xDelta && zDelta <= yDelta) {
// Drill down in z
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;
}
/**
* An instance of this class describes a single edge, and includes what is necessary to reliably determine intersection
* in the context of the even/odd algorithm used.
*/
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 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);
this.planeBounds = new XYZBounds();
this.planeBounds.addPoint(startPoint);
this.planeBounds.addPoint(endPoint);
this.planeBounds.addPlane(pm, this.plane, this.startPlane, this.endPlane);
//System.err.println("Recording edge "+this+" from "+startPoint+" to "+endPoint+"; bounds = "+planeBounds);
}
}
/**
* Iterator execution interface, for tree traversal. Pass an object implementing this interface
* into the traversal method of a tree, and each edge that matches will cause this object to be
* called.
*/
private static interface EdgeIterator {
/**
* @param edge is the edge that matched.
* @return true if the iteration should continue, false otherwise.
*/
public boolean matches(final Edge edge);
}
/**
* An instance of this class represents a node in a tree. The tree is designed to be given
* a value and from that to iterate over a list of edges.
* In order to do this efficiently, each new edge is dropped into the tree using its minimum and
* maximum value. If the new edge's value does not overlap the range, then it gets added
* either to the lesser side or the greater side, accordingly. If it does overlap, then the
* "overlapping" chain is instead traversed.
*
* This class is generic and can be used for any definition of "value".
*
*/
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) {
// Does this node overlap?
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 && minValue >= low && right.traverse(edgeIterator, minValue, maxValue) == false) {
return false;
}
}
return true;
}
}
/** An interface describing a tree.
*/
private static abstract class Tree {
private final Node rootNode;
protected static final Edge[] EMPTY_ARRAY = new Edge[0];
/** Constructor.
* @param allEdges is the list of all edges for the tree.
*/
public Tree(final List<Edge> allEdges) {
// Dump edges into an array and then sort it
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;
}
// add midpoint
int mid = (low + high) >>> 1;
final Node newNode = edges[mid];
// add children
newNode.left = createTree(edges, low, mid - 1);
newNode.right = createTree(edges, mid + 1, high);
// pull up max values to this node
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;
}
/** Get the minimum value from the edge.
* @param edge is the edge.
* @return the minimum value.
*/
protected abstract double getMinimum(final Edge edge);
/** Get the maximum value from the edge.
* @param edge is the edge.
* @return the maximum value.
*/
protected abstract double getMaximum(final Edge edge);
/** Traverse the tree, finding all edges that intersect the provided value.
* @param edgeIterator provides the method to call for any encountered matching edge.
* @param value is the value to match.
* @return false if the traversal was aborted before completion.
*/
public boolean traverse(final EdgeIterator edgeIterator, final double value) {
return traverse(edgeIterator, value, value);
}
/** Traverse the tree, finding all edges that intersect the provided value range.
* @param edgeIterator provides the method to call for any encountered matching edge.
* Edges will not be invoked more than once.
* @param minValue is the minimum value.
* @param maxValue is the maximum value.
* @return false if the traversal was aborted before completion.
*/
public boolean traverse(final EdgeIterator edgeIterator, final double minValue, final double maxValue) {
if (rootNode == null) {
return true;
}
return rootNode.traverse(edgeIterator, minValue, maxValue);
}
}
/** This is the z-tree.
*/
private static class ZTree extends Tree {
public Node rootNode = null;
public ZTree(final List<Edge> allEdges) {
super(allEdges);
}
/*
@Override
public boolean traverse(final EdgeIterator edgeIterator, final double value) {
System.err.println("Traversing in Z, value= "+value+"...");
return super.traverse(edgeIterator, value);
}
*/
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumZ();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumZ();
}
}
/** This is the y-tree.
*/
private static class YTree extends Tree {
public YTree(final List<Edge> allEdges) {
super(allEdges);
}
/*
@Override
public boolean traverse(final EdgeIterator edgeIterator, final double value) {
System.err.println("Traversing in Y, value= "+value+"...");
return super.traverse(edgeIterator, value);
}
*/
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumY();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumY();
}
}
/** This is the x-tree.
*/
private static class XTree extends Tree {
public XTree(final List<Edge> allEdges) {
super(allEdges);
}
/*
@Override
public boolean traverse(final EdgeIterator edgeIterator, final double value) {
System.err.println("Traversing in X, value= "+value+"...");
return super.traverse(edgeIterator, value);
}
*/
@Override
protected double getMinimum(final Edge edge) {
return edge.planeBounds.getMinimumX();
}
@Override
protected double getMaximum(final Edge edge) {
return edge.planeBounds.getMaximumX();
}
}
/** Assess whether edge intersects the provided plane plus bounds.
*/
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);
}
}
/** Count the number of verifiable edge crossings.
*/
private class LinearCrossingEdgeIterator implements EdgeIterator {
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;
public int crossingCount = 0;
public LinearCrossingEdgeIterator(final Plane plane, final Plane abovePlane, final Plane belowPlane, final double thePointX, final double thePointY, final double thePointZ) {
this.plane = plane;
this.abovePlane = abovePlane;
this.belowPlane = belowPlane;
this.bound1 = new SidedPlane(thePointX, thePointY, thePointZ, plane, testPoint);
this.bound2 = new SidedPlane(testPoint, plane, thePointX, thePointY, thePointZ);
this.thePointX = thePointX;
this.thePointY = thePointY;
this.thePointZ = thePointZ;
}
@Override
public boolean matches(final Edge edge) {
// Early exit if the point is on the edge.
if (edge.plane.evaluateIsZero(thePointX, thePointY, thePointZ) && edge.startPlane.isWithin(thePointX, thePointY, thePointZ) && edge.endPlane.isWithin(thePointX, thePointY, thePointZ)) {
return false;
}
final GeoPoint[] crossingPoints = plane.findCrossings(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
if (crossingPoints != null) {
// We need to handle the endpoint case, which is quite tricky.
for (final GeoPoint crossingPoint : crossingPoints) {
countCrossingPoint(crossingPoint, edge);
}
}
return true;
}
private void countCrossingPoint(final GeoPoint crossingPoint, final Edge edge) {
if (crossingPoint.isNumericallyIdentical(edge.startPoint)) {
// We have to figure out if this crossing should be counted.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
assert !(aboveIntersections.length > 0 && belowIntersections.length > 0) : "edge that ends in a crossing can't both up and down";
if (aboveIntersections.length == 0 && belowIntersections.length == 0) {
return;
}
final boolean edgeCrossesAbove = aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.previous;
assessAboveIntersections = abovePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assert !(assessAboveIntersections.length > 0 && assessBelowIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessAboveIntersections.length == 0 && assessBelowIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// To handle the latter situation, we need to know if the other edge will be looked at also, and then we can make
// a decision whether to count or not based on that.
// Compute the crossing points of this other edge.
final GeoPoint[] otherCrossingPoints = plane.findCrossings(planetModel, assessEdge.plane, bound1, bound2, assessEdge.startPlane, assessEdge.endPlane);
// Look for a matching endpoint. If the other endpoint doesn't show up, it is either out of bounds (in which case the
// transition won't be counted for that edge), or it is not a crossing for that edge (so, same conclusion).
for (final GeoPoint otherCrossingPoint : otherCrossingPoints) {
if (otherCrossingPoint.isNumericallyIdentical(assessEdge.endPoint)) {
// Found it!
// Both edges will try to contribute to the crossing count. By convention, we'll only include the earlier one.
// Since we're the latter point, we exit here in that case.
return;
}
}
// Both edges will not count the same point, so we can proceed. We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else if (crossingPoint.isNumericallyIdentical(edge.endPoint)) {
// Figure out if the crossing should be counted.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
assert !(aboveIntersections.length > 0 && belowIntersections.length > 0) : "edge that ends in a crossing can't both up and down";
if (aboveIntersections.length == 0 && belowIntersections.length == 0) {
return;
}
final boolean edgeCrossesAbove = aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.next;
assessAboveIntersections = abovePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assert !(assessAboveIntersections.length > 0 && assessBelowIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessAboveIntersections.length == 0 && assessBelowIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// By definition, we're the earlier plane in this case, so any crossing we detect we must count, by convention. It is unnecessary
// to consider what the other edge does, because when we get to it, it will look back and figure out what we did for this one.
// We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else {
crossingCount++;
}
}
}
/** Count the number of verifiable edge crossings for a dual-leg journey.
*/
private class DualCrossingEdgeIterator implements EdgeIterator {
private boolean isSecondLeg = false;
private final Plane testPointPlane;
private final Plane testPointAbovePlane;
private final Plane testPointBelowPlane;
private final Plane travelPlane;
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;
// These are computed on an as-needed basis
private boolean computedInsideOutside = false;
private Plane testPointInsidePlane;
private Plane testPointOutsidePlane;
private Plane travelInsidePlane;
private Plane travelOutsidePlane;
private SidedPlane insideTestPointCutoffPlane;
private SidedPlane insideTravelCutoffPlane;
// The counter
public int crossingCount = 0;
public DualCrossingEdgeIterator(final Plane testPointPlane, final Plane testPointAbovePlane, final Plane testPointBelowPlane,
final Plane travelPlane, final double thePointX, final double thePointY, final double thePointZ, final GeoPoint intersectionPoint) {
this.testPointPlane = testPointPlane;
this.testPointAbovePlane = testPointAbovePlane;
this.testPointBelowPlane = testPointBelowPlane;
this.travelPlane = travelPlane;
this.thePointX = thePointX;
this.thePointY = thePointY;
this.thePointZ = thePointZ;
this.intersectionPoint = intersectionPoint;
//System.err.println("Intersection point = "+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 is intersection point";
this.testPointCutoffPlane = new SidedPlane(intersectionPoint, testPointPlane, testPoint);
this.checkPointCutoffPlane = new SidedPlane(intersectionPoint, travelPlane, thePointX, thePointY, thePointZ);
this.testPointOtherCutoffPlane = new SidedPlane(testPoint, testPointPlane, intersectionPoint);
this.checkPointOtherCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, travelPlane, intersectionPoint);
// Sanity check
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) {
// Convert travel plane to a sided plane
final Membership intersectionBound1 = new SidedPlane(testPoint, travelPlane, travelPlane.D);
// Convert testPoint plane to a sided plane
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";
// Figure out which of the above/below planes are inside vs. outside. To do this,
// we look for the point that is within the bounds of the testPointPlane and travelPlane. The two sides that intersected there are the inside
// borders.
final Plane travelAbovePlane = new Plane(travelPlane, true);
final Plane travelBelowPlane = new Plane(travelPlane, false);
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;
if (aboveAbove.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointBelowPlane;
} else if (aboveBelow.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointAbovePlane;
} else if (belowBelow.length > 0) {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointAbovePlane;
} else {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointBelowPlane;
}
insideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, testPointInsidePlane, testPointInsidePlane.D);
insideTestPointCutoffPlane = new SidedPlane(testPoint, travelInsidePlane, travelInsidePlane.D);
computedInsideOutside = true;
}
}
public void setSecondLeg() {
isSecondLeg = true;
}
@Override
public boolean matches(final Edge edge) {
//System.err.println("Processing edge "+edge+", startpoint="+edge.startPoint+" endpoint="+edge.endPoint);
// Early exit if the point is on the edge.
if (edge.plane.evaluateIsZero(thePointX, thePointY, thePointZ) && edge.startPlane.isWithin(thePointX, thePointY, thePointZ) && edge.endPlane.isWithin(thePointX, thePointY, thePointZ)) {
//System.err.println(" Check point is on edge: isWithin = true");
return false;
}
// If the intersection point lies on this edge, we should still be able to consider crossing points only.
// Even if an intersection point is eliminated because it's not a crossing of one plane, it will have to be a crossing
// for at least one of the two planes in order to be a legitimate crossing of the combined path.
final GeoPoint[] crossingPoints;
if (isSecondLeg) {
//System.err.println(" check point plane = "+travelPlane);
crossingPoints = travelPlane.findCrossings(planetModel, edge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
} else {
//System.err.println(" test point plane = "+testPointPlane);
crossingPoints = testPointPlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
}
if (crossingPoints != null) {
// We need to handle the endpoint case, which is quite tricky.
for (final GeoPoint crossingPoint : crossingPoints) {
countCrossingPoint(crossingPoint, edge);
}
//System.err.println(" All crossing points processed");
} else {
//System.err.println(" No crossing points!");
}
return true;
}
private void countCrossingPoint(final GeoPoint crossingPoint, final Edge edge) {
//System.err.println(" Crossing point "+crossingPoint);
// We consider crossing points only in this method.
// Unlike the linear case, there are additional cases when:
// (1) The crossing point and the intersection point are the same, but are not the endpoint of an edge;
// (2) The crossing point and the intersection point are the same, and they *are* the endpoint of an edge.
// The other logical difference is that crossings of all kinds have to be considered so that:
// (a) both inside edges are considered together at all times;
// (b) both outside edges are considered together at all times;
// (c) inside edge crossings that are between the other leg's inside and outside edge are ignored.
// Intersection point crossings are either simple, or a crossing on an endpoint.
// In either case, we have to be sure to count each edge only once, since it might appear in both the
// first leg and the second. If the first leg can process it, it should, and the second should skip it.
if (crossingPoint.isNumericallyIdentical(intersectionPoint)) {
//System.err.println(" Crosses intersection point.");
if (isSecondLeg) {
// See whether this edge would have been processed in the first leg; if so, we skip it.
final GeoPoint[] firstLegCrossings = testPointPlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
for (final GeoPoint firstLegCrossing : firstLegCrossings) {
if (firstLegCrossing.isNumericallyIdentical(intersectionPoint)) {
// We already processed it, so we're done here.
//System.err.println(" Already processed on previous leg: exit");
return;
}
}
}
}
// Plane crossing, either first leg or second leg
if (crossingPoint.isNumericallyIdentical(edge.startPoint)) {
//System.err.println(" Crossing point = edge.startPoint");
// We have to figure out if this crossing should be counted.
computeInsideOutside();
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] insideTestPointPlaneIntersections = testPointInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTestPointCutoffPlane);
final GeoPoint[] insideTravelPlaneIntersections = travelInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTravelCutoffPlane);
final GeoPoint[] outsideTestPointPlaneIntersections = testPointOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] outsideTravelPlaneIntersections = travelOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
assert !(insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length > 0 && outsideTestPointPlaneIntersections.length + outsideTravelPlaneIntersections.length > 0) : "edge that ends in a crossing can't both up and down";
if (insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length == 0 && outsideTestPointPlaneIntersections.length + outsideTravelPlaneIntersections.length == 0) {
//System.err.println(" No inside or outside crossings found");
return;
}
final boolean edgeCrossesInside = insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessInsideTestPointIntersections;
GeoPoint[] assessInsideTravelIntersections;
GeoPoint[] assessOutsideTestPointIntersections;
GeoPoint[] assessOutsideTravelIntersections;
while (true) {
assessEdge = assessEdge.previous;
assessInsideTestPointIntersections = testPointInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTestPointCutoffPlane);
assessInsideTravelIntersections = travelInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTravelCutoffPlane);
assessOutsideTestPointIntersections = testPointOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessOutsideTravelIntersections = travelOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
// An edge can cross both outside and inside, because of the corner. But it can be considered to cross the inside ONLY if it crosses either of the inside edges.
//assert !(assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length > 0 && assessOutsideTestPointIntersections.length + assessOutsideTravelIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length == 0 && assessOutsideTestPointIntersections.length + assessOutsideTravelIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// To handle the latter situation, we need to know if the other edge will be looked at also, and then we can make
// a decision whether to count or not based on that.
// Compute the crossing points of this other edge.
final GeoPoint[] otherCrossingPoints;
if (isSecondLeg) {
otherCrossingPoints = travelPlane.findCrossings(planetModel, assessEdge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, assessEdge.startPlane, assessEdge.endPlane);
} else {
otherCrossingPoints = testPointPlane.findCrossings(planetModel, assessEdge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, assessEdge.startPlane, assessEdge.endPlane);
}
// Look for a matching endpoint. If the other endpoint doesn't show up, it is either out of bounds (in which case the
// transition won't be counted for that edge), or it is not a crossing for that edge (so, same conclusion).
for (final GeoPoint otherCrossingPoint : otherCrossingPoints) {
if (otherCrossingPoint.isNumericallyIdentical(assessEdge.endPoint)) {
// Found it!
// Both edges will try to contribute to the crossing count. By convention, we'll only include the earlier one.
// Since we're the latter point, we exit here in that case.
//System.err.println(" Earlier point fired, so this one shouldn't");
return;
}
}
// Both edges will not count the same point, so we can proceed. We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeInside = assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length > 0;
if (assessEdgeInside != edgeCrossesInside) {
//System.err.println(" Incrementing crossing count");
crossingCount++;
} else {
//System.err.println(" Entered and exited on same side");
}
} else if (crossingPoint.isNumericallyIdentical(edge.endPoint)) {
//System.err.println(" Crossing point = edge.endPoint");
// Figure out if the crossing should be counted.
computeInsideOutside();
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] insideTestPointPlaneIntersections = testPointInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTestPointCutoffPlane);
final GeoPoint[] insideTravelPlaneIntersections = travelInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTravelCutoffPlane);
final GeoPoint[] outsideTestPointPlaneIntersections = testPointOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] outsideTravelPlaneIntersections = travelOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
// An edge can cross both outside and inside, because of the corner. But it can be considered to cross the inside ONLY if it crosses either of the inside edges.
//assert !(insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length > 0 && outsideTestPointPlaneIntersections.length + outsideTravelPlaneIntersections.length > 0) : "edge that ends in a crossing can't go both up and down: insideTestPointPlaneIntersections: "+insideTestPointPlaneIntersections.length+" insideTravelPlaneIntersections: "+insideTravelPlaneIntersections.length+" outsideTestPointPlaneIntersections: "+outsideTestPointPlaneIntersections.length+" outsideTravelPlaneIntersections: "+outsideTravelPlaneIntersections.length;
if (insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length == 0 && outsideTestPointPlaneIntersections.length + outsideTravelPlaneIntersections.length == 0) {
//System.err.println(" No inside or outside crossings found");
return;
}
final boolean edgeCrossesInside = insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessInsideTestPointIntersections;
GeoPoint[] assessInsideTravelIntersections;
GeoPoint[] assessOutsideTestPointIntersections;
GeoPoint[] assessOutsideTravelIntersections;
while (true) {
assessEdge = assessEdge.next;
assessInsideTestPointIntersections = testPointInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTestPointCutoffPlane);
assessInsideTravelIntersections = travelInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTravelCutoffPlane);
assessOutsideTestPointIntersections = testPointOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessOutsideTravelIntersections = travelOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assert !(assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length > 0 && assessOutsideTestPointIntersections.length + assessOutsideTravelIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length == 0 && assessOutsideTestPointIntersections.length + assessOutsideTravelIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// By definition, we're the earlier plane in this case, so any crossing we detect we must count, by convention. It is unnecessary
// to consider what the other edge does, because when we get to it, it will look back and figure out what we did for this one.
// We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeInside = assessInsideTestPointIntersections.length + assessInsideTravelIntersections.length > 0;
if (assessEdgeInside != edgeCrossesInside) {
//System.err.println(" Incrementing crossing count");
crossingCount++;
} else {
//System.err.println(" Entered and exited on same side");
}
} else {
//System.err.println(" Not a special case: incrementing crossing count");
// Not a special case, so we can safely count a crossing.
crossingCount++;
}
}
}
@Override
public boolean equals(Object o) {
// Way too expensive to do this the hard way, so each complex polygon will be considered unique.
return this == o;
}
@Override
public int hashCode() {
// Each complex polygon is considered unique.
return System.identityHashCode(this);
}
@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="+testPoint+", testPointInSet="+testPointInSet+", 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++;
}
}
}