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
Show 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
*/
*/
...
@@ -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
(
Build
Config
config
)
{
private
SplitStrategy
resolveSplitStrategy
(
Build
er
<
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
);
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/bht/SolidSelfIntCheckAABB.java
View file @
7eb83cde
...
@@ -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
);
...
...
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
);
...
@@ -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
);
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/SelfIntersectionUtil.java
View file @
7eb83cde
...
@@ -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
.
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
());
...
@@ -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
.
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,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
;
...
...
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