Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
CityDoctor
CityDoctor2
Commits
a08f2df3
Commit
a08f2df3
authored
May 08, 2026
by
Numanoglu
Browse files
Inline BVH builder into tree builder
parent
2e257ba4
Changes
2
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyBuilder.java
deleted
100644 → 0
View file @
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
));
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyTree.java
View file @
a08f2df3
...
@@ -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
=
1
e
-
12
;
static
final
double
DEFAULT_DEGENERATE_TOL
=
1
e
-
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,7 +259,178 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -247,7 +259,178 @@ 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
());
}
private
Node
<
E
>
buildRoot
()
{
if
(
elements
.
isEmpty
())
{
return
null
;
}
List
<
BvhBuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
();
return
degree
==
2
?
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
)
:
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
);
}
private
List
<
BvhBuildItem
<
E
>>
toBuildItems
(
List
<
E
>
sourceElements
)
{
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
;
}
private
SplitStrategy
resolveSplitStrategy
()
{
if
(
splitStrategy
!=
SplitStrategy
.
AUTO
)
{
validateStrategyMatchesDegree
(
degree
,
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
;
if
(
treeDegree
==
2
&&
!
binary
)
{
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
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
()
<=
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
()
{
static
<
E
>
Builder
<
E
>
binaryDefault
()
{
...
@@ -268,32 +451,5 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -268,32 +451,5 @@ public class BoundingVolumeHierarchyTree<E> {
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
);
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
);
}
}
int
getDegree
()
{
return
degree
;
}
SplitStrategy
getSplitStrategy
()
{
return
splitStrategy
;
}
int
getMaxLeafSize
()
{
return
maxLeafSize
;
}
int
getMaxDepth
()
{
return
maxDepth
;
}
double
getDegenerateTolerance
()
{
return
degenerateTolerance
;
}
List
<
E
>
getElements
()
{
return
elements
;
}
Function
<
E
,
AABB
>
getAabbFunction
()
{
return
aabbFunction
;
}
}
}
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment