Commit 2d2487cc authored by Numanoglu's avatar Numanoglu
Browse files

Fix BVH query + update tests for Geometry/Stream API

parent 24f4d6cd
Pipeline #12353 failed with stage
in 2 minutes and 7 seconds
......@@ -17,11 +17,6 @@ public class AABB {
private double minX, minY, minZ;
private double maxX, maxY, maxZ;
// public static AABB of(Geometry geom) {
// return of(geom.getPolygons());
// }
//
//
public static AABB ofPolygons(List<? extends Polygon> polygons) {
List<Vector3d> points = new ArrayList<>();
for (Polygon p : polygons) {
......@@ -37,8 +32,6 @@ public class AABB {
return of(ring);
}
// NOTE: May replaced later: double[] extractBoundsFromPoints(List<? extends
// Point> points)
public static AABB ofPoints(List<? extends Vector3d> points) {
double minX = Double.POSITIVE_INFINITY, minY = Double.POSITIVE_INFINITY, minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY;
......@@ -246,7 +239,7 @@ public class AABB {
/**
* Returns true if two AABBs overlap (exclusive).
* Returns true if two AABBs overlap ( inclusive : touching counts as overlap )
*/
public boolean overlaps(AABB other) {
return !(getMaxX() < other.getMinX() ||
......@@ -270,7 +263,7 @@ public class AABB {
}
/**
* Checks if this and other boxes intersect (inclusive)
* Checks if this and other boxes intersect ( exclusive : touching does not count )
*/
public boolean intersects(AABB other) {
return !(other.maxX <= this.minX || other.minX >= this.maxX || other.maxY <= this.minY || other.minY >= this.maxY
......@@ -303,6 +296,5 @@ public class AABB {
public String toString() {
return "AABB [" + String.format("min: %.2f, %.2f, %.2f max: %.2f, %.2f, %.2f", minX, minY, minZ, maxX, maxY, maxZ) + "]";
}
}
}
......@@ -41,39 +41,5 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
}
return set.size();
// if (ring == null || ring.getVertices() == null) return 0;
// // Use a quantization resolution based on tol (avoid div by zero).
// final double q = (tol > 0) ? tol : 1e-9;
//
// // Hash set of quantized integer triplets "ix|iy|iz"
// Set<Long> buckets = new HashSet<>(ring.getVertices().size() * 2);
// for (Vertex v : ring.getVertices()) {
// long ix = Math.round(v.getX() / q);
// long iy = Math.round(v.getY() / q);
// long iz = Math.round(v.getZ() / q);
// // pack into a 64-bit key (simple mixing; safe if ranges are reasonable)
// long key = mix3(ix, iy, iz);
// buckets.add(key);
// }
// return buckets.size();
}
// Simple 3D integer mix to a 64-bit key (Xorshift-ish) from JavaDoc
// private static long mix3(long x, long y, long z) {
// long h = x * 73856093L ^ y * 19349663L ^ z * 83492791L;
// // final avalanche
// h ^= (h >>> 33);
// h *= 0xff51afd7ed558ccdL;
// h ^= (h >>> 33);
// h *= 0xc4ceb9fe1a85ec53L;
// h ^= (h >>> 33);
// return h;
// }
}
......@@ -84,11 +84,6 @@ public class BoundingVolumeHierarchyTree<E> {
this.root = root;
}
public List<E> getAllIntersectingElements(AABB box) {
// TODO: implement
throw new UnsupportedOperationException();
}
/**
* Recursively builds a balanced AABB tree from the list of elements.
*/
......@@ -116,36 +111,34 @@ public class BoundingVolumeHierarchyTree<E> {
return node;
}
/////////////////////////////////////////////////////////////////
/* Sketch: method for rapid intersection dedection */
/*
*
*
*/
public List<E> findCandidates(AABB query) {
List<E> candidates = new ArrayList<>();
findCandidatesRecursive(root, query, candidates);
return candidates;
}
/*
*
*
*
*/
private void findCandidatesRecursive(Node<E> node, AABB query, List<E> result) {
if (node == null || !node.getAabb().intersects(query)) {
return;
}
if (node.isLeaf()) {
if (aabbFunction.apply(node.getElement()).intersects(query)) {
result.add(node.getElement());
}
} else {
for (Node<E> child : node.getChildren()) {
findCandidatesRecursive(child, query, result);
}
}
}
// Works with overlaps()<--includes touching; not with intersects()
public List<E> getAllIntersectingElements(AABB query) {
List<E> result = new ArrayList<>();
getAllIntersectingElementsRecursive(root, query, result);
return result;
}
private void getAllIntersectingElementsRecursive(Node<E> node, AABB query, List<E> result) {
if (node == null) {
return;
}
// Broad-phase pruning (includes touching points)
if (!node.getAabb().overlaps(query)) {
return;
}
if (node.isLeaf()) {
// Leaf-AABB is all ready in heap
if (node.getAabb().overlaps(query)) {
result.add(node.getElement());
}
return;
}
for (Node<E> child : node.getChildren()) {
getAllIntersectingElementsRecursive(child, query, result);
}
}
}
......@@ -10,11 +10,6 @@ public class Node<E> {
private E element;
public Node(E element, AABB aabb) {
// // Anchor-3DPoint given by means of its coords
// double zeroX = 0;
// double zeroY = 0;
// double zeroZ = 0;
// this.aabb = new AABB(zeroX, zeroY, zeroZ, zeroX, zeroY, zeroZ);
this.aabb = aabb;
this.element = element;
}
......
......@@ -21,18 +21,6 @@ public class AABBTest {
@Test
public void testContainsAABB() {
// System.out.println("=== AABB TEST ===");
ConcretePolygon polygon = new ConcretePolygon();
LinearRing exterior = new LinearRing(LinearRingType.EXTERIOR);
exterior.addVertex(new Vertex(0, 0, 0));
exterior.addVertex(new Vertex(1, 0, 0));
exterior.addVertex(new Vertex(0, 1, 0));
exterior.addVertex(new Vertex(0, 0, 0));
polygon.setExteriorRing(exterior);
// Define points of a tetrahedron
Point3d p1 = new Point3d(0, 0, 0);
Point3d p2 = new Point3d(1, 0, 0);
......@@ -75,8 +63,7 @@ public class AABBTest {
List<Polygon> polys = Arrays.asList(base, side1, side2, side3);
AABB aabb = AABB.ofPolygons(polys);
// System.out.println("\nComputed " + aabb);
/*** 3D-Point-coords with Test Properties ***/
// Inside laying Point
double testInsideX = 0.5;
......@@ -94,18 +81,10 @@ public class AABBTest {
// inside point
Assert.assertTrue(aabb.contains(testInsideX, testInsideY, testInsideZ));
// System.out.println("\nTest point (inside): " + testInsideX + " " + testInsideY + " " + testInsideZ);
// System.out.println("-> contained?: " + aabb.contains(testInsideX, testInsideY, testInsideZ));
// outside point
Assert.assertFalse(aabb.contains(testOutsideX, testOutsideY, testOutsideZ));
// System.out.println("\nTest point (outside): " + testOutsideX + " " + testOutsideY + " " + testOutsideZ);
// System.out.println("-> contained?: " + aabb.contains(testOutsideX, testOutsideY, testOutsideZ));
// edge point
Assert.assertTrue(aabb.contains(testOnEdgeX, testOnEdgeY, testOnEdgeZ));
// System.out.println("\nTest point (on edge): " + testOnEdgeX + " " + testOnEdgeY + " " + testOnEdgeZ);
// System.out.println("-> contained?: " + aabb.contains(testOnEdgeX, testOnEdgeY, testOnEdgeZ));
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
//import de.hft.stuttgart.citydoctor2.checks.aabb.*;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class TestHouseAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("===TestHouseAABB===");
/*
* 3D House of St.Niclas
*
* */
// Haus-Basis: Würfelgröße 2x2x2, Dachhöhe +1
@Test
public void testHouseAabbContainsPoints() {
// 3D House: base 2x2, height 2, roof +1
double baseSize = 2.0;
double height = 2.0;
double roofHeight = 1.0;
// points for Ground floor
// ground points
Point3d b1 = new Point3d(0, 0, 0);
Point3d b2 = new Point3d(baseSize, 0, 0);
Point3d b3 = new Point3d(baseSize, baseSize, 0);
Point3d b4 = new Point3d(0, baseSize, 0);
// points for Roof
// top of walls
Point3d t1 = new Point3d(0, 0, height);
Point3d t2 = new Point3d(baseSize, 0, height);
Point3d t3 = new Point3d(baseSize, baseSize, height);
Point3d t4 = new Point3d(0, baseSize, height);
// Roofpeaks
Point3d roofPeak1 = new Point3d(baseSize/2, -0.2, height + roofHeight);
Point3d roofPeak2 = new Point3d(baseSize/2, baseSize + 0.2, height + roofHeight);
// roof peaks (slightly beyond footprint in Y to create overhang)
Point3d roofPeak1 = new Point3d(baseSize / 2, -0.2, height + roofHeight);
Point3d roofPeak2 = new Point3d(baseSize / 2, baseSize + 0.2, height + roofHeight);
List<ConcretePolygon> house = new ArrayList<>();
// floor (b1,b2,b3,b4)
// floor
house.add(createQuadPolygon(b1, b2, b3, b4));
// Walls
house.add(createQuadPolygon(b1, b2, t2, t1)); // vorne
house.add(createQuadPolygon(b2, b3, t3, t2)); // rechts
house.add(createQuadPolygon(b3, b4, t4, t3)); // hinten
house.add(createQuadPolygon(b4, b1, t1, t4)); // links
// walls
house.add(createQuadPolygon(b1, b2, t2, t1));
house.add(createQuadPolygon(b2, b3, t3, t2));
house.add(createQuadPolygon(b3, b4, t4, t3));
house.add(createQuadPolygon(b4, b1, t1, t4));
// sealing under roof
// ceiling under roof
house.add(createQuadPolygon(t1, t2, t3, t4));
// triangles of roof
// roof triangles
house.add(createTrianglePolygon(t1, t2, roofPeak1));
house.add(createTrianglePolygon(t4, t3, roofPeak2));
// faces of roof
// roof faces
house.add(createQuadPolygon(t2, t3, roofPeak2, roofPeak1));
house.add(createQuadPolygon(t1, t4, roofPeak2, roofPeak1));
AABB houseAABB = AABB.ofPolygons(house);
// houseAABB.print();
// Define test points
double[] insidePoint = {1.0, 1.0, 1.0}; // clearly inside (center of the base cube)
double[] roofPoint = {1.0, 1.0, 2.8}; // inside roof area but near the top
double[] outsidePoint = {3.0, 3.0, 3.0}; // clearly outside
double[] edgePoint = {2.0, 2.0, 2.0}; // exactly on the upper cube corner
// points
double[] insidePoint = {1.0, 1.0, 1.0}; // inside base volume
double[] roofPoint = {1.0, 1.0, 2.8}; // below roof peak (z=3.0), should be inside AABB
double[] outsidePoint= {3.0, 3.0, 3.0}; // outside
double[] edgePoint = {2.0, 2.0, 2.0}; // on top corner of base cube
// Test containment
System.out.println("\nTest point (inside): " + Arrays.toString(insidePoint));
System.out.println("-> contained? " + houseAABB.contains(insidePoint[0], insidePoint[1], insidePoint[2]));
// AABB containment assertions
Assert.assertTrue("insidePoint should be inside AABB",
houseAABB.contains(insidePoint[0], insidePoint[1], insidePoint[2]));
System.out.println("\nTest point (on roof): " + Arrays.toString(roofPoint));
System.out.println("-> contained? " + houseAABB.contains(roofPoint[0], roofPoint[1], roofPoint[2]));
Assert.assertTrue("roofPoint should be inside AABB (AABB includes the roof peak z)",
houseAABB.contains(roofPoint[0], roofPoint[1], roofPoint[2]));
System.out.println("\nTest point (outside): " + Arrays.toString(outsidePoint));
System.out.println("-> contained? " + houseAABB.contains(outsidePoint[0], outsidePoint[1], outsidePoint[2]));
Assert.assertFalse("outsidePoint should be outside AABB",
houseAABB.contains(outsidePoint[0], outsidePoint[1], outsidePoint[2]));
System.out.println("\nTest point (on edge): " + Arrays.toString(edgePoint));
System.out.println("-> contained? " + houseAABB.contains(edgePoint[0], edgePoint[1], edgePoint[2]));
Assert.assertTrue("edgePoint should be inside AABB (contains is inclusive)",
houseAABB.contains(edgePoint[0], edgePoint[1], edgePoint[2]));
// optional: check expected bounds explicitly (helps debugging)
Assert.assertEquals(0.0, houseAABB.getMinX(), 0.0);
Assert.assertEquals(-0.2, houseAABB.getMinY(), 0.0);
Assert.assertEquals(0.0, houseAABB.getMinZ(), 0.0);
Assert.assertEquals(2.0, houseAABB.getMaxX(), 0.0);
Assert.assertEquals(2.2, houseAABB.getMaxY(), 0.0);
Assert.assertEquals(3.0, houseAABB.getMaxZ(), 0.0);
}
/** Creates a triangular polygon from three points */
private static ConcretePolygon createTrianglePolygon(Point3d p1, Point3d p2, Point3d p3) {
// alocate poly
ConcretePolygon poly = new ConcretePolygon();
// alocate ring
ConcretePolygon poly = new ConcretePolygon();
LinearRing ring = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
// vertices of triangle
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Close ring
//
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // close ring
poly.setExteriorRing(ring);
return poly;
}
/** Creates a quadrilateral polygon from four points */
private static ConcretePolygon createQuadPolygon(Point3d p1, Point3d p2, Point3d p3, Point3d p4) {
ConcretePolygon poly = new ConcretePolygon();
ConcretePolygon poly = new ConcretePolygon();
LinearRing ring = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Close ring
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // close ring
poly.setExteriorRing(ring);
return poly;
}
}
}
\ No newline at end of file
......@@ -48,6 +48,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.bht.AABBUtils;
* @author Baris Numanoglu
*
*/
// TODO extend check with tree
public class NestedRingsCheckAABB extends Check {
private static final List<CheckId> dependencies;
......@@ -74,7 +75,7 @@ public class NestedRingsCheckAABB extends Check {
// --- NEW: cache AABBs of inner rings for broad-phase ---
Map<LinearRing, AABB> ringBoxes = new HashMap<>(inner.size());
for (LinearRing r : inner) {
ringBoxes.put(r, AABBUtils.computeAABBFromRing(r));
ringBoxes.put(r, AABB.of(r));
}
for (LinearRing interiorRing : inner) {
......@@ -90,7 +91,7 @@ public class NestedRingsCheckAABB extends Check {
AABB checkBox = ringBoxes.get(checkRing);
// If the inner AABB is not fully inside the other ring's AABB,
// "nested" is impossible -> skip narrow phase.
if (! AABBUtils.containsAabb(interiorBox, checkBox)) {
if (! interiorBox.contains(checkBox)) {
continue;
}
......
......@@ -61,6 +61,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.bht.AABBUtils;
* Axis Aligned Boundin Box Early-Out-Version of Matthias Betz's Check
* @author Baris Numanoglu
*/
// TODO extend check with tree
public class RingSelfIntCheckAABB extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
......@@ -111,7 +113,7 @@ public class RingSelfIntCheckAABB extends Check {
// Precompute padded AABBs for all edges (padding = epsilon)
List<EdgeBox> edgeBoxes = new ArrayList<>(edges.size());
for (Edge e : edges) {
edgeBoxes.add(new EdgeBox(e, AABBUtils.edgeAabb(e.getFrom(), e.getTo(), epsilon)));
edgeBoxes.add(new EdgeBox(e, AABB.of(e.getFrom(), e.getTo(), epsilon)));
}
// Optimized "point touches edge": first filter by padded edge AABB
......@@ -136,7 +138,7 @@ public class RingSelfIntCheckAABB extends Check {
//skip if AABBs don't overlap
EdgeBox eb2 = edgeBoxes.get(j);
if (!AABBUtils.overlaps(eb1.box, eb2.box)) {
if (!eb1.box.overlaps(eb2.box)) {
continue;
}
......@@ -204,7 +206,7 @@ public class RingSelfIntCheckAABB extends Check {
continue;
}
// Broad-phase: reject if vertex is outside padded edge AABB
if (!eb.box.encloses(v.getX(), v.getY(), v.getZ())) {
if (!eb.box.contains(v.getX(), v.getY(), v.getZ())) {
continue;
}
// Narrow-phase: exact segment-point distance
......
......@@ -28,7 +28,6 @@ import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.GeometrySelfIntersection;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
......@@ -38,11 +37,10 @@ import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABBUtils;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
/**
* Check for self intersecting solids
......@@ -79,37 +77,30 @@ public class SolidSelfIntCheckAABB extends Check {
if (g.getType() != GeometryType.SOLID && g.getType() != GeometryType.COMPOSITE_SURFACE) {
return;
}
// --- cheap broad-phase using per-polygon AABBs ---
// --- broad-phase via BVH (AABB tree) ---
List<Polygon> polys = g.getPolygons();
if (polys == null || polys.size() <= 1) {
g.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
return;
}
// Building BVH Tree
// Build AABBs once
AABB[] boxes = new AABB[polys.size()];
for (int i = 0; i < polys.size(); i++) {
ConcretePolygon cp = polys.get(i).getOriginal();
boxes[i] = AABB.of(cp);
}
// If no AABB pair overlaps, self-intersection is impossible
if (!AABB.doAnyBoxesOverlap(boxes)) {
g.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
return;
g.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
return;
}
// --- ---
CheckResult cr;
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g, 0.001, tree);
// Build BVH on polygons, but compute AABBs from the *original* polygons
BoundingVolumeHierarchyTree<Polygon> tree =
new BoundingVolumeHierarchyTree<>(polys, p -> AABB.of(p.getOriginal()));
// TODO: comparison with older version without tree
List<PolygonIntersection> intersections =
SelfIntersectionUtil.calculateSolidSelfIntersection(g, 0.001, tree);
if (intersections.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError e = new SolidSelfIntError(g, intersections);
cr = new CheckResult(this, ResultStatus.ERROR, e);
CheckError e = new SolidSelfIntError(g, intersections);
cr = new CheckResult(this, ResultStatus.ERROR, e);
}
g.addCheckResult(cr);
}
......
......@@ -85,6 +85,7 @@ public class SolidSelfIntCheck extends Check {
return;
}
CheckResult cr;
// TODO: reproduce older version by Mathias of calculateSolidSefInt to compare
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta);
if (intersections.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
......
......@@ -91,49 +91,113 @@ public class SelfIntersectionUtil {
}
public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta, BoundingVolumeHierarchyTree<Polygon> tree) {
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);
}
// Tesselation and filtering degenerated triangles
private static List<TesselatedPolygon> tesselateAndFilter(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
List<Polygon> candidates = tree.findCandidates(AABB.of(p1.getOriginal()));
if (candidates.isEmpty()) {
// TODO:
}
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;
}
for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
for (Iterator<Triangle3d> it = tessPolygon.getTriangles().iterator(); it.hasNext();) {
Triangle3d t = it.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 ev1 = eigenValues.get(1, 1);
if (ev1 < delta) {
it.remove();
}
}
tesselatedPolygons.add(tessPolygon);
}
return tesselatedPolygons;
}
/*
* @ Baris Numanoglu
*
* New Version with tree query
* */
public static List<PolygonIntersection> calculateSolidSelfIntersection(
Geometry g, double delta, BoundingVolumeHierarchyTree<Polygon> tree) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
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<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
// Query AABB based on original polygon (consistent with tree construction)
ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon
AABB q = AABB.of(p1Orig);
List<Polygon> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
continue; // no candidate ergo no overlap
}
for (Polygon cand : candidates) {
ConcretePolygon candOrig = cand.getOriginal();
Integer jObj = indexByOriginal.get(candOrig);
if (jObj == null) {
continue; // candidate not in tesselated-Liste (RE: edge case)
}
int j = jObj.intValue();
if (j <= i) {
continue; // avoids double pairwise checks and self-pair
}
TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO may be later a further tree in here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return intersections;
}
// Older Version without Tree: going to be used for comparison
public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
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 inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return intersections;
}
public static GeometrySelfIntersection doesSolidSelfIntersect(Geometry g, double epsilon) {
return selfIntersectionJava(g, epsilon);
}
......
......@@ -4,6 +4,7 @@ package de.hft.stuttgart.citydoctor2.checks.bht;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import java.util.Arrays;
import java.util.List;
......@@ -94,7 +95,8 @@ public class GeometryChecksWithAABBTest {
ConcretePolygon p = new ConcretePolygon();
p.setExteriorRing(ring);
Geometry g = new Geometry(GeometryType.SOLID, Lod.LOD2);
// TODO Check semantics of Orientation
Geometry g = new Geometry(GeometryType.SOLID, Lod.LOD2, Geometry.Orientation.OUTWARD);
g.addPolygon(p); // ensures parent links exist
g.updateEdgesAndVertices();
......@@ -133,7 +135,8 @@ public class GeometryChecksWithAABBTest {
));
p2.setExteriorRing(r2);
Geometry solid = new Geometry(GeometryType.SOLID, Lod.LOD2);
// TODO Check Orientation<-OUTWARD again
Geometry solid = new Geometry(GeometryType.SOLID, Lod.LOD2, Geometry.Orientation.OUTWARD);
solid.getPolygons().addAll(List.of(p1, p2));
SolidSelfIntCheckAABB check = new SolidSelfIntCheckAABB();
......
package de.hft.stuttgart.citydoctor2.checks.util;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
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.Lod;
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.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
public class SolidSelfIntersectionBVHUtilTest {
@Test
public void testBVHCalculateOnKnownGoodModel() throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel m = CityGmlParser.parseCityGmlFile(
"src/test/resources/SolidSelfIntTest1.gml",
config.getParserConfiguration()
);
Building building = m.getBuildings().findFirst().orElseThrow();
Geometry g = building.getGeometry(GeometryType.SOLID, Lod.LOD2);
assertNotNull("Expected SOLID LOD2 geometry in test model", g);
List<Polygon> polys = g.getPolygons();
assertNotNull(polys);
assertTrue("Expected at least 2 polygons", polys.size() > 1);
BoundingVolumeHierarchyTree<Polygon> tree =
new BoundingVolumeHierarchyTree<>(polys, p -> AABB.of(p.getOriginal()));
double delta = 0.001;
// calls new method with trees
List<PolygonIntersection> intersections =
SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta, tree);
// This file is a good example (no self intersection)
assertTrue("No self-intersections expected for SolidSelfIntTest1.gml",
intersections.isEmpty());
}
}
\ No newline at end of file
package de.hft.stuttgart.citydoctor2.checks.util;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
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.Lod;
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.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
/*
* First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version
*
* @ Baris Numanoglu
* */
public class SolidSelfIntersectionOldVsNewTest {
@Test
public void testOldVsNewSameResultCount() throws CityGmlParseException, InvalidGmlFileException {
compareOnFile("src/test/resources/SolidSelfIntTest1.gml", 0.001);
}
@Test
public void testOldVsNewSameResultCountFalsePositiveExample1() throws CityGmlParseException, InvalidGmlFileException {
compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive1.gml", 0.001);
}
@Test
public void testOldVsNewSameResultCountFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException {
compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive2.gml", 0.001);
}
private void compareOnFile(String gmlPath, double delta) throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel m = CityGmlParser.parseCityGmlFile(gmlPath, config.getParserConfiguration());
Building building = m.getBuildings().findFirst().orElseThrow();
Geometry g = building.getGeometry(GeometryType.SOLID, Lod.LOD2);
assertNotNull("Expected SOLID LOD2 geometry in test model: " + gmlPath, g);
List<Polygon> polys = g.getPolygons();
assertNotNull(polys);
assertTrue("Expected at least 2 polygons in: " + gmlPath, polys.size() > 1);
BoundingVolumeHierarchyTree<Polygon> tree =
new BoundingVolumeHierarchyTree<>(polys, p -> AABB.of(p.getOriginal()));
List<PolygonIntersection> oldRes = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta);
List<PolygonIntersection> newRes = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta, tree);
assertEquals("Old vs new self-intersection result count differs for: " + gmlPath,
oldRes.size(), newRes.size());
}
}
\ No newline at end of file
......@@ -61,6 +61,8 @@ import de.hft.stuttgart.citydoctor2.healer.Healer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.database.CityObjectCache;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
public class HealMissingSolidTest {
......@@ -70,7 +72,13 @@ public class HealMissingSolidTest {
@Test
public void testCreateSolid() throws CityDoctorWriteException, IOException, CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
CityDoctorModel model = new CityDoctorModel(config.getParserConfiguration(), new File("test"));
// edit @Numanoglu
CityDoctorModel model =
new CityDoctorModel(
config.getParserConfiguration(),
new File("test"),
new UnconnectedCache()
);
model.setCityModel(new CityModel());
Building b = new Building();
model.addBuilding(b);
......
......@@ -49,8 +49,10 @@ public class TestCityGmlAABB {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
CityDoctorModel model = de.hft.stuttgart.citydoctor2.healing.TestUtil.loadCityModel(filePathForParser, config);
System.out.println("Model loaded successfully.\n");
List<Building> buildings = model.getBuildings();
// TODO : use stream Building
List<Building> buildings = model.getBuildings().toList()
;
// --- 3) Menu: choose granularity ---
System.out.println("\nChoose granularity:");
......@@ -83,14 +85,13 @@ public class TestCityGmlAABB {
Set<ConcretePolygon> polys = collectAllPolygons(buildings);
BoundingVolumeHierarchyTree<ConcretePolygon> bvh = new BoundingVolumeHierarchyTree<>(
new ArrayList<>(polys),
AABBUtils::getAABB
);
new ArrayList<>(polys),
AABB::of
);
System.out.println("Polygons: " + polys.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
//bvh.getRoot().aabb.print();
}
}
......@@ -104,13 +105,13 @@ public class TestCityGmlAABB {
BoundingVolumeHierarchyTree<LinearRing> bvh = new BoundingVolumeHierarchyTree<>(
new ArrayList<>(rings),
AABBUtils::computeAABBFromRing
AABB::of
);
System.out.println(label + " rings: " + rings.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
// TODO Assertion BLOCK
System.out.println("Root AABB: " + bvh.getRoot().getAabb());
}
}
......@@ -120,13 +121,13 @@ public class TestCityGmlAABB {
BoundingVolumeHierarchyTree<Vertex> bvh = new BoundingVolumeHierarchyTree<>(
new ArrayList<>(verts),
AABBUtils::computeAABBFromVertex
AABB::of
);
System.out.println("Vertices: " + verts.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
//bvh.getRoot().aabb.print();
}
}
......@@ -252,7 +253,7 @@ public class TestCityGmlAABB {
"Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// TODO Remove if redund.
private static String normalizeResourcePath(String path) {
String p = path.replace('\\', '/');
// Quellordner-Präfixe entfernen, falls mit übergeben
......
......@@ -58,7 +58,7 @@ public class TestExample {
// visitor
// filter duplicate polygons out
Set<ConcretePolygon> allBuildingPolygons = new HashSet<>();
for (Building b : model.getBuildings()) {
for (Building b : model.getBuildings().toList()) {
b.accept(new CheckableUtilsVisitor() {
public void check(Polygon poly) {
allBuildingPolygons.add(poly.getOriginal());
......@@ -70,7 +70,7 @@ public class TestExample {
// // for loop
List<Polygon> allBuildingPolygons2 = new ArrayList<>();
for (Building b : model.getBuildings()) {
for (Building b : model.getBuildings().toList()) {
for (Geometry geom : b.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
allBuildingPolygons2.add(p);
......@@ -85,7 +85,7 @@ public class TestExample {
// visitor
// filter duplicate linear rings out
Set<LinearRing> allBuildingLinearRings = new HashSet<>();
for (Building b : model.getBuildings()) {
for (Building b : model.getBuildings().toList()) {
b.accept(new CheckableUtilsVisitor() {
public void check(LinearRing ring) {
allBuildingLinearRings.add(ring);
......@@ -100,7 +100,7 @@ public class TestExample {
// visitor
// filter duplicate exterior rings out
Set<LinearRing> allBuildingExteriorRings = new HashSet<>();
for (Building b : model.getBuildings()) {
for (Building b : model.getBuildings().toList()) {
b.accept(new CheckableUtilsVisitor() {
public void check(LinearRing ring) {
allBuildingExteriorRings.add(ring.getParent().getExteriorRing()); // ACHTUNG getParent
......@@ -110,24 +110,8 @@ public class TestExample {
System.out.println("\nExteriorRings via visitor:" + allBuildingExteriorRings); //new
// // new: ExteriorRings (List)
// // for loop
// List<LinearRing> allBuildingExteriorRings2 = new ArrayList<>();
// for (Building b : model.getBuildings()) {
// for (Geometry geom : b.getGeometries()) {
// for (Polygon p : geom.getPolygons()) {
// allBuildingExteriorRings2.add(p.getOriginal().getExteriorRing());
// }
// }
// }
// System.out.println("\nExteriorRing List:" + allBuildingExteriorRings2); //new
// new: InnerRings
// visitor
// filter duplicate inner rings out
Set<LinearRing> allBuildingInnerRings = new HashSet<>();
for (Building b : model.getBuildings()) {
for (Building b : model.getBuildings().toList()) {
b.accept(new CheckableUtilsVisitor() {
public void check(Polygon poly) {
allBuildingInnerRings.addAll(poly.getOriginal().getInnerRings());// ACHTUNG getOriginal
......
......@@ -87,8 +87,9 @@ public class TestExampleAABB {
Checker c = new Checker(config, model);
c.runChecks();
Building building = model.getBuildings().get(0);
Building building = model.getBuildings()
.findFirst()
.orElseThrow(() -> new IllegalStateException("No buildings found"));
// assertFalse(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
// assertEquals(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
// assertNotNull(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
......
......@@ -535,43 +535,35 @@ public class HealerController {
}
public void injectSolid(boolean useBs, boolean useBi, boolean useLod2, boolean useLod3, boolean useLod4) {
// if (model == null || currentFeature == null) {
// return;
// }
for (Building b : model.getBuildings()) {
// collect polygons
Map<Lod, List<Polygon>> availablePolygons = new EnumMap<>(Lod.class);
Map<Lod, Set<Polygon>> existingPolygons = new EnumMap<>(Lod.class);
collectPolygons(useBs, useBi, availablePolygons, existingPolygons, b);
if (useLod2) {
insertSolidGeometry(availablePolygons, b, existingPolygons, Lod.LOD2);
}
if (useLod3) {
insertSolidGeometry(availablePolygons, b, existingPolygons, Lod.LOD3);
}
if (useLod4) {
insertSolidGeometry(availablePolygons, b, existingPolygons, Lod.LOD4);
}
for (BuildingPart part : b.getBuildingParts()) {
// collect polygons
availablePolygons = new EnumMap<>(Lod.class);
existingPolygons = new EnumMap<>(Lod.class);
collectPolygons(useBs, useBi, availablePolygons, existingPolygons, part);
if (useLod2) {
insertSolidGeometry(availablePolygons, part, existingPolygons, Lod.LOD2);
}
if (useLod3) {
insertSolidGeometry(availablePolygons, part, existingPolygons, Lod.LOD3);
}
if (useLod4) {
insertSolidGeometry(availablePolygons, part, existingPolygons, Lod.LOD4);
}
}
}
if (model == null) {
return;
}
}
model.getBuildings().forEach(b -> {
// --- Building ---
Map<Lod, List<Polygon>> availablePolygonsB = new EnumMap<>(Lod.class);
Map<Lod, Set<Polygon>> existingPolygonsB = new EnumMap<>(Lod.class);
collectPolygons(useBs, useBi, availablePolygonsB, existingPolygonsB, b);
if (useLod2) insertSolidGeometry(availablePolygonsB, b, existingPolygonsB, Lod.LOD2);
if (useLod3) insertSolidGeometry(availablePolygonsB, b, existingPolygonsB, Lod.LOD3);
if (useLod4) insertSolidGeometry(availablePolygonsB, b, existingPolygonsB, Lod.LOD4);
// --- BuildingParts ---
// Falls getBuildingParts() jetzt auch Stream liefert, ist das so korrekt:
b.getBuildingParts().forEach(part -> {
Map<Lod, List<Polygon>> availablePolygonsP = new EnumMap<>(Lod.class);
Map<Lod, Set<Polygon>> existingPolygonsP = new EnumMap<>(Lod.class);
collectPolygons(useBs, useBi, availablePolygonsP, existingPolygonsP, part);
if (useLod2) insertSolidGeometry(availablePolygonsP, part, existingPolygonsP, Lod.LOD2);
if (useLod3) insertSolidGeometry(availablePolygonsP, part, existingPolygonsP, Lod.LOD3);
if (useLod4) insertSolidGeometry(availablePolygonsP, part, existingPolygonsP, Lod.LOD4);
});
});
}
private void collectPolygons(boolean useBs, boolean useBi, Map<Lod, List<Polygon>> availablePolygons,
Map<Lod, Set<Polygon>> existingPolygons, AbstractBuilding b) {
if (useBs) {
......
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