Commit 7eb83cde authored by Matthias Betz's avatar Matthias Betz
Browse files

fixes

parent 0a760af4
Pipeline #12384 failed with stage
in 1 minute and 35 seconds
package de.hft.stuttgart.citydoctor2.datastructure.bht; package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon; import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
/** /**
* Generic Bounding Volume Hierarchy (BVH) for AABBs. * Generic Bounding Volume Hierarchy (BVH) for AABBs.
* *
* Supports: * Supports: - binary BVH (degree 2) - octonary / 8-ary BVH (degree 8)
* - binary BVH (degree 2)
* - octonary / 8-ary BVH (degree 8)
* *
* Split strategies: * Split strategies: - binary: object median, object mean, spatial median -
* - binary: object median, object mean, spatial median * octonary: object median, object mean, spatial median
* - octonary: object median, object mean, spatial median
* *
* Notes: * Notes: - This implementation caches AABBs and centers during build time via
* - This implementation caches AABBs and centers during build time via BuildItem. * BuildItem. - The input list is NOT reordered. - The octonary variant is
* - The input list is NOT reordered. * implemented as an 8-ary BVH with tight child AABBs built from bucket
* - The octonary variant is implemented as an 8-ary BVH with tight child AABBs * contents, not as a strict spatial octree with fixed cell boxes.
* built from bucket contents, not as a strict spatial octree with fixed cell boxes.
* *
* @param <E> element type * @param <E> element type
*/ */
...@@ -42,107 +37,97 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -42,107 +37,97 @@ public class BoundingVolumeHierarchyTree<E> {
public enum SplitStrategy { public enum SplitStrategy {
AUTO, AUTO,
BINARY_OBJECT_MEDIAN, BINARY_OBJECT_MEDIAN, BINARY_OBJECT_MEAN, BINARY_SPATIAL_MEDIAN,
BINARY_OBJECT_MEAN,
BINARY_SPATIAL_MEDIAN,
OCTONARY_OBJECT_MEDIAN, OCTONARY_OBJECT_MEDIAN, OCTONARY_OBJECT_MEAN, OCTONARY_SPATIAL_MEDIAN
OCTONARY_OBJECT_MEAN,
OCTONARY_SPATIAL_MEDIAN
} }
public static final class BuildConfig { public static final class Builder<E> {
private final int degree; private int degree = 2;
private final SplitStrategy splitStrategy; private SplitStrategy splitStrategy = SplitStrategy.AUTO;
private final int maxLeafSize; private int maxLeafSize = DEFAULT_BINARY_LEAF_SIZE;
private final int maxDepth; private int maxDepth = DEFAULT_MAX_DEPTH;
private final double degenerateTolerance; private double degenerateTolerance = DEFAULT_DEGENERATE_TOL;
private List<E> elements;
private Function<E, AABB> aabbFunction;
private BuildConfig(Builder b) { public Builder() {
this.degree = b.degree;
this.splitStrategy = Objects.requireNonNull(b.splitStrategy, "splitStrategy");
this.maxLeafSize = b.maxLeafSize;
this.maxDepth = b.maxDepth;
this.degenerateTolerance = b.degenerateTolerance;
} }
public int getDegree() { public Builder<E> elements(List<E> elements) {
return degree; this.elements = elements;
return this;
} }
public SplitStrategy getSplitStrategy() { public Builder<E> function(Function<E, AABB> aabbFunction) {
return splitStrategy; this.aabbFunction = aabbFunction;
return this;
} }
public int getMaxLeafSize() { public Builder<E> degree(int degree) {
return maxLeafSize; this.degree = degree;
return this;
} }
public int getMaxDepth() { public Builder<E> splitStrategy(SplitStrategy splitStrategy) {
return maxDepth; this.splitStrategy = splitStrategy;
return this;
} }
public double getDegenerateTolerance() { public Builder<E> maxLeafSize(int maxLeafSize) {
return degenerateTolerance; this.maxLeafSize = maxLeafSize;
return this;
} }
public static Builder builder() { public Builder<E> maxDepth(int maxDepth) {
return new Builder(); this.maxDepth = maxDepth;
return this;
} }
public static BuildConfig binaryDefault() { public Builder<E> degenerateTolerance(double degenerateTolerance) {
return builder() this.degenerateTolerance = degenerateTolerance;
.degree(2) return this;
.splitStrategy(SplitStrategy.AUTO)
.maxLeafSize(DEFAULT_BINARY_LEAF_SIZE)
.maxDepth(DEFAULT_MAX_DEPTH)
.degenerateTolerance(DEFAULT_DEGENERATE_TOL)
.build();
} }
public static BuildConfig octonaryDefault() { public Function<E, AABB> getAabbFunction() {
return builder() return aabbFunction;
.degree(8)
.splitStrategy(SplitStrategy.AUTO)
.maxLeafSize(DEFAULT_OCTONARY_LEAF_SIZE)
.maxDepth(DEFAULT_MAX_DEPTH)
.degenerateTolerance(DEFAULT_DEGENERATE_TOL)
.build();
} }
public static final class Builder { public double getDegenerateTolerance() {
private int degree = 2; return degenerateTolerance;
private SplitStrategy splitStrategy = SplitStrategy.AUTO; }
private int maxLeafSize = DEFAULT_BINARY_LEAF_SIZE;
private int maxDepth = DEFAULT_MAX_DEPTH;
private double degenerateTolerance = DEFAULT_DEGENERATE_TOL;
public Builder degree(int degree) { public int getDegree() {
this.degree = degree; return degree;
return this;
} }
public Builder splitStrategy(SplitStrategy splitStrategy) { public List<E> getElements() {
this.splitStrategy = splitStrategy; return elements;
return this;
} }
public Builder maxLeafSize(int maxLeafSize) { public int getMaxDepth() {
this.maxLeafSize = maxLeafSize; return maxDepth;
return this;
} }
public Builder maxDepth(int maxDepth) { public int getMaxLeafSize() {
this.maxDepth = maxDepth; return maxLeafSize;
return this;
} }
public Builder degenerateTolerance(double degenerateTolerance) { public SplitStrategy getSplitStrategy() {
this.degenerateTolerance = degenerateTolerance; return splitStrategy;
return this; }
public Builder<E> binaryDefault() {
return new Builder<E>().degree(2).splitStrategy(SplitStrategy.AUTO).maxLeafSize(DEFAULT_BINARY_LEAF_SIZE)
.maxDepth(DEFAULT_MAX_DEPTH).degenerateTolerance(DEFAULT_DEGENERATE_TOL);
}
public Builder<E> octonaryDefault() {
return new Builder<E>().degree(8).splitStrategy(SplitStrategy.AUTO).maxLeafSize(DEFAULT_OCTONARY_LEAF_SIZE)
.maxDepth(DEFAULT_MAX_DEPTH).degenerateTolerance(DEFAULT_DEGENERATE_TOL);
} }
public BuildConfig build() { public BoundingVolumeHierarchyTree<E> build() {
if (degree != 2 && degree != 8) { if (degree != 2 && degree != 8) {
throw new IllegalArgumentException("Only degree 2 and 8 are supported."); throw new IllegalArgumentException("Only degree 2 and 8 are supported.");
} }
...@@ -155,8 +140,7 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -155,8 +140,7 @@ public class BoundingVolumeHierarchyTree<E> {
if (degenerateTolerance < 0.0) { if (degenerateTolerance < 0.0) {
throw new IllegalArgumentException("degenerateTolerance must be >= 0."); throw new IllegalArgumentException("degenerateTolerance must be >= 0.");
} }
return new BuildConfig(this); return new BoundingVolumeHierarchyTree<E>(this);
}
} }
} }
...@@ -180,84 +164,68 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -180,84 +164,68 @@ public class BoundingVolumeHierarchyTree<E> {
double center(int axis) { double center(int axis) {
switch (axis) { switch (axis) {
case 0: return centerX; case 0:
case 1: return centerY; return centerX;
case 2: return centerZ; case 1:
default: throw new IllegalArgumentException("axis must be 0, 1, or 2"); return centerY;
} case 2:
} return centerZ;
default:
throw new IllegalArgumentException("axis must be 0, 1, or 2");
} }
/**
* Creates an empty BVH.
*/
public BoundingVolumeHierarchyTree(Function<E, AABB> aabbFunction) {
this.root = null;
this.aabbFunction = Objects.requireNonNull(aabbFunction, "aabbFunction");
} }
/**
* Creates a binary BVH with default config (AUTO -> binary spatial median).
*/
public BoundingVolumeHierarchyTree(List<E> elements, Function<E, AABB> aabbFunction) {
this(elements, aabbFunction, BuildConfig.binaryDefault());
} }
/** private BoundingVolumeHierarchyTree(Builder<E> builder) {
* Creates a BVH with a full config. Objects.requireNonNull(builder, "config");
*/ this.aabbFunction = Objects.requireNonNull(builder.aabbFunction);
public BoundingVolumeHierarchyTree(List<E> elements, Function<E, AABB> aabbFunction, BuildConfig config) { List<E> elements = Objects.requireNonNull(builder.getElements());
this.aabbFunction = Objects.requireNonNull(aabbFunction, "aabbFunction");
Objects.requireNonNull(config, "config");
if (elements == null || elements.isEmpty()) { if (elements == null || elements.isEmpty()) {
this.root = null; this.root = null;
return; return;
} }
List<BuildItem<E>> items = toBuildItems(elements); List<BuildItem<E>> items = toBuildItems(elements);
SplitStrategy resolvedStrategy = resolveSplitStrategy(config); SplitStrategy resolvedStrategy = resolveSplitStrategy(builder);
if (config.getDegree() == 2) { if (builder.getDegree() == 2) {
this.root = buildBinaryRecursive(items, 0, resolvedStrategy, config); this.root = buildBinaryRecursive(items, 0, resolvedStrategy, builder);
} else { } else {
this.root = buildOctonaryRecursive(items, 0, resolvedStrategy, config); this.root = buildOctonaryRecursive(items, 0, resolvedStrategy, builder);
} }
} }
/** /**
* Convenience factory: binary BVH with AUTO/defaults. * Convenience factory: binary BVH with AUTO/defaults.
*/ */
public static <E> BoundingVolumeHierarchyTree<E> newBinary( public static <E> BoundingVolumeHierarchyTree<E> newBinary(List<E> elements, Function<E, AABB> aabbFunction) {
List<E> elements, Builder<E> binaryDefault = new Builder<E>().binaryDefault();
Function<E, AABB> aabbFunction) { binaryDefault.elements(elements);
return new BoundingVolumeHierarchyTree<>(elements, aabbFunction, BuildConfig.binaryDefault()); binaryDefault.function(aabbFunction);
return new BoundingVolumeHierarchyTree<>(binaryDefault);
} }
/** /**
* Convenience factory: octonary BVH with AUTO/defaults. * Convenience factory: octonary BVH with AUTO/defaults.
*/ */
public static <E> BoundingVolumeHierarchyTree<E> newOctonary( public static <E> BoundingVolumeHierarchyTree<E> newOctonary(List<E> elements, Function<E, AABB> aabbFunction) {
List<E> elements, Builder<E> octonaryDefault = new Builder<E>().octonaryDefault();
Function<E, AABB> aabbFunction) { octonaryDefault.elements(elements);
return new BoundingVolumeHierarchyTree<>(elements, aabbFunction, BuildConfig.octonaryDefault()); octonaryDefault.function(aabbFunction);
return new BoundingVolumeHierarchyTree<>(octonaryDefault);
} }
/** /**
* Convenience factory: octonary BVH with a custom maxLeafSize. * Convenience factory: octonary BVH with a custom maxLeafSize.
*/ */
public static <E> BoundingVolumeHierarchyTree<E> newOctonary( public static <E> BoundingVolumeHierarchyTree<E> newOctonary(List<E> elements, Function<E, AABB> aabbFunction,
List<E> elements,
Function<E, AABB> aabbFunction,
int maxLeafSize) { int maxLeafSize) {
BuildConfig cfg = BuildConfig.builder() Builder<E> octonaryDefault = new Builder<E>().octonaryDefault();
.degree(8) octonaryDefault.elements(elements);
.splitStrategy(SplitStrategy.AUTO) octonaryDefault.function(aabbFunction);
.maxLeafSize(maxLeafSize) octonaryDefault.maxLeafSize(maxLeafSize);
.maxDepth(computeDefaultMaxDepth(elements != null ? elements.size() : 0)) octonaryDefault.maxDepth(computeDefaultMaxDepth(elements != null ? elements.size() : 0));
.degenerateTolerance(DEFAULT_DEGENERATE_TOL) return new BoundingVolumeHierarchyTree<>(octonaryDefault);
.build();
return new BoundingVolumeHierarchyTree<>(elements, aabbFunction, cfg);
} }
public Node<E> getRoot() { public Node<E> getRoot() {
...@@ -337,25 +305,21 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -337,25 +305,21 @@ public class BoundingVolumeHierarchyTree<E> {
return items; return items;
} }
private SplitStrategy resolveSplitStrategy(BuildConfig config) { private SplitStrategy resolveSplitStrategy(Builder<E> config) {
if (config.getSplitStrategy() != SplitStrategy.AUTO) { if (config.getSplitStrategy() != SplitStrategy.AUTO) {
validateStrategyMatchesDegree(config.getDegree(), config.getSplitStrategy()); validateStrategyMatchesDegree(config.getDegree(), config.getSplitStrategy());
return config.getSplitStrategy(); return config.getSplitStrategy();
} }
return (config.getDegree() == 2) return (config.getDegree() == 2) ? SplitStrategy.BINARY_SPATIAL_MEDIAN : SplitStrategy.OCTONARY_OBJECT_MEAN;
? SplitStrategy.BINARY_SPATIAL_MEDIAN
: SplitStrategy.OCTONARY_OBJECT_MEAN;
} }
private void validateStrategyMatchesDegree(int degree, SplitStrategy strategy) { private void validateStrategyMatchesDegree(int degree, SplitStrategy strategy) {
boolean binary = strategy == SplitStrategy.BINARY_OBJECT_MEDIAN boolean binary = strategy == SplitStrategy.BINARY_OBJECT_MEDIAN || strategy == SplitStrategy.BINARY_OBJECT_MEAN
|| strategy == SplitStrategy.BINARY_OBJECT_MEAN
|| strategy == SplitStrategy.BINARY_SPATIAL_MEDIAN; || strategy == SplitStrategy.BINARY_SPATIAL_MEDIAN;
boolean octonary = strategy == SplitStrategy.OCTONARY_OBJECT_MEDIAN boolean octonary = strategy == SplitStrategy.OCTONARY_OBJECT_MEDIAN
|| strategy == SplitStrategy.OCTONARY_OBJECT_MEAN || strategy == SplitStrategy.OCTONARY_OBJECT_MEAN || strategy == SplitStrategy.OCTONARY_SPATIAL_MEDIAN;
|| strategy == SplitStrategy.OCTONARY_SPATIAL_MEDIAN;
if (degree == 2 && !binary) { if (degree == 2 && !binary) {
throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 2."); throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 2.");
...@@ -365,11 +329,8 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -365,11 +329,8 @@ public class BoundingVolumeHierarchyTree<E> {
} }
} }
private Node<E> buildBinaryRecursive( private Node<E> buildBinaryRecursive(List<BuildItem<E>> items, int depth, SplitStrategy strategy,
List<BuildItem<E>> items, Builder<E> config) {
int depth,
SplitStrategy strategy,
BuildConfig config) {
AABB totalAabb = getAggregateAABBFromItems(items); AABB totalAabb = getAggregateAABBFromItems(items);
...@@ -404,11 +365,8 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -404,11 +365,8 @@ public class BoundingVolumeHierarchyTree<E> {
return node; return node;
} }
private Node<E> buildOctonaryRecursive( private Node<E> buildOctonaryRecursive(List<BuildItem<E>> items, int depth, SplitStrategy strategy,
List<BuildItem<E>> items, Builder<E> config) {
int depth,
SplitStrategy strategy,
BuildConfig config) {
AABB totalAabb = getAggregateAABBFromItems(items); AABB totalAabb = getAggregateAABBFromItems(items);
...@@ -448,9 +406,8 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -448,9 +406,8 @@ public class BoundingVolumeHierarchyTree<E> {
return node; return node;
} }
private boolean shouldStop(List<BuildItem<E>> items, int depth, BuildConfig config, AABB totalAabb) { private boolean shouldStop(List<BuildItem<E>> items, int depth, Builder<E> config, AABB totalAabb) {
return items.size() <= config.getMaxLeafSize() return items.size() <= config.getMaxLeafSize() || depth >= config.getMaxDepth()
|| depth >= config.getMaxDepth()
|| totalAabb.isDegenerate(config.getDegenerateTolerance()); || totalAabb.isDegenerate(config.getDegenerateTolerance());
} }
...@@ -531,10 +488,17 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -531,10 +488,17 @@ public class BoundingVolumeHierarchyTree<E> {
private SplitResult<E> splitBinarySpatialMedian(List<BuildItem<E>> items, int axis, AABB totalAabb) { private SplitResult<E> splitBinarySpatialMedian(List<BuildItem<E>> items, int axis, AABB totalAabb) {
double splitValue; double splitValue;
switch (axis) { switch (axis) {
case 0: splitValue = totalAabb.getCenterX(); break; case 0:
case 1: splitValue = totalAabb.getCenterY(); break; splitValue = totalAabb.getCenterX();
case 2: splitValue = totalAabb.getCenterZ(); break; break;
default: throw new IllegalArgumentException("axis must be 0, 1, or 2"); case 1:
splitValue = totalAabb.getCenterY();
break;
case 2:
splitValue = totalAabb.getCenterZ();
break;
default:
throw new IllegalArgumentException("axis must be 0, 1, or 2");
} }
List<BuildItem<E>> left = new ArrayList<>(); List<BuildItem<E>> left = new ArrayList<>();
...@@ -635,7 +599,8 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -635,7 +599,8 @@ public class BoundingVolumeHierarchyTree<E> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private OctSplit<E> bucketizeOctonary(List<BuildItem<E>> items, double sx, double sy, double sz, boolean fallbackToMeanOnFailure) { private OctSplit<E> bucketizeOctonary(List<BuildItem<E>> items, double sx, double sy, double sz,
boolean fallbackToMeanOnFailure) {
List<BuildItem<E>>[] buckets = new List[8]; List<BuildItem<E>>[] buckets = new List[8];
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
buckets[i] = new ArrayList<>(); buckets[i] = new ArrayList<>();
...@@ -666,9 +631,12 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -666,9 +631,12 @@ public class BoundingVolumeHierarchyTree<E> {
private int octantIndex(double x, double y, double z, double sx, double sy, double sz) { private int octantIndex(double x, double y, double z, double sx, double sy, double sz) {
int idx = 0; int idx = 0;
if (x >= sx) idx |= 1; if (x >= sx)
if (y >= sy) idx |= 2; idx |= 1;
if (z >= sz) idx |= 4; if (y >= sy)
idx |= 2;
if (z >= sz)
idx |= 4;
return idx; return idx;
} }
...@@ -683,7 +651,6 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -683,7 +651,6 @@ public class BoundingVolumeHierarchyTree<E> {
return new OctSplit<>(buckets); return new OctSplit<>(buckets);
} }
@SuppressWarnings("unchecked")
static <E> OctSplit<E> invalid() { static <E> OctSplit<E> invalid() {
return new OctSplit<>(null); return new OctSplit<>(null);
} }
......
...@@ -89,7 +89,10 @@ public class SolidSelfIntCheckAABB extends Check { ...@@ -89,7 +89,10 @@ public class SolidSelfIntCheckAABB extends Check {
// Build BVH on polygons, but compute AABBs from the *original* polygons // Build BVH on polygons, but compute AABBs from the *original* polygons
BoundingVolumeHierarchyTree<Polygon> tree = BoundingVolumeHierarchyTree<Polygon> tree =
new BoundingVolumeHierarchyTree<>(polys, p -> AABB.of(p.getOriginal())); new BoundingVolumeHierarchyTree.Builder<Polygon>()
.elements(polys)
.function(p -> AABB.of(p.getOriginal()))
.build();
// TODO: comparison with older version without tree // TODO: comparison with older version without tree
List<PolygonIntersection> intersections = List<PolygonIntersection> intersections =
SelfIntersectionUtil.calculateSolidSelfIntersection(g, 0.001, tree); SelfIntersectionUtil.calculateSolidSelfIntersection(g, 0.001, tree);
......
...@@ -84,7 +84,7 @@ public class NestedRingsCheck extends Check { ...@@ -84,7 +84,7 @@ public class NestedRingsCheck extends Check {
@Override @Override
public void check(Polygon p) { public void check(Polygon p) {
if (useAabbFilter) { if (p.getInnerRings().size() > 3) {
checkWithBoundingBoxFilter(p); checkWithBoundingBoxFilter(p);
} else { } else {
checkOriginal(p); checkOriginal(p);
......
...@@ -204,10 +204,8 @@ public class RingSelfIntCheck extends Check { ...@@ -204,10 +204,8 @@ public class RingSelfIntCheck extends Check {
} }
} }
BoundingVolumeHierarchyTree<Edge> edgeTree = new BoundingVolumeHierarchyTree<>( BoundingVolumeHierarchyTree<Edge> edgeTree = BoundingVolumeHierarchyTree.newBinary(
edges, edges, e -> AABB.of(e.getFrom(), e.getTo(), epsilon));
e -> AABB.of(e.getFrom(), e.getTo(), epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (int i = 0; i < edges.size(); i++) { for (int i = 0; i < edges.size(); i++) {
Edge e1 = edges.get(i); Edge e1 = edges.get(i);
...@@ -264,10 +262,8 @@ public class RingSelfIntCheck extends Check { ...@@ -264,10 +262,8 @@ public class RingSelfIntCheck extends Check {
List<Edge> edges = getEdgesForRing(lr); List<Edge> edges = getEdgesForRing(lr);
BoundingVolumeHierarchyTree<Vertex> vertexTree = new BoundingVolumeHierarchyTree<>( BoundingVolumeHierarchyTree<Vertex> vertexTree =
vertices, BoundingVolumeHierarchyTree.newBinary(vertices, v -> AABB.of(v, epsilon));
v -> AABB.of(v, epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (Edge e : edges) { for (Edge e : edges) {
if (checkForPointsTouchingEdgeTree(lr, e, vertexTree)) { if (checkForPointsTouchingEdgeTree(lr, e, vertexTree)) {
...@@ -275,10 +271,8 @@ public class RingSelfIntCheck extends Check { ...@@ -275,10 +271,8 @@ public class RingSelfIntCheck extends Check {
} }
} }
BoundingVolumeHierarchyTree<Edge> edgeTree = new BoundingVolumeHierarchyTree<>( BoundingVolumeHierarchyTree<Edge> edgeTree = BoundingVolumeHierarchyTree.newBinary(
edges, edges, e -> AABB.of(e.getFrom(), e.getTo(), epsilon));
e -> AABB.of(e.getFrom(), e.getTo(), epsilon),
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault());
for (int i = 0; i < edges.size(); i++) { for (int i = 0; i < edges.size(); i++) {
Edge e1 = edges.get(i); Edge e1 = edges.get(i);
......
...@@ -133,7 +133,7 @@ public class SelfIntersectionUtil { ...@@ -133,7 +133,7 @@ public class SelfIntersectionUtil {
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree( public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g, Geometry g,
double delta, double delta,
BoundingVolumeHierarchyTree.BuildConfig treeConfig) { BoundingVolumeHierarchyTree.Builder<Integer> 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());
...@@ -141,13 +141,10 @@ public class SelfIntersectionUtil { ...@@ -141,13 +141,10 @@ public class SelfIntersectionUtil {
indices.add(i); indices.add(i);
} }
treeConfig.elements(indices).function(index -> AABB.of(tesselatedPolygons.get(index).getOriginal()));
// Build BVH on polygon indices, while computing AABBs from the original polygons // Build BVH on polygon indices, while computing AABBs from the original polygons
BoundingVolumeHierarchyTree<Integer> tree = BoundingVolumeHierarchyTree<Integer> tree = treeConfig.build();
new BoundingVolumeHierarchyTree<>(
indices,
index -> AABB.of(tesselatedPolygons.get(index).getOriginal()),
treeConfig
);
List<PolygonIntersection> intersections = new ArrayList<>(); List<PolygonIntersection> intersections = new ArrayList<>();
...@@ -187,7 +184,7 @@ public class SelfIntersectionUtil { ...@@ -187,7 +184,7 @@ public class SelfIntersectionUtil {
return calculateSolidSelfIntersectionWithTree( return calculateSolidSelfIntersectionWithTree(
g, g,
delta, delta,
BoundingVolumeHierarchyTree.BuildConfig.binaryDefault() new BoundingVolumeHierarchyTree.Builder<Integer>().binaryDefault()
); );
} }
......
package de.hft.stuttgart.citydoctor2.checks.util; package de.hft.stuttgart.citydoctor2.checks.util;
import static org.junit.Assert.*; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.List; import java.util.List;
import org.junit.Test; import org.citygml4j.core.model.CityGMLVersion;
import org.citygml4j.core.model.core.CityModel;
import org.junit.jupiter.api.Test;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration; import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
import de.hft.stuttgart.citydoctor2.datastructure.Building; import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
...@@ -15,14 +20,31 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod; ...@@ -15,14 +20,31 @@ 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.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
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.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
public class SolidSelfIntersectionBVHUtilTest { public class SolidSelfIntersectionBVHUtilTest {
@Test
public void testWriteModel() throws CityDoctorWriteException {
Building b = new Building();
b.addGeometry(GeometryTestUtils.createGoodGeometry());
b.setGmlObject(new org.citygml4j.core.model.building.Building());
UnconnectedCache unconnectedCache = new UnconnectedCache();
CityDoctorModel model = new CityDoctorModel(
new ParserConfiguration(8, false), new File("test.gml"), unconnectedCache);
model.setParsedCityGMLVersion(CityGMLVersion.v2_0);
model.setCityModel(new CityModel());
model.addBuilding(b);
model.saveAs("test.gml", false);
}
@Test @Test
public void testBVHCalculateOnKnownGoodModel() throws CityGmlParseException, InvalidGmlFileException { public void testBVHCalculateOnKnownGoodModel() throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig(); ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
...@@ -42,7 +64,7 @@ public class SolidSelfIntersectionBVHUtilTest { ...@@ -42,7 +64,7 @@ public class SolidSelfIntersectionBVHUtilTest {
assertTrue("Expected at least 2 polygons", polys.size() > 1); assertTrue("Expected at least 2 polygons", polys.size() > 1);
BoundingVolumeHierarchyTree<Polygon> tree = BoundingVolumeHierarchyTree<Polygon> tree =
new BoundingVolumeHierarchyTree<>(polys, p -> AABB.of(p.getOriginal())); BoundingVolumeHierarchyTree.newBinary(polys, p -> AABB.of(p.getOriginal()));
double delta = 0.001; double delta = 0.001;
......
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