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
7eb83cde
Commit
7eb83cde
authored
Apr 16, 2026
by
Matthias Betz
Browse files
fixes
parent
0a760af4
Pipeline
#12384
failed with stage
in 1 minute and 35 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyTree.java
View file @
7eb83cde
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
*/
*/
public
class
BoundingVolumeHierarchyTree
<
E
>
{
public
class
BoundingVolumeHierarchyTree
<
E
>
{
private
Node
<
E
>
root
;
private
Node
<
E
>
root
;
private
final
Function
<
E
,
AABB
>
aabbFunction
;
private
final
Function
<
E
,
AABB
>
aabbFunction
;
private
static
final
int
DEFAULT_MIN_DEPTH
=
4
;
private
static
final
int
DEFAULT_MIN_DEPTH
=
4
;
private
static
final
int
DEFAULT_MAX_DEPTH
=
32
;
private
static
final
int
DEFAULT_MAX_DEPTH
=
32
;
private
static
final
int
DEFAULT_BINARY_LEAF_SIZE
=
1
;
private
static
final
int
DEFAULT_BINARY_LEAF_SIZE
=
1
;
private
static
final
int
DEFAULT_OCTONARY_LEAF_SIZE
=
8
;
private
static
final
int
DEFAULT_OCTONARY_LEAF_SIZE
=
8
;
private
static
final
double
DEFAULT_DEGENERATE_TOL
=
1
e
-
12
;
private
static
final
double
DEFAULT_DEGENERATE_TOL
=
1
e
-
12
;
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_MEAN
,
OCTONARY_SPATIAL_MEDIAN
}
OCTONARY_OBJECT_MEDIAN
,
OCTONARY_OBJECT_MEAN
,
public
static
final
class
Builder
<
E
>
{
OCTONARY_SPATIAL_MEDIAN
private
int
degree
=
2
;
}
private
SplitStrategy
splitStrategy
=
SplitStrategy
.
AUTO
;
private
int
maxLeafSize
=
DEFAULT_BINARY_LEAF_SIZE
;
public
static
final
class
BuildConfig
{
private
int
maxDepth
=
DEFAULT_MAX_DEPTH
;
private
final
int
degree
;
private
double
degenerateTolerance
=
DEFAULT_DEGENERATE_TOL
;
private
final
SplitStrategy
splitStrategy
;
private
List
<
E
>
elements
;
private
final
int
maxLeafSize
;
private
Function
<
E
,
AABB
>
aabbFunction
;
private
final
int
maxDepth
;
private
final
double
degenerateTolerance
;
public
Builder
()
{
}
private
BuildConfig
(
Builder
b
)
{
this
.
degree
=
b
.
degree
;
public
Builder
<
E
>
elements
(
List
<
E
>
elements
)
{
this
.
splitStrategy
=
Objects
.
requireNonNull
(
b
.
splitStrategy
,
"splitStrategy"
);
this
.
elements
=
elements
;
this
.
maxLeafSize
=
b
.
maxLeafSize
;
return
this
;
this
.
maxDepth
=
b
.
maxDepth
;
}
this
.
degenerateTolerance
=
b
.
degenerateTolerance
;
}
public
Builder
<
E
>
function
(
Function
<
E
,
AABB
>
aabbFunction
)
{
this
.
aabbFunction
=
aabbFunction
;
public
int
getDegree
()
{
return
this
;
return
degree
;
}
}
public
Builder
<
E
>
degree
(
int
degree
)
{
public
SplitStrategy
getSplitStrategy
()
{
this
.
degree
=
degree
;
return
splitStrategy
;
return
this
;
}
}
public
int
getMaxLeafSize
()
{
public
Builder
<
E
>
splitStrategy
(
SplitStrategy
splitStrategy
)
{
return
maxLeafSize
;
this
.
splitStrategy
=
splitStrategy
;
}
return
this
;
}
public
int
getMaxDepth
()
{
return
maxDepth
;
public
Builder
<
E
>
maxLeafSize
(
int
maxLeafSize
)
{
}
this
.
maxLeafSize
=
maxLeafSize
;
return
this
;
public
double
getDegenerateTolerance
()
{
}
return
degenerateTolerance
;
}
public
Builder
<
E
>
maxDepth
(
int
maxDepth
)
{
this
.
maxDepth
=
maxDepth
;
public
static
Builder
builder
()
{
return
this
;
return
new
Builder
();
}
}
public
Builder
<
E
>
degenerateTolerance
(
double
degenerateTolerance
)
{
public
static
BuildConfig
binaryDefault
()
{
this
.
degenerateTolerance
=
degenerateTolerance
;
return
builder
()
return
this
;
.
degree
(
2
)
}
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
DEFAULT_BINARY_LEAF_SIZE
)
public
Function
<
E
,
AABB
>
getAabbFunction
()
{
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
return
aabbFunction
;
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
}
.
build
();
}
public
double
getDegenerateTolerance
()
{
return
degenerateTolerance
;
public
static
BuildConfig
octonaryDefault
()
{
}
return
builder
()
.
degree
(
8
)
public
int
getDegree
()
{
.
splitStrategy
(
SplitStrategy
.
AUTO
)
return
degree
;
.
maxLeafSize
(
DEFAULT_OCTONARY_LEAF_SIZE
)
}
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
public
List
<
E
>
getElements
()
{
.
build
();
return
elements
;
}
}
public
static
final
class
Builder
{
public
int
getMaxDepth
()
{
private
int
degree
=
2
;
return
maxDepth
;
private
SplitStrategy
splitStrategy
=
SplitStrategy
.
AUTO
;
}
private
int
maxLeafSize
=
DEFAULT_BINARY_LEAF_SIZE
;
private
int
maxDepth
=
DEFAULT_MAX_DEPTH
;
public
int
getMaxLeafSize
()
{
private
double
degenerateTolerance
=
DEFAULT_DEGENERATE_TOL
;
return
maxLeafSize
;
}
public
Builder
degree
(
int
degree
)
{
this
.
degree
=
degree
;
public
SplitStrategy
getSplitStrategy
()
{
return
this
;
return
splitStrategy
;
}
}
public
Builder
splitStrategy
(
SplitStrategy
splitStrategy
)
{
public
Builder
<
E
>
binaryDefault
()
{
this
.
splitStrategy
=
splitStrategy
;
return
new
Builder
<
E
>().
degree
(
2
).
splitStrategy
(
SplitStrategy
.
AUTO
).
maxLeafSize
(
DEFAULT_BINARY_LEAF_SIZE
)
return
this
;
.
maxDepth
(
DEFAULT_MAX_DEPTH
).
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
);
}
}
public
Builder
maxLeafSize
(
int
maxLeafSize
)
{
public
Builder
<
E
>
octonaryDefault
()
{
this
.
maxLeafSize
=
maxLeafSize
;
return
new
Builder
<
E
>().
degree
(
8
).
splitStrategy
(
SplitStrategy
.
AUTO
).
maxLeafSize
(
DEFAULT_OCTONARY_LEAF_SIZE
)
return
this
;
.
maxDepth
(
DEFAULT_MAX_DEPTH
).
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
);
}
}
public
Builder
maxDepth
(
int
maxDepth
)
{
public
BoundingVolumeHierarchyTree
<
E
>
build
()
{
this
.
maxDepth
=
maxDepth
;
if
(
degree
!=
2
&&
degree
!=
8
)
{
return
this
;
throw
new
IllegalArgumentException
(
"Only degree 2 and 8 are supported."
);
}
}
if
(
maxLeafSize
<
1
)
{
public
Builder
degenerateTolerance
(
double
degenerateTolerance
)
{
throw
new
IllegalArgumentException
(
"maxLeafSize must be >= 1."
);
this
.
degenerateTolerance
=
degenerateTolerance
;
}
return
this
;
if
(
maxDepth
<
1
)
{
}
throw
new
IllegalArgumentException
(
"maxDepth must be >= 1."
);
}
public
BuildConfig
build
()
{
if
(
degenerateTolerance
<
0.0
)
{
if
(
degree
!=
2
&&
degree
!=
8
)
{
throw
new
IllegalArgumentException
(
"degenerateTolerance must be >= 0."
);
throw
new
IllegalArgumentException
(
"Only degree 2 and 8 are supported."
);
}
}
return
new
BoundingVolumeHierarchyTree
<
E
>(
this
);
if
(
maxLeafSize
<
1
)
{
}
throw
new
IllegalArgumentException
(
"maxLeafSize must be >= 1."
);
}
}
if
(
maxDepth
<
1
)
{
/**
throw
new
IllegalArgumentException
(
"maxDepth must be >= 1."
);
* Internal build-time cache.
}
*/
if
(
degenerateTolerance
<
0.0
)
{
private
static
final
class
BuildItem
<
E
>
{
throw
new
IllegalArgumentException
(
"degenerateTolerance must be >= 0."
);
final
E
element
;
}
final
AABB
aabb
;
return
new
BuildConfig
(
this
);
final
double
centerX
;
}
final
double
centerY
;
}
final
double
centerZ
;
}
BuildItem
(
E
element
,
AABB
aabb
)
{
/**
this
.
element
=
element
;
* Internal build-time cache.
this
.
aabb
=
aabb
;
*/
this
.
centerX
=
0.5
*
(
aabb
.
getMinX
()
+
aabb
.
getMaxX
());
private
static
final
class
BuildItem
<
E
>
{
this
.
centerY
=
0.5
*
(
aabb
.
getMinY
()
+
aabb
.
getMaxY
());
final
E
element
;
this
.
centerZ
=
0.5
*
(
aabb
.
getMinZ
()
+
aabb
.
getMaxZ
());
final
AABB
aabb
;
}
final
double
centerX
;
final
double
centerY
;
double
center
(
int
axis
)
{
final
double
centerZ
;
switch
(
axis
)
{
case
0
:
BuildItem
(
E
element
,
AABB
aabb
)
{
return
centerX
;
this
.
element
=
element
;
case
1
:
this
.
aabb
=
aabb
;
return
centerY
;
this
.
centerX
=
0.5
*
(
aabb
.
getMinX
()
+
aabb
.
getMaxX
());
case
2
:
this
.
centerY
=
0.5
*
(
aabb
.
getMinY
()
+
aabb
.
getMaxY
());
return
centerZ
;
this
.
centerZ
=
0.5
*
(
aabb
.
getMinZ
()
+
aabb
.
getMaxZ
());
default
:
}
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
}
double
center
(
int
axis
)
{
}
switch
(
axis
)
{
}
case
0
:
return
centerX
;
case
1
:
return
centerY
;
private
BoundingVolumeHierarchyTree
(
Builder
<
E
>
builder
)
{
case
2
:
return
centerZ
;
Objects
.
requireNonNull
(
builder
,
"config"
);
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
this
.
aabbFunction
=
Objects
.
requireNonNull
(
builder
.
aabbFunction
);
}
List
<
E
>
elements
=
Objects
.
requireNonNull
(
builder
.
getElements
());
}
if
(
elements
==
null
||
elements
.
isEmpty
())
{
}
this
.
root
=
null
;
return
;
/**
}
* Creates an empty BVH.
*/
List
<
BuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
public
BoundingVolumeHierarchyTree
(
Function
<
E
,
AABB
>
aabbFunction
)
{
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
(
builder
);
this
.
root
=
null
;
this
.
aabbFunction
=
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
if
(
builder
.
getDegree
()
==
2
)
{
}
this
.
root
=
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
,
builder
);
}
else
{
/**
this
.
root
=
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
,
builder
);
* 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
());
/**
}
* Convenience factory: binary BVH with AUTO/defaults.
*/
/**
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newBinary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
* Creates a BVH with a full config.
Builder
<
E
>
binaryDefault
=
new
Builder
<
E
>().
binaryDefault
();
*/
binaryDefault
.
elements
(
elements
);
public
BoundingVolumeHierarchyTree
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
,
BuildConfig
config
)
{
binaryDefault
.
function
(
aabbFunction
);
this
.
aabbFunction
=
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
return
new
BoundingVolumeHierarchyTree
<>(
binaryDefault
);
Objects
.
requireNonNull
(
config
,
"config"
);
}
if
(
elements
==
null
||
elements
.
isEmpty
())
{
/**
this
.
root
=
null
;
* Convenience factory: octonary BVH with AUTO/defaults.
return
;
*/
}
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
Builder
<
E
>
octonaryDefault
=
new
Builder
<
E
>().
octonaryDefault
();
List
<
BuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
octonaryDefault
.
elements
(
elements
);
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
(
config
);
octonaryDefault
.
function
(
aabbFunction
);
return
new
BoundingVolumeHierarchyTree
<>(
octonaryDefault
);
if
(
config
.
getDegree
()
==
2
)
{
}
this
.
root
=
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
,
config
);
}
else
{
/**
this
.
root
=
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
,
config
);
* Convenience factory: octonary BVH with a custom maxLeafSize.
}
*/
}
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
,
int
maxLeafSize
)
{
/**
Builder
<
E
>
octonaryDefault
=
new
Builder
<
E
>().
octonaryDefault
();
* Convenience factory: binary BVH with AUTO/defaults.
octonaryDefault
.
elements
(
elements
);
*/
octonaryDefault
.
function
(
aabbFunction
);
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newBinary
(
octonaryDefault
.
maxLeafSize
(
maxLeafSize
);
List
<
E
>
elements
,
octonaryDefault
.
maxDepth
(
computeDefaultMaxDepth
(
elements
!=
null
?
elements
.
size
()
:
0
));
Function
<
E
,
AABB
>
aabbFunction
)
{
return
new
BoundingVolumeHierarchyTree
<>(
octonaryDefault
);
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
BuildConfig
.
binaryDefault
());
}
}
public
Node
<
E
>
getRoot
()
{
/**
return
root
;
* Convenience factory: octonary BVH with AUTO/defaults.
}
*/
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
public
void
setRoot
(
Node
<
E
>
root
)
{
List
<
E
>
elements
,
this
.
root
=
root
;
Function
<
E
,
AABB
>
aabbFunction
)
{
}
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
BuildConfig
.
octonaryDefault
());
}
/**
* NOTE: generic version already exists.
/**
*/
* Convenience factory: octonary BVH with a custom maxLeafSize.
public
static
AABB
getAggregateAABB
(
List
<
ConcretePolygon
>
polygons
)
{
*/
if
(
polygons
.
size
()
==
1
)
{
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
return
AABB
.
of
(
polygons
.
get
(
0
));
List
<
E
>
elements
,
}
Function
<
E
,
AABB
>
aabbFunction
,
return
getAggregateAABB
(
polygons
,
AABB:
:
of
);
int
maxLeafSize
)
{
}
BuildConfig
cfg
=
BuildConfig
.
builder
()
.
degree
(
8
)
public
static
<
E
>
AABB
getAggregateAABB
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunc
)
{
.
splitStrategy
(
SplitStrategy
.
AUTO
)
double
minX
=
Double
.
POSITIVE_INFINITY
;
.
maxLeafSize
(
maxLeafSize
)
double
minY
=
Double
.
POSITIVE_INFINITY
;
.
maxDepth
(
computeDefaultMaxDepth
(
elements
!=
null
?
elements
.
size
()
:
0
))
double
minZ
=
Double
.
POSITIVE_INFINITY
;
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
.
build
();
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
cfg
);
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
}
for
(
E
e
:
elements
)
{
public
Node
<
E
>
getRoot
()
{
AABB
aabb
=
aabbFunc
.
apply
(
e
);
return
root
;
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
}
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
public
void
setRoot
(
Node
<
E
>
root
)
{
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
this
.
root
=
root
;
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
}
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
}
/**
* NOTE: generic version already exists.
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
*/
}
public
static
AABB
getAggregateAABB
(
List
<
ConcretePolygon
>
polygons
)
{
if
(
polygons
.
size
()
==
1
)
{
public
List
<
E
>
getAllIntersectingElements
(
AABB
query
)
{
return
AABB
.
of
(
polygons
.
get
(
0
));
List
<
E
>
result
=
new
ArrayList
<>();
}
getAllIntersectingElementsRecursive
(
root
,
query
,
result
);
return
getAggregateAABB
(
polygons
,
AABB:
:
of
);
return
result
;
}
}
public
static
<
E
>
AABB
getAggregateAABB
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunc
)
{
private
void
getAllIntersectingElementsRecursive
(
Node
<
E
>
node
,
AABB
query
,
List
<
E
>
result
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
;
if
(
node
==
null
)
{
double
minY
=
Double
.
POSITIVE_INFINITY
;
return
;
double
minZ
=
Double
.
POSITIVE_INFINITY
;
}
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
if
(!
node
.
getAabb
().
overlaps
(
query
))
{
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
return
;
}
for
(
E
e
:
elements
)
{
AABB
aabb
=
aabbFunc
.
apply
(
e
);
if
(
node
.
isLeaf
())
{
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
result
.
add
(
node
.
getElement
());
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
return
;
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
}
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
for
(
Node
<
E
>
child
:
node
.
getChildren
())
{
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
getAllIntersectingElementsRecursive
(
child
,
query
,
result
);
}
}
}
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
// ---------------------------------------------------------------------
// Build
public
List
<
E
>
getAllIntersectingElements
(
AABB
query
)
{
// ---------------------------------------------------------------------
List
<
E
>
result
=
new
ArrayList
<>();
getAllIntersectingElementsRecursive
(
root
,
query
,
result
);
private
List
<
BuildItem
<
E
>>
toBuildItems
(
List
<
E
>
elements
)
{
return
result
;
List
<
BuildItem
<
E
>>
items
=
new
ArrayList
<>(
elements
.
size
());
}
for
(
E
e
:
elements
)
{
AABB
aabb
=
Objects
.
requireNonNull
(
aabbFunction
.
apply
(
e
),
"aabbFunction returned null"
);
private
void
getAllIntersectingElementsRecursive
(
Node
<
E
>
node
,
AABB
query
,
List
<
E
>
result
)
{
items
.
add
(
new
BuildItem
<>(
e
,
aabb
));
if
(
node
==
null
)
{
}
return
;
return
items
;
}
}
if
(!
node
.
getAabb
().
overlaps
(
query
))
{
private
SplitStrategy
resolveSplitStrategy
(
Builder
<
E
>
config
)
{
return
;
if
(
config
.
getSplitStrategy
()
!=
SplitStrategy
.
AUTO
)
{
}
validateStrategyMatchesDegree
(
config
.
getDegree
(),
config
.
getSplitStrategy
());
return
config
.
getSplitStrategy
();
if
(
node
.
isLeaf
())
{
}
result
.
add
(
node
.
getElement
());
return
;
return
(
config
.
getDegree
()
==
2
)
?
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
:
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
}
}
for
(
Node
<
E
>
child
:
node
.
getChildren
())
{
private
void
validateStrategyMatchesDegree
(
int
degree
,
SplitStrategy
strategy
)
{
getAllIntersectingElementsRecursive
(
child
,
query
,
result
);
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
;
// Build
// ---------------------------------------------------------------------
if
(
degree
==
2
&&
!
binary
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 2."
);
private
List
<
BuildItem
<
E
>>
toBuildItems
(
List
<
E
>
elements
)
{
}
List
<
BuildItem
<
E
>>
items
=
new
ArrayList
<>(
elements
.
size
());
if
(
degree
==
8
&&
!
octonary
)
{
for
(
E
e
:
elements
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 8."
);
AABB
aabb
=
Objects
.
requireNonNull
(
aabbFunction
.
apply
(
e
),
"aabbFunction returned null"
);
}
items
.
add
(
new
BuildItem
<>(
e
,
aabb
));
}
}
return
items
;
private
Node
<
E
>
buildBinaryRecursive
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
SplitStrategy
strategy
,
}
Builder
<
E
>
config
)
{
private
SplitStrategy
resolveSplitStrategy
(
BuildConfig
config
)
{
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
if
(
config
.
getSplitStrategy
()
!=
SplitStrategy
.
AUTO
)
{
validateStrategyMatchesDegree
(
config
.
getDegree
(),
config
.
getSplitStrategy
());
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
return
config
.
getSplitStrategy
();
return
packLeafFromItems
(
items
,
totalAabb
);
}
}
return
(
config
.
getDegree
()
==
2
)
int
axis
=
totalAabb
.
findLongestAxis
();
?
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
SplitResult
<
E
>
split
;
:
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
}
switch
(
strategy
)
{
case
BINARY_OBJECT_MEDIAN:
private
void
validateStrategyMatchesDegree
(
int
degree
,
SplitStrategy
strategy
)
{
split
=
splitBinaryObjectMedian
(
items
,
axis
);
boolean
binary
=
strategy
==
SplitStrategy
.
BINARY_OBJECT_MEDIAN
break
;
||
strategy
==
SplitStrategy
.
BINARY_OBJECT_MEAN
case
BINARY_OBJECT_MEAN:
||
strategy
==
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
;
split
=
splitBinaryObjectMean
(
items
,
axis
);
break
;
boolean
octonary
=
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEDIAN
case
BINARY_SPATIAL_MEDIAN:
||
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEAN
split
=
splitBinarySpatialMedian
(
items
,
axis
,
totalAabb
);
||
strategy
==
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
;
break
;
default
:
if
(
degree
==
2
&&
!
binary
)
{
throw
new
IllegalStateException
(
"Unexpected binary strategy: "
+
strategy
);
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 2."
);
}
}
if
(
degree
==
8
&&
!
octonary
)
{
if
(!
split
.
valid
())
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 8."
);
return
packLeafFromItems
(
items
,
totalAabb
);
}
}
}
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
private
Node
<
E
>
buildBinaryRecursive
(
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
left
,
depth
+
1
,
strategy
,
config
));
List
<
BuildItem
<
E
>>
items
,
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
right
,
depth
+
1
,
strategy
,
config
));
int
depth
,
return
node
;
SplitStrategy
strategy
,
}
BuildConfig
config
)
{
private
Node
<
E
>
buildOctonaryRecursive
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
SplitStrategy
strategy
,
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
Builder
<
E
>
config
)
{
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
return
packLeafFromItems
(
items
,
totalAabb
);
}
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
return
packLeafFromItems
(
items
,
totalAabb
);
int
axis
=
totalAabb
.
findLongestAxis
();
}
SplitResult
<
E
>
split
;
OctSplit
<
E
>
split
;
switch
(
strategy
)
{
switch
(
strategy
)
{
case
BINARY_OBJECT_MEDIAN:
case
OCTONARY_OBJECT_MEDIAN:
split
=
splitBinaryObjectMedian
(
items
,
axis
);
split
=
splitOctonaryObjectMedian
(
items
);
break
;
break
;
case
BINARY_OBJECT_MEAN:
case
OCTONARY_OBJECT_MEAN:
split
=
splitBinaryObjectMean
(
items
,
axis
);
split
=
splitOctonaryObjectMean
(
items
);
break
;
break
;
case
BINARY_SPATIAL_MEDIAN:
case
OCTONARY_SPATIAL_MEDIAN:
split
=
splitBinarySpatialMedian
(
items
,
axis
,
totalAabb
);
split
=
splitOctonarySpatialMedian
(
items
,
totalAabb
);
break
;
break
;
default
:
default
:
throw
new
IllegalStateException
(
"Unexpected binary strategy: "
+
strategy
);
throw
new
IllegalStateException
(
"Unexpected octonary strategy: "
+
strategy
);
}
}
if
(!
split
.
valid
())
{
if
(!
split
.
valid
())
{
return
packLeafFromItems
(
items
,
totalAabb
);
return
packLeafFromItems
(
items
,
totalAabb
);
}
}
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
left
,
depth
+
1
,
strategy
,
config
));
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
right
,
depth
+
1
,
strategy
,
config
));
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
return
node
;
List
<
BuildItem
<
E
>>
bucket
=
split
.
buckets
[
i
];
}
if
(
bucket
.
isEmpty
())
{
continue
;
private
Node
<
E
>
buildOctonaryRecursive
(
}
List
<
BuildItem
<
E
>>
items
,
node
.
getChildren
().
add
(
buildOctonaryRecursive
(
bucket
,
depth
+
1
,
strategy
,
config
));
int
depth
,
}
SplitStrategy
strategy
,
BuildConfig
config
)
{
return
node
;
}
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
private
boolean
shouldStop
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
Builder
<
E
>
config
,
AABB
totalAabb
)
{
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
return
items
.
size
()
<=
config
.
getMaxLeafSize
()
||
depth
>=
config
.
getMaxDepth
()
return
packLeafFromItems
(
items
,
totalAabb
);
||
totalAabb
.
isDegenerate
(
config
.
getDegenerateTolerance
());
}
}
OctSplit
<
E
>
split
;
private
Node
<
E
>
packLeafFromItems
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
switch
(
strategy
)
{
if
(
items
.
size
()
==
1
)
{
case
OCTONARY_OBJECT_MEDIAN:
BuildItem
<
E
>
item
=
items
.
get
(
0
);
split
=
splitOctonaryObjectMedian
(
items
);
return
new
Node
<>(
item
.
element
,
item
.
aabb
);
break
;
}
case
OCTONARY_OBJECT_MEAN:
split
=
splitOctonaryObjectMean
(
items
);
Node
<
E
>
leaf
=
new
Node
<>(
null
,
totalAabb
);
break
;
for
(
BuildItem
<
E
>
item
:
items
)
{
case
OCTONARY_SPATIAL_MEDIAN:
leaf
.
getChildren
().
add
(
new
Node
<>(
item
.
element
,
item
.
aabb
));
split
=
splitOctonarySpatialMedian
(
items
,
totalAabb
);
}
break
;
return
leaf
;
default
:
}
throw
new
IllegalStateException
(
"Unexpected octonary strategy: "
+
strategy
);
}
private
static
<
E
>
AABB
getAggregateAABBFromItems
(
List
<
BuildItem
<
E
>>
items
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
;
if
(!
split
.
valid
())
{
double
minY
=
Double
.
POSITIVE_INFINITY
;
return
packLeafFromItems
(
items
,
totalAabb
);
double
minZ
=
Double
.
POSITIVE_INFINITY
;
}
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
for
(
BuildItem
<
E
>
item
:
items
)
{
List
<
BuildItem
<
E
>>
bucket
=
split
.
buckets
[
i
];
AABB
aabb
=
item
.
aabb
;
if
(
bucket
.
isEmpty
())
{
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
continue
;
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
}
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
node
.
getChildren
().
add
(
buildOctonaryRecursive
(
bucket
,
depth
+
1
,
strategy
,
config
));
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
}
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
return
node
;
}
}
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
private
boolean
shouldStop
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
BuildConfig
config
,
AABB
totalAabb
)
{
}
return
items
.
size
()
<=
config
.
getMaxLeafSize
()
||
depth
>=
config
.
getMaxDepth
()
// ---------------------------------------------------------------------
||
totalAabb
.
isDegenerate
(
config
.
getDegenerateTolerance
());
// Binary splits
}
// ---------------------------------------------------------------------
private
Node
<
E
>
packLeafFromItems
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
private
SplitResult
<
E
>
splitBinaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
if
(
items
.
size
()
==
1
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
center
(
axis
)));
BuildItem
<
E
>
item
=
items
.
get
(
0
);
int
mid
=
items
.
size
()
/
2
;
return
new
Node
<>(
item
.
element
,
item
.
aabb
);
}
if
(
mid
<=
0
||
mid
>=
items
.
size
())
{
return
SplitResult
.
invalid
();
Node
<
E
>
leaf
=
new
Node
<>(
null
,
totalAabb
);
}
for
(
BuildItem
<
E
>
item
:
items
)
{
leaf
.
getChildren
().
add
(
new
Node
<>(
item
.
element
,
item
.
aabb
));
return
SplitResult
.
of
(
items
.
subList
(
0
,
mid
),
items
.
subList
(
mid
,
items
.
size
()));
}
}
return
leaf
;
}
private
SplitResult
<
E
>
splitBinaryObjectMean
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
double
sum
=
0.0
;
private
static
<
E
>
AABB
getAggregateAABBFromItems
(
List
<
BuildItem
<
E
>>
items
)
{
for
(
BuildItem
<
E
>
item
:
items
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
;
sum
+=
item
.
center
(
axis
);
double
minY
=
Double
.
POSITIVE_INFINITY
;
}
double
minZ
=
Double
.
POSITIVE_INFINITY
;
double
splitValue
=
sum
/
items
.
size
();
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
for
(
BuildItem
<
E
>
item
:
items
)
{
for
(
BuildItem
<
E
>
item
:
items
)
{
AABB
aabb
=
item
.
aabb
;
if
(
item
.
center
(
axis
)
<
splitValue
)
{
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
left
.
add
(
item
);
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
}
else
{
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
right
.
add
(
item
);
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
}
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
}
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
splitBinaryObjectMedian
(
items
,
axis
);
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
}
return
SplitResult
.
of
(
left
,
right
);
// ---------------------------------------------------------------------
}
// Binary splits
// ---------------------------------------------------------------------
private
SplitResult
<
E
>
splitBinarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
,
AABB
totalAabb
)
{
double
splitValue
;
private
SplitResult
<
E
>
splitBinaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
switch
(
axis
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
center
(
axis
)));
case
0
:
int
mid
=
items
.
size
()
/
2
;
splitValue
=
totalAabb
.
getCenterX
();
break
;
if
(
mid
<=
0
||
mid
>=
items
.
size
())
{
case
1
:
return
SplitResult
.
invalid
();
splitValue
=
totalAabb
.
getCenterY
();
}
break
;
case
2
:
return
SplitResult
.
of
(
items
.
subList
(
0
,
mid
),
items
.
subList
(
mid
,
items
.
size
()));
splitValue
=
totalAabb
.
getCenterZ
();
}
break
;
default
:
private
SplitResult
<
E
>
splitBinaryObjectMean
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
double
sum
=
0.0
;
}
for
(
BuildItem
<
E
>
item
:
items
)
{
sum
+=
item
.
center
(
axis
);
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
}
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
double
splitValue
=
sum
/
items
.
size
();
for
(
BuildItem
<
E
>
item
:
items
)
{
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
if
(
item
.
center
(
axis
)
<
splitValue
)
{
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
left
.
add
(
item
);
}
else
{
for
(
BuildItem
<
E
>
item
:
items
)
{
right
.
add
(
item
);
if
(
item
.
center
(
axis
)
<
splitValue
)
{
}
left
.
add
(
item
);
}
}
else
{
right
.
add
(
item
);
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
}
return
splitBinaryObjectMean
(
items
,
axis
);
}
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
SplitResult
.
of
(
left
,
right
);
return
splitBinaryObjectMedian
(
items
,
axis
);
}
}
private
static
final
class
SplitResult
<
E
>
{
return
SplitResult
.
of
(
left
,
right
);
final
List
<
BuildItem
<
E
>>
left
;
}
final
List
<
BuildItem
<
E
>>
right
;
private
SplitResult
<
E
>
splitBinarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
,
AABB
totalAabb
)
{
private
SplitResult
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
double
splitValue
;
this
.
left
=
left
;
switch
(
axis
)
{
this
.
right
=
right
;
case
0
:
splitValue
=
totalAabb
.
getCenterX
();
break
;
}
case
1
:
splitValue
=
totalAabb
.
getCenterY
();
break
;
case
2
:
splitValue
=
totalAabb
.
getCenterZ
();
break
;
static
<
E
>
SplitResult
<
E
>
of
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
return
new
SplitResult
<>(
left
,
right
);
}
}
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
static
<
E
>
SplitResult
<
E
>
invalid
()
{
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
return
new
SplitResult
<>(
null
,
null
);
}
for
(
BuildItem
<
E
>
item
:
items
)
{
if
(
item
.
center
(
axis
)
<
splitValue
)
{
boolean
valid
()
{
left
.
add
(
item
);
return
left
!=
null
&&
right
!=
null
&&
!
left
.
isEmpty
()
&&
!
right
.
isEmpty
();
}
else
{
}
right
.
add
(
item
);
}
}
}
// ---------------------------------------------------------------------
// Octonary / 8-ary splits
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
// ---------------------------------------------------------------------
return
splitBinaryObjectMean
(
items
,
axis
);
}
private
OctSplit
<
E
>
splitOctonaryObjectMean
(
List
<
BuildItem
<
E
>>
items
)
{
double
sumX
=
0.0
;
return
SplitResult
.
of
(
left
,
right
);
double
sumY
=
0.0
;
}
double
sumZ
=
0.0
;
private
static
final
class
SplitResult
<
E
>
{
for
(
BuildItem
<
E
>
item
:
items
)
{
final
List
<
BuildItem
<
E
>>
left
;
sumX
+=
item
.
centerX
;
final
List
<
BuildItem
<
E
>>
right
;
sumY
+=
item
.
centerY
;
sumZ
+=
item
.
centerZ
;
private
SplitResult
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
}
this
.
left
=
left
;
this
.
right
=
right
;
double
sx
=
sumX
/
items
.
size
();
}
double
sy
=
sumY
/
items
.
size
();
double
sz
=
sumZ
/
items
.
size
();
static
<
E
>
SplitResult
<
E
>
of
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
return
new
SplitResult
<>(
left
,
right
);
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
}
static
<
E
>
SplitResult
<
E
>
invalid
()
{
private
OctSplit
<
E
>
splitOctonarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
return
new
SplitResult
<>(
null
,
null
);
double
sx
=
totalAabb
.
getCenterX
();
}
double
sy
=
totalAabb
.
getCenterY
();
double
sz
=
totalAabb
.
getCenterZ
();
boolean
valid
()
{
return
left
!=
null
&&
right
!=
null
&&
!
left
.
isEmpty
()
&&
!
right
.
isEmpty
();
OctSplit
<
E
>
split
=
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
false
);
}
if
(!
split
.
valid
())
{
}
return
splitOctonaryObjectMean
(
items
);
}
// ---------------------------------------------------------------------
return
split
;
// Octonary / 8-ary splits
}
// ---------------------------------------------------------------------
private
OctSplit
<
E
>
splitOctonaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
)
{
private
OctSplit
<
E
>
splitOctonaryObjectMean
(
List
<
BuildItem
<
E
>>
items
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerX
));
double
sumX
=
0.0
;
double
sx
=
medianValue
(
items
,
0
);
double
sumY
=
0.0
;
double
sumZ
=
0.0
;
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerY
));
double
sy
=
medianValue
(
items
,
1
);
for
(
BuildItem
<
E
>
item
:
items
)
{
sumX
+=
item
.
centerX
;
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerZ
));
sumY
+=
item
.
centerY
;
double
sz
=
medianValue
(
items
,
2
);
sumZ
+=
item
.
centerZ
;
}
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
double
sx
=
sumX
/
items
.
size
();
double
sy
=
sumY
/
items
.
size
();
private
double
medianValue
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
double
sz
=
sumZ
/
items
.
size
();
int
n
=
items
.
size
();
int
mid
=
n
/
2
;
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
if
((
n
&
1
)
==
1
)
{
return
items
.
get
(
mid
).
center
(
axis
);
private
OctSplit
<
E
>
splitOctonarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
}
double
sx
=
totalAabb
.
getCenterX
();
return
0.5
*
(
items
.
get
(
mid
-
1
).
center
(
axis
)
+
items
.
get
(
mid
).
center
(
axis
));
double
sy
=
totalAabb
.
getCenterY
();
}
double
sz
=
totalAabb
.
getCenterZ
();
@SuppressWarnings
(
"unchecked"
)
OctSplit
<
E
>
split
=
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
false
);
private
OctSplit
<
E
>
bucketizeOctonary
(
List
<
BuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
,
if
(!
split
.
valid
())
{
boolean
fallbackToMeanOnFailure
)
{
return
splitOctonaryObjectMean
(
items
);
List
<
BuildItem
<
E
>>[]
buckets
=
new
List
[
8
];
}
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
return
split
;
buckets
[
i
]
=
new
ArrayList
<>();
}
}
private
OctSplit
<
E
>
splitOctonaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
)
{
int
nonEmptyCount
=
0
;
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerX
));
boolean
[]
seen
=
new
boolean
[
8
];
double
sx
=
medianValue
(
items
,
0
);
for
(
BuildItem
<
E
>
item
:
items
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerY
));
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
double
sy
=
medianValue
(
items
,
1
);
buckets
[
idx
].
add
(
item
);
if
(!
seen
[
idx
])
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerZ
));
seen
[
idx
]
=
true
;
double
sz
=
medianValue
(
items
,
2
);
nonEmptyCount
++;
}
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
}
if
(
nonEmptyCount
<=
1
)
{
private
double
medianValue
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
if
(
fallbackToMeanOnFailure
)
{
int
n
=
items
.
size
();
// caller may already be object mean, so just mark invalid here
int
mid
=
n
/
2
;
return
OctSplit
.
invalid
();
}
if
((
n
&
1
)
==
1
)
{
return
OctSplit
.
invalid
();
return
items
.
get
(
mid
).
center
(
axis
);
}
}
return
0.5
*
(
items
.
get
(
mid
-
1
).
center
(
axis
)
+
items
.
get
(
mid
).
center
(
axis
));
return
OctSplit
.
of
(
buckets
);
}
}
@SuppressWarnings
(
"unchecked"
)
private
int
octantIndex
(
double
x
,
double
y
,
double
z
,
double
sx
,
double
sy
,
double
sz
)
{
private
OctSplit
<
E
>
bucketizeOctonary
(
List
<
BuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
,
boolean
fallbackToMeanOnFailure
)
{
int
idx
=
0
;
List
<
BuildItem
<
E
>>[]
buckets
=
new
List
[
8
];
if
(
x
>=
sx
)
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
idx
|=
1
;
buckets
[
i
]
=
new
ArrayList
<>();
if
(
y
>=
sy
)
}
idx
|=
2
;
if
(
z
>=
sz
)
int
nonEmptyCount
=
0
;
idx
|=
4
;
boolean
[]
seen
=
new
boolean
[
8
];
return
idx
;
}
for
(
BuildItem
<
E
>
item
:
items
)
{
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
private
static
final
class
OctSplit
<
E
>
{
buckets
[
idx
].
add
(
item
);
final
List
<
BuildItem
<
E
>>[]
buckets
;
if
(!
seen
[
idx
])
{
seen
[
idx
]
=
true
;
private
OctSplit
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
nonEmptyCount
++;
this
.
buckets
=
buckets
;
}
}
}
static
<
E
>
OctSplit
<
E
>
of
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
if
(
nonEmptyCount
<=
1
)
{
return
new
OctSplit
<>(
buckets
);
if
(
fallbackToMeanOnFailure
)
{
}
// caller may already be object mean, so just mark invalid here
return
OctSplit
.
invalid
();
static
<
E
>
OctSplit
<
E
>
invalid
()
{
}
return
new
OctSplit
<>(
null
);
return
OctSplit
.
invalid
();
}
}
boolean
valid
()
{
return
OctSplit
.
of
(
buckets
);
if
(
buckets
==
null
)
{
}
return
false
;
}
private
int
octantIndex
(
double
x
,
double
y
,
double
z
,
double
sx
,
double
sy
,
double
sz
)
{
int
nonEmpty
=
0
;
int
idx
=
0
;
for
(
List
<
BuildItem
<
E
>>
bucket
:
buckets
)
{
if
(
x
>=
sx
)
idx
|=
1
;
if
(
bucket
!=
null
&&
!
bucket
.
isEmpty
())
{
if
(
y
>=
sy
)
idx
|=
2
;
nonEmpty
++;
if
(
z
>=
sz
)
idx
|=
4
;
}
return
idx
;
}
}
return
nonEmpty
>
1
;
}
private
static
final
class
OctSplit
<
E
>
{
}
final
List
<
BuildItem
<
E
>>[]
buckets
;
// ---------------------------------------------------------------------
private
OctSplit
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
// helpers: to be continued with more heuristics
this
.
buckets
=
buckets
;
// ---------------------------------------------------------------------
}
private
static
int
computeDefaultMaxDepth
(
int
n
)
{
static
<
E
>
OctSplit
<
E
>
of
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
if
(
n
<=
1
)
{
return
new
OctSplit
<>(
buckets
);
return
DEFAULT_MIN_DEPTH
;
}
}
double
log2n
=
Math
.
log
(
n
)
/
Math
.
log
(
2.0
);
@SuppressWarnings
(
"unchecked"
)
int
depth
=
(
int
)
Math
.
ceil
(
2.0
*
log2n
);
static
<
E
>
OctSplit
<
E
>
invalid
()
{
return
clamp
(
depth
,
DEFAULT_MIN_DEPTH
,
DEFAULT_MAX_DEPTH
);
return
new
OctSplit
<>(
null
);
}
}
private
static
int
clamp
(
int
value
,
int
min
,
int
max
)
{
boolean
valid
()
{
return
Math
.
max
(
min
,
Math
.
min
(
max
,
value
));
if
(
buckets
==
null
)
{
}
return
false
;
}
int
nonEmpty
=
0
;
for
(
List
<
BuildItem
<
E
>>
bucket
:
buckets
)
{
if
(
bucket
!=
null
&&
!
bucket
.
isEmpty
())
{
nonEmpty
++;
}
}
return
nonEmpty
>
1
;
}
}
// ---------------------------------------------------------------------
// helpers: to be continued with more heuristics
// ---------------------------------------------------------------------
private
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
));
}
}
}
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/bht/SolidSelfIntCheckAABB.java
View file @
7eb83cde
...
@@ -88,8 +88,11 @@ public class SolidSelfIntCheckAABB extends Check {
...
@@ -88,8 +88,11 @@ public class SolidSelfIntCheckAABB extends Check {
CheckResult
cr
;
CheckResult
cr
;
// 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
);
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NestedRingsCheck.java
View file @
7eb83cde
...
@@ -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
);
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
View file @
7eb83cde
...
@@ -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
);
...
@@ -263,11 +261,9 @@ public class RingSelfIntCheck extends Check {
...
@@ -263,11 +261,9 @@ 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
);
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/SelfIntersectionUtil.java
View file @
7eb83cde
...
@@ -133,21 +133,18 @@ public class SelfIntersectionUtil {
...
@@ -133,21 +133,18 @@ public class SelfIntersectionUtil {
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersectionWithTree
(
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersectionWithTree
(
Geometry
g
,
Geometry
g
,
double
delta
,
double
delta
,
BoundingVolumeHierarchyTree
.
Build
Config
treeConfig
)
{
BoundingVolumeHierarchyTree
.
Build
er
<
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
());
for
(
int
i
=
0
;
i
<
tesselatedPolygons
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
tesselatedPolygons
.
size
();
i
++)
{
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
.
Build
Config
.
binaryDefault
()
new
BoundingVolumeHierarchyTree
.
Build
er
<
Integer
>()
.
binaryDefault
()
);
);
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/util/SolidSelfIntersectionBVHUtilTest.java
View file @
7eb83cde
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,13 +20,30 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod;
...
@@ -15,13 +20,30 @@ 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
{
...
@@ -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
;
...
...
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