Commit a08f2df3 authored by Numanoglu's avatar Numanoglu
Browse files

Inline BVH builder into tree builder

parent 2e257ba4
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
final class BoundingVolumeHierarchyBuilder<E> {
private final BoundingVolumeHierarchyTree.Builder<E> config;
private final Function<E, AABB> aabbFunction;
BoundingVolumeHierarchyBuilder(BoundingVolumeHierarchyTree.Builder<E> config) {
this.config = Objects.requireNonNull(config, "config");
this.aabbFunction = Objects.requireNonNull(config.getAabbFunction(), "aabbFunction");
}
Node<E> buildRoot() {
List<E> elements = Objects.requireNonNull(config.getElements(), "elements");
if (elements.isEmpty()) {
return null;
}
List<BvhBuildItem<E>> items = toBuildItems(elements);
SplitStrategy resolvedStrategy = resolveSplitStrategy();
return config.getDegree() == 2
? buildBinaryRecursive(items, 0, resolvedStrategy)
: buildOctonaryRecursive(items, 0, resolvedStrategy);
}
private List<BvhBuildItem<E>> toBuildItems(List<E> elements) {
List<BvhBuildItem<E>> items = new ArrayList<>(elements.size());
for (E e : elements) {
AABB aabb = Objects.requireNonNull(aabbFunction.apply(e), "aabbFunction returned null");
items.add(new BvhBuildItem<>(e, aabb));
}
return items;
}
private SplitStrategy resolveSplitStrategy() {
if (config.getSplitStrategy() != SplitStrategy.AUTO) {
validateStrategyMatchesDegree(config.getDegree(), config.getSplitStrategy());
return config.getSplitStrategy();
}
return config.getDegree() == 2
? SplitStrategy.BINARY_SPATIAL_MEDIAN
: SplitStrategy.OCTONARY_OBJECT_MEAN;
}
private void validateStrategyMatchesDegree(int degree, SplitStrategy strategy) {
boolean binary = strategy == SplitStrategy.BINARY_OBJECT_MEDIAN
|| strategy == SplitStrategy.BINARY_OBJECT_MEAN
|| strategy == SplitStrategy.BINARY_SPATIAL_MEDIAN;
boolean octonary = strategy == SplitStrategy.OCTONARY_OBJECT_MEDIAN
|| strategy == SplitStrategy.OCTONARY_OBJECT_MEAN
|| strategy == SplitStrategy.OCTONARY_SPATIAL_MEDIAN;
if (degree == 2 && !binary) {
throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 2.");
}
if (degree == 8 && !octonary) {
throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 8.");
}
}
private Node<E> buildBinaryRecursive(
List<BvhBuildItem<E>> items,
int depth,
SplitStrategy strategy) {
AABB totalAabb = getAggregateAABBFromItems(items);
if (shouldStop(items, depth, totalAabb)) {
return packTerminalNode(items, totalAabb);
}
int axis = totalAabb.findLongestAxis();
BinarySplitResult<E> split;
switch (strategy) {
case BINARY_OBJECT_MEDIAN:
split = BinarySplitters.objectMedian(items, axis);
break;
case BINARY_OBJECT_MEAN:
split = BinarySplitters.objectMean(items, axis);
break;
case BINARY_SPATIAL_MEDIAN:
split = BinarySplitters.spatialMedian(items, axis, totalAabb);
break;
default:
throw new IllegalStateException("Unexpected binary strategy: " + strategy);
}
if (!split.valid()) {
return packTerminalNode(items, totalAabb);
}
Node<E> node = new Node<>(null, totalAabb);
node.getChildren().add(buildBinaryRecursive(split.left, depth + 1, strategy));
node.getChildren().add(buildBinaryRecursive(split.right, depth + 1, strategy));
return node;
}
private Node<E> buildOctonaryRecursive(
List<BvhBuildItem<E>> items,
int depth,
SplitStrategy strategy) {
AABB totalAabb = getAggregateAABBFromItems(items);
if (shouldStop(items, depth, totalAabb)) {
return packTerminalNode(items, totalAabb);
}
OctonarySplitResult<E> split;
switch (strategy) {
case OCTONARY_OBJECT_MEDIAN:
split = OctonarySplitters.objectMedian(items);
break;
case OCTONARY_OBJECT_MEAN:
split = OctonarySplitters.objectMean(items);
break;
case OCTONARY_SPATIAL_MEDIAN:
split = OctonarySplitters.spatialMedian(items, totalAabb);
break;
default:
throw new IllegalStateException("Unexpected octonary strategy: " + strategy);
}
if (!split.valid()) {
return packTerminalNode(items, totalAabb);
}
Node<E> node = new Node<>(null, totalAabb);
for (int i = 0; i < 8; i++) {
List<BvhBuildItem<E>> bucket = split.buckets[i];
if (bucket.isEmpty()) {
continue;
}
node.getChildren().add(buildOctonaryRecursive(bucket, depth + 1, strategy));
}
return node;
}
private boolean shouldStop(List<BvhBuildItem<E>> items, int depth, AABB totalAabb) {
return items.size() <= config.getMaxLeafSize()
|| depth >= config.getMaxDepth()
// Very flat bounds are kept as one terminal group to avoid repeated
// ineffective spatial splits on effectively lower-dimensional data.
|| totalAabb.isDegenerate(config.getDegenerateTolerance());
}
private Node<E> packTerminalNode(List<BvhBuildItem<E>> items, AABB totalAabb) {
if (items.size() == 1) {
BvhBuildItem<E> item = items.get(0);
return new Node<>(item.element, item.aabb);
}
Node<E> leafGroup = new Node<>(null, totalAabb);
// A terminal group keeps its aggregate AABB for pruning, while the children
// remain the actual element leaves returned by queries.
for (BvhBuildItem<E> item : items) {
leafGroup.getChildren().add(new Node<>(item.element, item.aabb));
}
return leafGroup;
}
private static <E> AABB getAggregateAABBFromItems(List<BvhBuildItem<E>> items) {
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double maxZ = Double.NEGATIVE_INFINITY;
for (BvhBuildItem<E> item : items) {
AABB aabb = item.aabb;
minX = Math.min(minX, aabb.getMinX());
minY = Math.min(minY, aabb.getMinY());
minZ = Math.min(minZ, aabb.getMinZ());
maxX = Math.max(maxX, aabb.getMaxX());
maxY = Math.max(maxY, aabb.getMaxY());
maxZ = Math.max(maxZ, aabb.getMaxZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
static int computeDefaultMaxDepth(int n) {
if (n <= 1) {
return BoundingVolumeHierarchyTree.DEFAULT_MIN_DEPTH;
}
double log2n = Math.log(n) / Math.log(2.0);
int depth = (int) Math.ceil(2.0 * log2n);
return clamp(
depth,
BoundingVolumeHierarchyTree.DEFAULT_MIN_DEPTH,
BoundingVolumeHierarchyTree.DEFAULT_MAX_DEPTH);
}
private static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}
...@@ -16,9 +16,8 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -16,9 +16,8 @@ public class BoundingVolumeHierarchyTree<E> {
static final int DEFAULT_OCTONARY_LEAF_SIZE = 8; static final int DEFAULT_OCTONARY_LEAF_SIZE = 8;
static final double DEFAULT_DEGENERATE_TOL = 1e-12; static final double DEFAULT_DEGENERATE_TOL = 1e-12;
private BoundingVolumeHierarchyTree(Builder<E> builder) { private BoundingVolumeHierarchyTree(Node<E> root) {
Objects.requireNonNull(builder, "builder"); this.root = root;
this.root = new BoundingVolumeHierarchyBuilder<>(builder).buildRoot();
} }
public static <E> Builder<E> builder() { public static <E> Builder<E> builder() {
...@@ -59,7 +58,7 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -59,7 +58,7 @@ public class BoundingVolumeHierarchyTree<E> {
.elements(elements) .elements(elements)
.aabbFunction(aabbFunction) .aabbFunction(aabbFunction)
.maxLeafSize(maxLeafSize) .maxLeafSize(maxLeafSize)
.maxDepth(BoundingVolumeHierarchyBuilder.computeDefaultMaxDepth( .maxDepth(computeDefaultMaxDepth(
elements != null ? elements.size() : 0)) elements != null ? elements.size() : 0))
.build(); .build();
} }
...@@ -75,7 +74,7 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -75,7 +74,7 @@ public class BoundingVolumeHierarchyTree<E> {
.degree(degreeFor(splitStrategy)) .degree(degreeFor(splitStrategy))
.splitStrategy(splitStrategy) .splitStrategy(splitStrategy)
.maxLeafSize(defaultMaxLeafSizeFor(splitStrategy)) .maxLeafSize(defaultMaxLeafSizeFor(splitStrategy))
.maxDepth(BoundingVolumeHierarchyBuilder.computeDefaultMaxDepth( .maxDepth(computeDefaultMaxDepth(
elements != null ? elements.size() : 0)) elements != null ? elements.size() : 0))
.build(); .build();
} }
...@@ -102,6 +101,19 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -102,6 +101,19 @@ public class BoundingVolumeHierarchyTree<E> {
: DEFAULT_OCTONARY_LEAF_SIZE; : DEFAULT_OCTONARY_LEAF_SIZE;
} }
static int computeDefaultMaxDepth(int n) {
if (n <= 1) {
return DEFAULT_MIN_DEPTH;
}
double log2n = Math.log(n) / Math.log(2.0);
int depth = (int) Math.ceil(2.0 * log2n);
return clamp(depth, DEFAULT_MIN_DEPTH, DEFAULT_MAX_DEPTH);
}
private static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
public Node<E> getRoot() { public Node<E> getRoot() {
return root; return root;
} }
...@@ -247,53 +259,197 @@ public class BoundingVolumeHierarchyTree<E> { ...@@ -247,53 +259,197 @@ public class BoundingVolumeHierarchyTree<E> {
throw new IllegalArgumentException("degenerateTolerance must be >= 0."); throw new IllegalArgumentException("degenerateTolerance must be >= 0.");
} }
return new BoundingVolumeHierarchyTree<>(this); return new BoundingVolumeHierarchyTree<>(buildRoot());
} }
static <E> Builder<E> binaryDefault() { private Node<E> buildRoot() {
return new Builder<E>() if (elements.isEmpty()) {
.degree(2) return null;
.splitStrategy(SplitStrategy.AUTO)
.maxLeafSize(DEFAULT_BINARY_LEAF_SIZE)
.maxDepth(DEFAULT_MAX_DEPTH)
.degenerateTolerance(DEFAULT_DEGENERATE_TOL);
} }
static <E> Builder<E> octonaryDefault() { List<BvhBuildItem<E>> items = toBuildItems(elements);
return new Builder<E>() SplitStrategy resolvedStrategy = resolveSplitStrategy();
.degree(8) return degree == 2
.splitStrategy(SplitStrategy.AUTO) ? buildBinaryRecursive(items, 0, resolvedStrategy)
.maxLeafSize(DEFAULT_OCTONARY_LEAF_SIZE) : buildOctonaryRecursive(items, 0, resolvedStrategy);
.maxDepth(DEFAULT_MAX_DEPTH)
.degenerateTolerance(DEFAULT_DEGENERATE_TOL);
} }
int getDegree() { private List<BvhBuildItem<E>> toBuildItems(List<E> sourceElements) {
return degree; List<BvhBuildItem<E>> items = new ArrayList<>(sourceElements.size());
for (E e : sourceElements) {
AABB aabb = Objects.requireNonNull(aabbFunction.apply(e), "aabbFunction returned null");
items.add(new BvhBuildItem<>(e, aabb));
}
return items;
} }
SplitStrategy getSplitStrategy() { private SplitStrategy resolveSplitStrategy() {
if (splitStrategy != SplitStrategy.AUTO) {
validateStrategyMatchesDegree(degree, splitStrategy);
return splitStrategy; return splitStrategy;
} }
return degree == 2
? SplitStrategy.BINARY_SPATIAL_MEDIAN
: SplitStrategy.OCTONARY_OBJECT_MEAN;
}
private void validateStrategyMatchesDegree(int treeDegree, SplitStrategy strategy) {
boolean binary = strategy == SplitStrategy.BINARY_OBJECT_MEDIAN
|| strategy == SplitStrategy.BINARY_OBJECT_MEAN
|| strategy == SplitStrategy.BINARY_SPATIAL_MEDIAN;
boolean octonary = strategy == SplitStrategy.OCTONARY_OBJECT_MEDIAN
|| strategy == SplitStrategy.OCTONARY_OBJECT_MEAN
|| strategy == SplitStrategy.OCTONARY_SPATIAL_MEDIAN;
int getMaxLeafSize() { if (treeDegree == 2 && !binary) {
return maxLeafSize; throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 2.");
}
if (treeDegree == 8 && !octonary) {
throw new IllegalArgumentException("Strategy " + strategy + " does not match degree 8.");
}
}
private Node<E> buildBinaryRecursive(
List<BvhBuildItem<E>> items,
int depth,
SplitStrategy strategy) {
AABB totalAabb = getAggregateAABBFromItems(items);
if (shouldStop(items, depth, totalAabb)) {
return packTerminalNode(items, totalAabb);
} }
int getMaxDepth() { int axis = totalAabb.findLongestAxis();
return maxDepth; BinarySplitResult<E> split;
switch (strategy) {
case BINARY_OBJECT_MEDIAN:
split = BinarySplitters.objectMedian(items, axis);
break;
case BINARY_OBJECT_MEAN:
split = BinarySplitters.objectMean(items, axis);
break;
case BINARY_SPATIAL_MEDIAN:
split = BinarySplitters.spatialMedian(items, axis, totalAabb);
break;
default:
throw new IllegalStateException("Unexpected binary strategy: " + strategy);
} }
double getDegenerateTolerance() { if (!split.valid()) {
return degenerateTolerance; return packTerminalNode(items, totalAabb);
}
Node<E> node = new Node<>(null, totalAabb);
node.getChildren().add(buildBinaryRecursive(split.left, depth + 1, strategy));
node.getChildren().add(buildBinaryRecursive(split.right, depth + 1, strategy));
return node;
}
private Node<E> buildOctonaryRecursive(
List<BvhBuildItem<E>> items,
int depth,
SplitStrategy strategy) {
AABB totalAabb = getAggregateAABBFromItems(items);
if (shouldStop(items, depth, totalAabb)) {
return packTerminalNode(items, totalAabb);
}
OctonarySplitResult<E> split;
switch (strategy) {
case OCTONARY_OBJECT_MEDIAN:
split = OctonarySplitters.objectMedian(items);
break;
case OCTONARY_OBJECT_MEAN:
split = OctonarySplitters.objectMean(items);
break;
case OCTONARY_SPATIAL_MEDIAN:
split = OctonarySplitters.spatialMedian(items, totalAabb);
break;
default:
throw new IllegalStateException("Unexpected octonary strategy: " + strategy);
} }
List<E> getElements() { if (!split.valid()) {
return elements; return packTerminalNode(items, totalAabb);
} }
Function<E, AABB> getAabbFunction() { Node<E> node = new Node<>(null, totalAabb);
return aabbFunction; for (int i = 0; i < 8; i++) {
List<BvhBuildItem<E>> bucket = split.buckets[i];
if (bucket.isEmpty()) {
continue;
} }
node.getChildren().add(buildOctonaryRecursive(bucket, depth + 1, strategy));
}
return node;
}
private boolean shouldStop(List<BvhBuildItem<E>> items, int depth, AABB totalAabb) {
return items.size() <= maxLeafSize
|| depth >= maxDepth
// Very flat bounds are kept as one terminal group to avoid repeated
// ineffective spatial splits on effectively lower-dimensional data.
|| totalAabb.isDegenerate(degenerateTolerance);
}
private Node<E> packTerminalNode(List<BvhBuildItem<E>> items, AABB totalAabb) {
if (items.size() == 1) {
BvhBuildItem<E> item = items.get(0);
return new Node<>(item.element, item.aabb);
}
Node<E> leafGroup = new Node<>(null, totalAabb);
// A terminal group keeps its aggregate AABB for pruning, while the children
// remain the actual element leaves returned by queries.
for (BvhBuildItem<E> item : items) {
leafGroup.getChildren().add(new Node<>(item.element, item.aabb));
}
return leafGroup;
}
private static <E> AABB getAggregateAABBFromItems(List<BvhBuildItem<E>> items) {
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double maxZ = Double.NEGATIVE_INFINITY;
for (BvhBuildItem<E> item : items) {
AABB aabb = item.aabb;
minX = Math.min(minX, aabb.getMinX());
minY = Math.min(minY, aabb.getMinY());
minZ = Math.min(minZ, aabb.getMinZ());
maxX = Math.max(maxX, aabb.getMaxX());
maxY = Math.max(maxY, aabb.getMaxY());
maxZ = Math.max(maxZ, aabb.getMaxZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
static <E> 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);
}
static <E> 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);
}
} }
} }
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