Commit 73b1c745 authored by Numanoglu's avatar Numanoglu
Browse files

Redesign BVH and Further Applications and Tests for BVH-Tree

parent a4c9094e
Pipeline #12381 failed with stage
in 2 minutes and 15 seconds
...@@ -103,18 +103,57 @@ public class AABB { ...@@ -103,18 +103,57 @@ public class AABB {
return new AABB(minX, minY, minZ, maxX, maxY, maxZ); return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
} }
/*** ---------------Epsilontic---------------------------------------- ***/
/**
* Returns a new AABB expanded by pad in all directions.
* Negative padding is treated as 0.
*/
public AABB padded(double pad) {
double p = Math.max(0.0, pad);
return new AABB(
minX - p, minY - p, minZ - p,
maxX + p, maxY + p, maxZ + p
);
}
/** Builds a padded AABB around a segment (two vertices). */ /**
public static AABB of(Vertex a, Vertex b, double pad) { * Computes an AABB from a LinearRing and expands it by pad in all directions.
double minX = Math.min(a.getX(), b.getX()) - pad; */
double minY = Math.min(a.getY(), b.getY()) - pad; public static AABB of(LinearRing ring, double pad) {
double minZ = Math.min(a.getZ(), b.getZ()) - pad; return of(ring).padded(pad);
double maxX = Math.max(a.getX(), b.getX()) + pad; }
double maxY = Math.max(a.getY(), b.getY()) + pad;
double maxZ = Math.max(a.getZ(), b.getZ()) + pad; /**
return new AABB(minX, minY, minZ, maxX, maxY, maxZ); * Computes an AABB from a Polygon and expands it by pad in all directions.
} */
public static AABB of(Polygon poly, double pad) {
return of(poly).padded(pad);
}
/**
* Computes a padded AABB for a single point.
* pad == 0 returns the degenerate point AABB.
*/
public static AABB of(Vector3d vertex, double pad) {
return of(vertex).padded(pad);
}
/**
* Builds a padded AABB around a segment (two vertices).
*/
public static AABB of(Vertex a, Vertex b, double pad) {
double p = Math.max(0.0, pad);
double minX = Math.min(a.getX(), b.getX()) - p;
double minY = Math.min(a.getY(), b.getY()) - p;
double minZ = Math.min(a.getZ(), b.getZ()) - p;
double maxX = Math.max(a.getX(), b.getX()) + p;
double maxY = Math.max(a.getY(), b.getY()) + p;
double maxZ = Math.max(a.getZ(), b.getZ()) + p;
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
//----------------------------------------------------------------------------------------
/** /**
* Constructs an AABB from explicit min and max coordinates. * Constructs an AABB from explicit min and max coordinates.
*/ */
......
...@@ -35,6 +35,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; ...@@ -35,6 +35,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
/** /**
* Checks whether a inner ring is completely contained in another inner ring * Checks whether a inner ring is completely contained in another inner ring
...@@ -57,9 +58,40 @@ public class NestedRingsCheck extends Check { ...@@ -57,9 +58,40 @@ public class NestedRingsCheck extends Check {
deps.add(CheckId.C_GE_P_ORIENTATION_RINGS_SAME); deps.add(CheckId.C_GE_P_ORIENTATION_RINGS_SAME);
dependencies = Collections.unmodifiableList(deps); dependencies = Collections.unmodifiableList(deps);
} }
/**
* FilterSwitch @Numanoglu
* true -> use AABB prefilter
* false -> use original double-loop exact version
*/
private boolean useAabbFilter = true;
public NestedRingsCheck() {
}
public NestedRingsCheck(boolean useAabbFilter) {
this.useAabbFilter = useAabbFilter;
}
public void setUseAabbFilter(boolean useAabbFilter) {
this.useAabbFilter = useAabbFilter;
}
public boolean isUseAabbFilter() {
return useAabbFilter;
}
//
@Override @Override
public void check(Polygon p) { public void check(Polygon p) {
if (useAabbFilter) {
checkWithBoundingBoxFilter(p);
} else {
checkOriginal(p);
}
}
private void checkOriginal(Polygon p){
for (LinearRing interiorRing : p.getInnerRings()) { for (LinearRing interiorRing : p.getInnerRings()) {
for (LinearRing checkRing : p.getInnerRings()) { for (LinearRing checkRing : p.getInnerRings()) {
if (checkRing == interiorRing) { if (checkRing == interiorRing) {
...@@ -80,6 +112,71 @@ public class NestedRingsCheck extends Check { ...@@ -80,6 +112,71 @@ public class NestedRingsCheck extends Check {
p.addCheckResult(cr); p.addCheckResult(cr);
} }
/**
* Alternative implementation using cached AABBs as broad-phase filter.
* Exact geometry check is still done via areAllPointsInside(...).
*/
public void checkWithBoundingBoxFilter(Polygon p) {
List<LinearRing> innerRings = p.getInnerRings();
if (innerRings == null || innerRings.size() < 2) {
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
return;
}
List<RingAabbEntry> ringEntries = new ArrayList<>(innerRings.size());
for (LinearRing ring : innerRings) {
ringEntries.add(new RingAabbEntry(ring, AABB.of(ring)));
}
for (int i = 0; i < ringEntries.size(); i++) {
RingAabbEntry outerEntry = ringEntries.get(i);
LinearRing interiorRing = outerEntry.ring;
AABB outerAabb = outerEntry.aabb;
for (int j = 0; j < ringEntries.size(); j++) {
if (i == j) {
// do not compare with itself
continue;
}
RingAabbEntry innerEntry = ringEntries.get(j);
LinearRing checkRing = innerEntry.ring;
AABB innerAabb = innerEntry.aabb;
// Broad phase:
// if outer AABB does not fully contain inner AABB,
// full geometric containment is impossible
if (!outerAabb.contains(innerAabb)) {
continue;
}
// Narrow phase:
// exact test
if (areAllPointsInside(interiorRing, checkRing)) {
CheckError err = new NestedRingError(p, interiorRing, checkRing);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return;
}
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
}
private static final class RingAabbEntry {
final LinearRing ring;
final AABB aabb;
RingAabbEntry(LinearRing ring, AABB aabb) {
this.ring = ring;
this.aabb = aabb;
}
}
private boolean areAllPointsInside(LinearRing ring, LinearRing checkRing) { private boolean areAllPointsInside(LinearRing ring, LinearRing checkRing) {
boolean isInside = true; boolean isInside = true;
for (Vertex v : checkRing.getVertices()) { for (Vertex v : checkRing.getVertices()) {
......
...@@ -37,11 +37,12 @@ import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError; ...@@ -37,11 +37,12 @@ import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError;
import de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError; import de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError;
import de.hft.stuttgart.citydoctor2.checks.Checks; import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.Edge; import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.math.CovarianceMatrix; import de.hft.stuttgart.citydoctor2.math.CovarianceMatrix;
import de.hft.stuttgart.citydoctor2.math.DistanceResult; import de.hft.stuttgart.citydoctor2.math.DistanceResult;
import de.hft.stuttgart.citydoctor2.math.Matrix3x3d; import de.hft.stuttgart.citydoctor2.math.Matrix3x3d;
...@@ -50,6 +51,8 @@ import de.hft.stuttgart.citydoctor2.math.Segment3d; ...@@ -50,6 +51,8 @@ import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
/** /**
* Checks whether a ring self intersects. Also checks if a point is too close to * Checks whether a ring self intersects. Also checks if a point is too close to
* an edge of the ring. * an edge of the ring.
...@@ -57,15 +60,22 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -57,15 +60,22 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
* @author Matthias Betz * @author Matthias Betz
* *
*/ */
/**
* Variants:
* - OLD: only BoundingBox replaced by AABB, logic otherwise unchanged
* - TREE_1_EDGE_BVH: edge-edge broad phase via BVH
* - TREE_2_EDGE_AND_VERTEX_BVH: edge-edge via edge BVH, point-edge via vertex BVH
*
* @author Baris Numanoglu
*/
public class RingSelfIntCheck extends Check { public class RingSelfIntCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance"; private static final String EPSILON_NAME = "minVertexDistance";
// check requirement class for default parameters
private double degeneratedRingTolerance = 0.01; private double degeneratedRingTolerance = 0.01;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT; private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
static { static {
...@@ -76,6 +86,28 @@ public class RingSelfIntCheck extends Check { ...@@ -76,6 +86,28 @@ public class RingSelfIntCheck extends Check {
dependencies = Collections.unmodifiableList(deps); dependencies = Collections.unmodifiableList(deps);
} }
public enum Variant {
OLD,
TREE_1_EDGE_BVH,
TREE_2_EDGE_AND_VERTEX_BVH
}
private Variant variant = Variant.OLD;
public RingSelfIntCheck() {
}
public RingSelfIntCheck(Variant variant) {
this.variant = variant;
}
public void setVariant(Variant variant) {
this.variant = variant;
}
public Variant getVariant() {
return variant;
}
@Override @Override
public void init(Map<String, String> parameters, ParserConfiguration config) { public void init(Map<String, String> parameters, ParserConfiguration config) {
...@@ -90,25 +122,38 @@ public class RingSelfIntCheck extends Check { ...@@ -90,25 +122,38 @@ public class RingSelfIntCheck extends Check {
@Override @Override
public void check(LinearRing lr) { public void check(LinearRing lr) {
checkRingJava(lr); switch (variant) {
case OLD:
checkRingJavaOld(lr);
break;
case TREE_1_EDGE_BVH:
checkRingJavaTree1(lr);
break;
case TREE_2_EDGE_AND_VERTEX_BVH:
checkRingJavaTree2(lr);
break;
default:
checkRingJavaOld(lr);
break;
}
} }
private void checkRingJava(LinearRing lr) { /**
// check for tiny edge as well * OLD:
// store all used points in temporary list * only BoundingBox -> AABB replaced, otherwise logic unchanged.
*/
private void checkRingJavaOld(LinearRing lr) {
List<Vertex> vertices = lr.getVertices(); List<Vertex> vertices = lr.getVertices();
Vector3d centroid = CovarianceMatrix.getCentroid(vertices); Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid); EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
if (checkEigenvalues(lr, vertices, ed)) { if (checkEigenvalues(lr, vertices, ed)) {
// found tiny edge error, abort further checking
return; return;
} }
List<Edge> edges = getEdgesForRing(lr); List<Edge> edges = getEdgesForRing(lr);
for (Edge e : edges) { for (Edge e : edges) {
if (checkForPointsTouchingEdge(lr, e)) { if (checkForPointsTouchingEdgeOld(lr, e)) {
return; return;
} }
} }
...@@ -125,7 +170,6 @@ public class RingSelfIntCheck extends Check { ...@@ -125,7 +170,6 @@ public class RingSelfIntCheck extends Check {
Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo()); Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo());
DistanceResult dr = s1.getDistanceResult(s2); DistanceResult dr = s1.getDistanceResult(s2);
if (dr.distance() < epsilon) { if (dr.distance() < epsilon) {
// intersection
CheckError err = new RingEdgeIntersectionError(lr, e1, e2, dr.point1()); CheckError err = new RingEdgeIntersectionError(lr, e1, e2, dr.point1());
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err); CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr); lr.addCheckResult(cr);
...@@ -134,27 +178,167 @@ public class RingSelfIntCheck extends Check { ...@@ -134,27 +178,167 @@ public class RingSelfIntCheck extends Check {
} }
} }
// no errors detected
CheckResult cr = new CheckResult(this, ResultStatus.OK, null); CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr); lr.addCheckResult(cr);
} }
/**
* TREE_1:
* - point-edge remains old
* - edge-edge uses Edge-BVH
*/
private void checkRingJavaTree1(LinearRing lr) {
List<Vertex> vertices = lr.getVertices();
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
if (checkEigenvalues(lr, vertices, ed)) {
return;
}
List<Edge> edges = getEdgesForRing(lr);
for (Edge e : edges) {
if (checkForPointsTouchingEdgeOld(lr, e)) {
return;
}
}
BoundingVolumeHierarchyTree<Edge> edgeTree = new BoundingVolumeHierarchyTree<>(
edges,
e -> AABB.of(e.getFrom(), e.getTo(), epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (int i = 0; i < edges.size(); i++) {
Edge e1 = edges.get(i);
AABB q = AABB.of(e1.getFrom(), e1.getTo(), epsilon);
List<Edge> candidates = edgeTree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
continue;
}
Segment3d s1 = new Segment3d(e1.getFrom(), e1.getTo());
for (Edge e2 : candidates) {
if (e1 == e2) {
continue;
}
if (e1.getConnectionPoint(e2) != null) {
continue;
}
// avoid double pairwise checks
int j = edges.indexOf(e2);
if (j <= i) {
continue;
}
Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo());
DistanceResult dr = s1.getDistanceResult(s2);
if (dr.distance() < epsilon) {
CheckError err = new RingEdgeIntersectionError(lr, e1, e2, dr.point1());
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr);
}
/**
* TREE_2:
* - point-edge uses Vertex-BVH
* - edge-edge uses Edge-BVH
*/
private void checkRingJavaTree2(LinearRing lr) {
List<Vertex> vertices = lr.getVertices();
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
if (checkEigenvalues(lr, vertices, ed)) {
return;
}
List<Edge> edges = getEdgesForRing(lr);
BoundingVolumeHierarchyTree<Vertex> vertexTree = new BoundingVolumeHierarchyTree<>(
vertices,
v -> AABB.of(v, epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (Edge e : edges) {
if (checkForPointsTouchingEdgeTree(lr, e, vertexTree)) {
return;
}
}
BoundingVolumeHierarchyTree<Edge> edgeTree = new BoundingVolumeHierarchyTree<>(
edges,
e -> AABB.of(e.getFrom(), e.getTo(), epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (int i = 0; i < edges.size(); i++) {
Edge e1 = edges.get(i);
AABB q = AABB.of(e1.getFrom(), e1.getTo(), epsilon);
List<Edge> candidates = edgeTree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
continue;
}
Segment3d s1 = new Segment3d(e1.getFrom(), e1.getTo());
for (Edge e2 : candidates) {
if (e1 == e2) {
continue;
}
if (e1.getConnectionPoint(e2) != null) {
continue;
}
int j = edges.indexOf(e2);
if (j <= i) {
continue;
}
Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo());
DistanceResult dr = s1.getDistanceResult(s2);
if (dr.distance() < epsilon) {
CheckError err = new RingEdgeIntersectionError(lr, e1, e2, dr.point1());
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr);
}
private boolean checkEigenvalues(LinearRing lr, List<Vertex> points, EigenvalueDecomposition ed) { private boolean checkEigenvalues(LinearRing lr, List<Vertex> points, EigenvalueDecomposition ed) {
Matrix3x3d mat = new Matrix3x3d(ed.getV().getArray()); Matrix3x3d mat = new Matrix3x3d(ed.getV().getArray());
List<Vector3d> rotatedVertices = new ArrayList<>(); List<Vector3d> rotatedVertices = new ArrayList<>();
for (Vertex v : points) { for (Vertex v : points) {
rotatedVertices.add(mat.mult(v)); rotatedVertices.add(mat.mult(v));
} }
BoundingBox bbox = BoundingBox.ofPoints(rotatedVertices);
AABB bbox = AABB.ofPoints(rotatedVertices);
double width = bbox.getMaxX() - bbox.getMinX();
double height = bbox.getMaxY() - bbox.getMinY();
double depth = bbox.getMaxZ() - bbox.getMinZ();
int nrOfEigenvaluesBelowTolerance = 0; int nrOfEigenvaluesBelowTolerance = 0;
if (bbox.getWidth() < degeneratedRingTolerance) { if (width < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++; nrOfEigenvaluesBelowTolerance++;
} }
if (bbox.getHeight() < degeneratedRingTolerance) { if (height < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++; nrOfEigenvaluesBelowTolerance++;
} }
if (bbox.getDepth() < degeneratedRingTolerance) { if (depth < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++; nrOfEigenvaluesBelowTolerance++;
} }
...@@ -166,7 +350,7 @@ public class RingSelfIntCheck extends Check { ...@@ -166,7 +350,7 @@ public class RingSelfIntCheck extends Check {
return false; return false;
} }
private boolean checkForPointsTouchingEdge(LinearRing lr, Edge e1) { private boolean checkForPointsTouchingEdgeOld(LinearRing lr, Edge e1) {
Segment3d seg = new Segment3d(e1.getFrom(), e1.getTo()); Segment3d seg = new Segment3d(e1.getFrom(), e1.getTo());
for (Vertex v : lr.getVertices()) { for (Vertex v : lr.getVertices()) {
if (v != e1.getFrom() && v != e1.getTo() && seg.getDistance(v) < epsilon) { if (v != e1.getFrom() && v != e1.getTo() && seg.getDistance(v) < epsilon) {
...@@ -179,6 +363,30 @@ public class RingSelfIntCheck extends Check { ...@@ -179,6 +363,30 @@ public class RingSelfIntCheck extends Check {
return false; return false;
} }
private boolean checkForPointsTouchingEdgeTree(
LinearRing lr,
Edge e1,
BoundingVolumeHierarchyTree<Vertex> vertexTree) {
Segment3d seg = new Segment3d(e1.getFrom(), e1.getTo());
AABB q = AABB.of(e1.getFrom(), e1.getTo(), epsilon);
List<Vertex> candidates = vertexTree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
return false;
}
for (Vertex v : candidates) {
if (v != e1.getFrom() && v != e1.getTo() && seg.getDistance(v) < epsilon) {
CheckError err = new PointTouchesEdgeError(lr, e1, v);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return true;
}
}
return false;
}
private List<Edge> getEdgesForRing(LinearRing lr) { private List<Edge> getEdgesForRing(LinearRing lr) {
List<Edge> edges = new ArrayList<>(); List<Edge> edges = new ArrayList<>();
Geometry geom = lr.getParent().getParent(); Geometry geom = lr.getParent().getParent();
...@@ -218,4 +426,4 @@ public class RingSelfIntCheck extends Check { ...@@ -218,4 +426,4 @@ public class RingSelfIntCheck extends Check {
public CheckId getCheckId() { public CheckId getCheckId() {
return CheckId.C_GE_R_SELF_INTERSECTION; return CheckId.C_GE_R_SELF_INTERSECTION;
} }
} }
\ No newline at end of file
...@@ -129,27 +129,25 @@ public class SelfIntersectionUtil { ...@@ -129,27 +129,25 @@ public class SelfIntersectionUtil {
* *
* New Version with tree query * New Version with tree query
* */ * */
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree( public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g, double delta) { Geometry g,
double delta,
BoundingVolumeHierarchyTree.BuildConfig treeConfig) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta); List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<Integer> indices = new ArrayList<>(tesselatedPolygons.size()); List<Integer> indices = new ArrayList<>(tesselatedPolygons.size());
for (int i = 0; i < tesselatedPolygons.size(); i++) { for (int i = 0; i < tesselatedPolygons.size(); i++) {
indices.add(i); indices.add(i);
} }
// Build BVH on polygons, but compute AABBs from the *original* polygons // Build BVH on polygon indices, while computing AABBs from the original polygons
BoundingVolumeHierarchyTree<Integer> tree = BoundingVolumeHierarchyTree<Integer> tree =
new BoundingVolumeHierarchyTree<>(indices, index -> AABB.of(tesselatedPolygons.get(index).getOriginal())); new BoundingVolumeHierarchyTree<>(
indices,
index -> AABB.of(tesselatedPolygons.get(index).getOriginal()),
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons treeConfig
// IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>(); );
// for (int i = 0; i < tesselatedPolygons.size(); i++) {
// Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon
// ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
// indexByOriginal.put(orig, i);
// }
List<PolygonIntersection> intersections = new ArrayList<>(); List<PolygonIntersection> intersections = new ArrayList<>();
...@@ -159,20 +157,20 @@ public class SelfIntersectionUtil { ...@@ -159,20 +157,20 @@ public class SelfIntersectionUtil {
// Query AABB based on original polygon (consistent with tree construction) // Query AABB based on original polygon (consistent with tree construction)
AABB q = AABB.of(p1.getOriginal()); AABB q = AABB.of(p1.getOriginal());
List<Integer> candidates = tree.getAllOverlappingElements(q); List<Integer> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
continue; // no candidate ergo no overlap continue; // no candidate => no overlap
} }
for (Integer j : candidates) { for (Integer j : candidates) {
if (j <= i) { if (j <= i) {
// avoids double pairwise checks and self-pair // avoids double checks and self-pair
continue; continue;
} }
TesselatedPolygon p2 = tesselatedPolygons.get(j); TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO may be later a further tree in here // TODO maybe later use another tree here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta); GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) { if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2())); intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
...@@ -181,12 +179,22 @@ public class SelfIntersectionUtil { ...@@ -181,12 +179,22 @@ public class SelfIntersectionUtil {
} }
return intersections; return intersections;
} }
// older signature with two arguments provisionally
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g, double delta) {
return calculateSolidSelfIntersectionWithTree(
g,
delta,
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault()
);
}
/* /*
* @ Baris Numanoglu * @ Baris Numanoglu
* *
* New Version with tree query * Version with IdentityHashmap tree query
* */ * */
public static List<PolygonIntersection> calculateSolidSelfIntersection( public static List<PolygonIntersection> calculateSolidSelfIntersection(
Geometry g, double delta, BoundingVolumeHierarchyTree<Polygon> tree) { Geometry g, double delta, BoundingVolumeHierarchyTree<Polygon> tree) {
...@@ -210,7 +218,7 @@ public class SelfIntersectionUtil { ...@@ -210,7 +218,7 @@ public class SelfIntersectionUtil {
ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon
AABB q = AABB.of(p1Orig); AABB q = AABB.of(p1Orig);
List<Polygon> candidates = tree.getAllOverlappingElements(q); List<Polygon> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
continue; // no candidate ergo no overlap continue; // no candidate ergo no overlap
} }
...@@ -258,6 +266,45 @@ public class SelfIntersectionUtil { ...@@ -258,6 +266,45 @@ public class SelfIntersectionUtil {
return intersections; return intersections;
} }
// The very oldest Version Without Tree and internal tesselation
public static List<PolygonIntersection> calculateSolidSelfIntersection0(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
for (Iterator<Triangle3d> iterator = tessPolygon.getTriangles().iterator(); iterator.hasNext();) {
Triangle3d t = iterator.next();
List<Vector3d> vertices = new ArrayList<>(3);
vertices.add(t.getP1());
vertices.add(t.getP2());
vertices.add(t.getP3());
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
Matrix eigenValues = ed.getD();
double[] eigenValuesArray = new double[3];
eigenValuesArray[0] = eigenValues.get(0, 0);
eigenValuesArray[1] = eigenValues.get(1, 1);
eigenValuesArray[2] = eigenValues.get(2, 2);
if (eigenValuesArray[1] < delta) {
iterator.remove();
}
}
tesselatedPolygons.add(tessPolygon);
}
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
for (int j = i + 1; j < tesselatedPolygons.size(); j++) {
TesselatedPolygon p2 = tesselatedPolygons.get(j);
GeometrySelfIntersection intersection = doPolygonsIntersect(p1, p2, delta);
if (intersection != null) {
intersections.add(PolygonIntersection.triangles(intersection.t1(), intersection.t2()));
}
}
}
return intersections;
}
public static GeometrySelfIntersection doesSolidSelfIntersect(Geometry g, double epsilon) { public static GeometrySelfIntersection doesSolidSelfIntersect(Geometry g, double epsilon) {
return selfIntersectionJava(g, epsilon); return selfIntersectionJava(g, epsilon);
} }
......
package de.hft.stuttgart.citydoctor2.checks.geometry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
/**
* Compares RingSelfIntCheck variants on the same input model.
*
* Compared variants:
* - OLD
* - TREE_1_EDGE_BVH
* - TREE_2_EDGE_AND_VERTEX_BVH
*/
public class RingSelfIntCheckVariantComparisonTest {
private static final String TEST_GML =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml";
private static final double EPSILON = 0.001;
@Test
public void oldVsTree1VsTree2_sameErrorCounts()
throws CityGmlParseException, InvalidGmlFileException {
Geometry geometryOld = parseGeometry(TEST_GML);
Geometry geometryTree1 = parseGeometry(TEST_GML);
Geometry geometryTree2 = parseGeometry(TEST_GML);
long start = System.nanoTime();
int oldCount = runCheckAndCountErrors(geometryOld, RingSelfIntCheck.Variant.OLD);
long oldTime = System.nanoTime() - start;
System.out.println("RingSelfIntCheck OLD count=" + oldCount + " time(ns)=" + oldTime);
start = System.nanoTime();
int tree1Count = runCheckAndCountErrors(geometryTree1, RingSelfIntCheck.Variant.TREE_1_EDGE_BVH);
long tree1Time = System.nanoTime() - start;
System.out.println("RingSelfIntCheck TREE_1_EDGE_BVH count=" + tree1Count + " time(ns)=" + tree1Time);
start = System.nanoTime();
int tree2Count = runCheckAndCountErrors(geometryTree2, RingSelfIntCheck.Variant.TREE_2_EDGE_AND_VERTEX_BVH);
long tree2Time = System.nanoTime() - start;
System.out.println("RingSelfIntCheck TREE_2_EDGE_AND_VERTEX_BVH count=" + tree2Count + " time(ns)=" + tree2Time);
assertEquals("OLD vs TREE_1_EDGE_BVH differs", oldCount, tree1Count);
assertEquals("OLD vs TREE_2_EDGE_AND_VERTEX_BVH differs", oldCount, tree2Count);
}
@Test
public void perRingResultsMatch_oldVsTree1VsTree2()
throws CityGmlParseException, InvalidGmlFileException {
Geometry geometryOld = parseGeometry(TEST_GML);
Geometry geometryTree1 = parseGeometry(TEST_GML);
Geometry geometryTree2 = parseGeometry(TEST_GML);
List<LinearRing> oldRings = collectRings(geometryOld);
List<LinearRing> tree1Rings = collectRings(geometryTree1);
List<LinearRing> tree2Rings = collectRings(geometryTree2);
assertEquals("Different number of rings in old/tree1 geometry", oldRings.size(), tree1Rings.size());
assertEquals("Different number of rings in old/tree2 geometry", oldRings.size(), tree2Rings.size());
for (int i = 0; i < oldRings.size(); i++) {
LinearRing oldRing = oldRings.get(i);
LinearRing tree1Ring = tree1Rings.get(i);
LinearRing tree2Ring = tree2Rings.get(i);
RingSelfIntCheck oldCheck = createCheck(RingSelfIntCheck.Variant.OLD);
RingSelfIntCheck tree1Check = createCheck(RingSelfIntCheck.Variant.TREE_1_EDGE_BVH);
RingSelfIntCheck tree2Check = createCheck(RingSelfIntCheck.Variant.TREE_2_EDGE_AND_VERTEX_BVH);
oldCheck.check(oldRing);
tree1Check.check(tree1Ring);
tree2Check.check(tree2Ring);
CheckResult oldResult = oldRing.getCheckResult(oldCheck);
CheckResult tree1Result = tree1Ring.getCheckResult(tree1Check);
CheckResult tree2Result = tree2Ring.getCheckResult(tree2Check);
assertNotNull("OLD result is null for ring index " + i, oldResult);
assertNotNull("TREE_1 result is null for ring index " + i, tree1Result);
assertNotNull("TREE_2 result is null for ring index " + i, tree2Result);
assertEquals("OLD vs TREE_1 status differs for ring index " + i,
oldResult.getResultStatus(), tree1Result.getResultStatus());
assertEquals("OLD vs TREE_2 status differs for ring index " + i,
oldResult.getResultStatus(), tree2Result.getResultStatus());
}
}
private int runCheckAndCountErrors(Geometry geometry, RingSelfIntCheck.Variant variant) {
int count = 0;
for (LinearRing ring : collectRings(geometry)) {
RingSelfIntCheck check = createCheck(variant);
check.check(ring);
CheckResult result = ring.getCheckResult(check);
assertNotNull("CheckResult must not be null", result);
if (result.getResultStatus() == ResultStatus.ERROR) {
count++;
}
}
return count;
}
private RingSelfIntCheck createCheck(RingSelfIntCheck.Variant variant) {
RingSelfIntCheck check = new RingSelfIntCheck(variant);
check.init(Collections.singletonMap("minVertexDistance", String.valueOf(EPSILON)), null);
return check;
}
private List<LinearRing> collectRings(Geometry geometry) {
assertNotNull("geometry must not be null", geometry);
List<LinearRing> rings = new ArrayList<>();
for (Polygon polygon : geometry.getPolygons()) {
if (polygon.getExteriorRing() != null) {
rings.add(polygon.getExteriorRing());
}
rings.addAll(polygon.getInnerRings());
}
return rings;
}
private Geometry parseGeometry(String gmlPath)
throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel model =
CityGmlParser.parseCityGmlFile(gmlPath, config.getParserConfiguration());
Building building = model.getBuildings().findFirst().orElseThrow();
Geometry geometry = building.getGeometry(GeometryType.SOLID, Lod.LOD2);
assertNotNull("Expected SOLID LOD2 geometry in test model: " + gmlPath, geometry);
return geometry;
}
}
\ No newline at end of file
...@@ -133,8 +133,6 @@ public class SolidSelfIntCheckTest { ...@@ -133,8 +133,6 @@ public class SolidSelfIntCheckTest {
public void testKnownFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException { public void testKnownFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException {
testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive2.gml"); testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive2.gml");
} }
} }
...@@ -13,12 +13,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry; ...@@ -13,12 +13,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod; import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
/* /*
* First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version * First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version
...@@ -27,20 +27,20 @@ import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; ...@@ -27,20 +27,20 @@ import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
* */ * */
public class SolidSelfIntersectionOldVsNewTest { public class SolidSelfIntersectionOldVsNewTest {
@Test // @Test
public void testOldVsNewSameResultCount() throws CityGmlParseException, InvalidGmlFileException { // public void testOldVsNewSameResultCount() throws CityGmlParseException, InvalidGmlFileException {
compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh2.gml", 0.001); // compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh2.gml", 0.001);
} // }// dubious Test data: may be TP and not FP?
//
// @Test // @Test
// public void testOldVsNewSameResultCountFalsePositiveExample1() throws CityGmlParseException, InvalidGmlFileException { // public void testOldVsNewSameResultCountFalsePositiveExample1() throws CityGmlParseException, InvalidGmlFileException {
// compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive1.gml", 0.001); // compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive1.gml", 0.001);
// } // }
//
// @Test @Test
// public void testOldVsNewSameResultCountFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException { public void testOldVsNewSameResultCountFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException {
// compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive2.gml", 0.001); compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive2.gml", 0.001);
// } }
private void compareOnFile(String gmlPath, double delta) throws CityGmlParseException, InvalidGmlFileException { private void compareOnFile(String gmlPath, double delta) throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig(); ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
...@@ -56,18 +56,46 @@ public class SolidSelfIntersectionOldVsNewTest { ...@@ -56,18 +56,46 @@ public class SolidSelfIntersectionOldVsNewTest {
assertNotNull(polys); assertNotNull(polys);
assertTrue("Expected at least 2 polygons in: " + gmlPath, polys.size() > 1); assertTrue("Expected at least 2 polygons in: " + gmlPath, polys.size() > 1);
/// Without Tree
long start = System.nanoTime(); long start = System.nanoTime();
List<PolygonIntersection> oldRes = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta); List<PolygonIntersection> oldRes = SelfIntersectionUtil.calculateSolidSelfIntersection0(g, delta);
long dif = System.nanoTime() - start; long dif = System.nanoTime() - start;
System.out.println("Alt: " + dif); System.out.println("Alt: " + dif);
/// With IdentityHashMap
start = System.nanoTime();
BoundingVolumeHierarchyTree<Polygon> polygonTree =
new BoundingVolumeHierarchyTree<>(
g.getPolygons(),
AABB::of,
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault()
);
List<PolygonIntersection> oldTreeRes =
SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta, polygonTree);
dif = System.nanoTime() - start;
System.out.println("Alt + external polygon tree: " + dif);
/// With Polygon Indices
start = System.nanoTime(); start = System.nanoTime();
List<PolygonIntersection> newRes = SelfIntersectionUtil.calculateSolidSelfIntersectionWithTree(g, delta); List<PolygonIntersection> newRes =
SelfIntersectionUtil.calculateSolidSelfIntersectionWithTree(g, delta);
dif = System.nanoTime() - start; dif = System.nanoTime() - start;
System.out.println("Neu: " + dif); System.out.println("Neu: " + dif);
assertFalse(oldRes.isEmpty()); ///
assertFalse(newRes.isEmpty()); System.out.println("oldRes.size() = " + oldRes.size());
System.out.println("oldTreeRes.size() = " + oldTreeRes.size());
System.out.println("newRes.size() = " + newRes.size());
assertFalse("oldRes is empty for: " + gmlPath, oldRes.isEmpty());
assertFalse("oldTreeRes is empty for: " + gmlPath, oldTreeRes.isEmpty());
assertFalse("newRes is empty for: " + gmlPath, newRes.isEmpty());
assertEquals("Old vs oldTree differs for: " + gmlPath,
oldRes.size(), oldTreeRes.size());
assertEquals("Old vs new self-intersection result count differs for: " + gmlPath, oldRes.size(), newRes.size()); assertEquals("Old vs new self-intersection result count differs for: " + gmlPath, oldRes.size(), newRes.size());
} }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment