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
e6f4979d
Commit
e6f4979d
authored
Apr 28, 2026
by
Numanoglu
Browse files
Add BVH variants and validation tests
parent
54c56f2d
Changes
25
Expand all
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BinarySplitResult.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
import
java.util.List
;
final
class
BinarySplitResult
<
E
>
{
final
List
<
BvhBuildItem
<
E
>>
left
;
final
List
<
BvhBuildItem
<
E
>>
right
;
private
BinarySplitResult
(
List
<
BvhBuildItem
<
E
>>
left
,
List
<
BvhBuildItem
<
E
>>
right
)
{
this
.
left
=
left
;
this
.
right
=
right
;
}
static
<
E
>
BinarySplitResult
<
E
>
of
(
List
<
BvhBuildItem
<
E
>>
left
,
List
<
BvhBuildItem
<
E
>>
right
)
{
return
new
BinarySplitResult
<>(
left
,
right
);
}
static
<
E
>
BinarySplitResult
<
E
>
invalid
()
{
return
new
BinarySplitResult
<>(
null
,
null
);
}
boolean
valid
()
{
return
left
!=
null
&&
right
!=
null
&&
!
left
.
isEmpty
()
&&
!
right
.
isEmpty
();
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BinarySplitters.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
import
java.util.ArrayList
;
import
java.util.Comparator
;
import
java.util.List
;
final
class
BinarySplitters
{
private
BinarySplitters
()
{
}
static
<
E
>
BinarySplitResult
<
E
>
objectMedian
(
List
<
BvhBuildItem
<
E
>>
items
,
int
axis
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
center
(
axis
)));
int
mid
=
items
.
size
()
/
2
;
if
(
mid
<=
0
||
mid
>=
items
.
size
())
{
return
BinarySplitResult
.
invalid
();
}
return
BinarySplitResult
.
of
(
items
.
subList
(
0
,
mid
),
items
.
subList
(
mid
,
items
.
size
()));
}
static
<
E
>
BinarySplitResult
<
E
>
objectMean
(
List
<
BvhBuildItem
<
E
>>
items
,
int
axis
)
{
double
sum
=
0.0
;
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
sum
+=
item
.
center
(
axis
);
}
double
splitValue
=
sum
/
items
.
size
();
List
<
BvhBuildItem
<
E
>>
left
=
new
ArrayList
<>();
List
<
BvhBuildItem
<
E
>>
right
=
new
ArrayList
<>();
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
if
(
item
.
center
(
axis
)
<
splitValue
)
{
left
.
add
(
item
);
}
else
{
right
.
add
(
item
);
}
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
objectMedian
(
items
,
axis
);
}
return
BinarySplitResult
.
of
(
left
,
right
);
}
static
<
E
>
BinarySplitResult
<
E
>
spatialMedian
(
List
<
BvhBuildItem
<
E
>>
items
,
int
axis
,
AABB
totalAabb
)
{
double
splitValue
;
switch
(
axis
)
{
case
0
:
splitValue
=
totalAabb
.
getCenterX
();
break
;
case
1
:
splitValue
=
totalAabb
.
getCenterY
();
break
;
case
2
:
splitValue
=
totalAabb
.
getCenterZ
();
break
;
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
}
List
<
BvhBuildItem
<
E
>>
left
=
new
ArrayList
<>();
List
<
BvhBuildItem
<
E
>>
right
=
new
ArrayList
<>();
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
if
(
item
.
center
(
axis
)
<
splitValue
)
{
left
.
add
(
item
);
}
else
{
right
.
add
(
item
);
}
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
objectMean
(
items
,
axis
);
}
return
BinarySplitResult
.
of
(
left
,
right
);
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyBuilder.java
0 → 100644
View file @
e6f4979d
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
()
||
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
);
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 @
e6f4979d
This diff is collapsed.
Click to expand it.
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BvhBuildItem.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
final
class
BvhBuildItem
<
E
>
{
final
E
element
;
final
AABB
aabb
;
final
double
centerX
;
final
double
centerY
;
final
double
centerZ
;
BvhBuildItem
(
E
element
,
AABB
aabb
)
{
this
.
element
=
element
;
this
.
aabb
=
aabb
;
this
.
centerX
=
0.5
*
(
aabb
.
getMinX
()
+
aabb
.
getMaxX
());
this
.
centerY
=
0.5
*
(
aabb
.
getMinY
()
+
aabb
.
getMaxY
());
this
.
centerZ
=
0.5
*
(
aabb
.
getMinZ
()
+
aabb
.
getMaxZ
());
}
double
center
(
int
axis
)
{
switch
(
axis
)
{
case
0
:
return
centerX
;
case
1
:
return
centerY
;
case
2
:
return
centerZ
;
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
}
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/OctonarySplitResult.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
import
java.util.List
;
final
class
OctonarySplitResult
<
E
>
{
final
List
<
BvhBuildItem
<
E
>>[]
buckets
;
private
OctonarySplitResult
(
List
<
BvhBuildItem
<
E
>>[]
buckets
)
{
this
.
buckets
=
buckets
;
}
static
<
E
>
OctonarySplitResult
<
E
>
of
(
List
<
BvhBuildItem
<
E
>>[]
buckets
)
{
return
new
OctonarySplitResult
<>(
buckets
);
}
static
<
E
>
OctonarySplitResult
<
E
>
invalid
()
{
return
new
OctonarySplitResult
<>(
null
);
}
boolean
valid
()
{
if
(
buckets
==
null
)
{
return
false
;
}
int
nonEmpty
=
0
;
for
(
List
<
BvhBuildItem
<
E
>>
bucket
:
buckets
)
{
if
(
bucket
!=
null
&&
!
bucket
.
isEmpty
())
{
nonEmpty
++;
}
}
return
nonEmpty
>
1
;
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/OctonarySplitters.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
import
java.util.ArrayList
;
import
java.util.Comparator
;
import
java.util.List
;
final
class
OctonarySplitters
{
private
OctonarySplitters
()
{
}
static
<
E
>
OctonarySplitResult
<
E
>
objectMean
(
List
<
BvhBuildItem
<
E
>>
items
)
{
double
sumX
=
0.0
;
double
sumY
=
0.0
;
double
sumZ
=
0.0
;
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
sumX
+=
item
.
centerX
;
sumY
+=
item
.
centerY
;
sumZ
+=
item
.
centerZ
;
}
double
sx
=
sumX
/
items
.
size
();
double
sy
=
sumY
/
items
.
size
();
double
sz
=
sumZ
/
items
.
size
();
return
bucketize
(
items
,
sx
,
sy
,
sz
);
}
static
<
E
>
OctonarySplitResult
<
E
>
spatialMedian
(
List
<
BvhBuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
double
sx
=
totalAabb
.
getCenterX
();
double
sy
=
totalAabb
.
getCenterY
();
double
sz
=
totalAabb
.
getCenterZ
();
OctonarySplitResult
<
E
>
split
=
bucketize
(
items
,
sx
,
sy
,
sz
);
if
(!
split
.
valid
())
{
return
objectMean
(
items
);
}
return
split
;
}
static
<
E
>
OctonarySplitResult
<
E
>
objectMedian
(
List
<
BvhBuildItem
<
E
>>
items
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerX
));
double
sx
=
medianValue
(
items
,
0
);
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerY
));
double
sy
=
medianValue
(
items
,
1
);
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerZ
));
double
sz
=
medianValue
(
items
,
2
);
OctonarySplitResult
<
E
>
split
=
bucketize
(
items
,
sx
,
sy
,
sz
);
if
(!
split
.
valid
())
{
return
objectMean
(
items
);
}
return
split
;
}
private
static
<
E
>
double
medianValue
(
List
<
BvhBuildItem
<
E
>>
items
,
int
axis
)
{
int
n
=
items
.
size
();
int
mid
=
n
/
2
;
if
((
n
&
1
)
==
1
)
{
return
items
.
get
(
mid
).
center
(
axis
);
}
return
0.5
*
(
items
.
get
(
mid
-
1
).
center
(
axis
)
+
items
.
get
(
mid
).
center
(
axis
));
}
@SuppressWarnings
(
"unchecked"
)
private
static
<
E
>
OctonarySplitResult
<
E
>
bucketize
(
List
<
BvhBuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
)
{
List
<
BvhBuildItem
<
E
>>[]
buckets
=
new
List
[
8
];
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
buckets
[
i
]
=
new
ArrayList
<>();
}
int
nonEmptyCount
=
0
;
boolean
[]
seen
=
new
boolean
[
8
];
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
buckets
[
idx
].
add
(
item
);
if
(!
seen
[
idx
])
{
seen
[
idx
]
=
true
;
nonEmptyCount
++;
}
}
if
(
nonEmptyCount
<=
1
)
{
return
OctonarySplitResult
.
invalid
();
}
return
OctonarySplitResult
.
of
(
buckets
);
}
private
static
int
octantIndex
(
double
x
,
double
y
,
double
z
,
double
sx
,
double
sy
,
double
sz
)
{
int
idx
=
0
;
if
(
x
>=
sx
)
{
idx
|=
1
;
}
if
(
y
>=
sy
)
{
idx
|=
2
;
}
if
(
z
>=
sz
)
{
idx
|=
4
;
}
return
idx
;
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/SplitStrategy.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
public
enum
SplitStrategy
{
AUTO
,
BINARY_OBJECT_MEDIAN
,
BINARY_OBJECT_MEAN
,
BINARY_SPATIAL_MEDIAN
,
OCTONARY_OBJECT_MEDIAN
,
OCTONARY_OBJECT_MEAN
,
OCTONARY_SPATIAL_MEDIAN
}
CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyTreeVariantsTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.datastructure.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.util.ArrayList
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Set
;
import
org.junit.Test
;
public
class
BoundingVolumeHierarchyTreeVariantsTest
{
@Test
public
void
allConcreteSplitStrategiesReturnSameCandidatesAsBruteForce
()
{
List
<
TestBox
>
boxes
=
createSyntheticBoxes
();
List
<
AABB
>
queries
=
createQueries
();
for
(
SplitStrategy
strategy
:
concreteStrategies
())
{
BoundingVolumeHierarchyTree
<
TestBox
>
tree
=
BoundingVolumeHierarchyTree
.
newWithStrategy
(
boxes
,
box
->
box
.
aabb
,
strategy
);
assertTrue
(
"Expected root for "
+
strategy
,
tree
.
getRoot
()
!=
null
);
for
(
AABB
query
:
queries
)
{
Set
<
String
>
expected
=
bruteForceCandidates
(
boxes
,
query
);
Set
<
String
>
actual
=
names
(
tree
.
getAllIntersectingElements
(
query
));
assertFalse
(
"Synthetic query should hit at least one box for "
+
strategy
,
actual
.
isEmpty
());
assertEquals
(
"Candidate set differs for "
+
strategy
,
expected
,
actual
);
}
}
}
@Test
(
expected
=
IllegalArgumentException
.
class
)
public
void
autoStrategyIsNotAConcreteFactoryVariant
()
{
BoundingVolumeHierarchyTree
.
newWithStrategy
(
createSyntheticBoxes
(),
box
->
box
.
aabb
,
SplitStrategy
.
AUTO
);
}
private
static
SplitStrategy
[]
concreteStrategies
()
{
return
new
SplitStrategy
[]
{
SplitStrategy
.
BINARY_OBJECT_MEDIAN
,
SplitStrategy
.
BINARY_OBJECT_MEAN
,
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
,
SplitStrategy
.
OCTONARY_OBJECT_MEDIAN
,
SplitStrategy
.
OCTONARY_OBJECT_MEAN
,
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
};
}
private
static
List
<
TestBox
>
createSyntheticBoxes
()
{
List
<
TestBox
>
boxes
=
new
ArrayList
<>();
boxes
.
add
(
new
TestBox
(
"a"
,
new
AABB
(
0.0
,
0.0
,
0.0
,
1.0
,
1.0
,
1.0
)));
boxes
.
add
(
new
TestBox
(
"b"
,
new
AABB
(
0.8
,
0.8
,
0.0
,
1.8
,
1.8
,
1.0
)));
boxes
.
add
(
new
TestBox
(
"c"
,
new
AABB
(
3.0
,
0.0
,
0.0
,
4.0
,
1.0
,
1.0
)));
boxes
.
add
(
new
TestBox
(
"d"
,
new
AABB
(
0.0
,
3.0
,
0.0
,
1.0
,
4.0
,
1.0
)));
boxes
.
add
(
new
TestBox
(
"e"
,
new
AABB
(
3.0
,
3.0
,
0.0
,
4.0
,
4.0
,
1.0
)));
boxes
.
add
(
new
TestBox
(
"f"
,
new
AABB
(
6.0
,
6.0
,
0.0
,
7.0
,
7.0
,
1.0
)));
return
boxes
;
}
private
static
List
<
AABB
>
createQueries
()
{
List
<
AABB
>
queries
=
new
ArrayList
<>();
queries
.
add
(
new
AABB
(
0.5
,
0.5
,
0.0
,
1.2
,
1.2
,
1.0
));
queries
.
add
(
new
AABB
(
2.9
,
2.9
,
0.0
,
4.1
,
4.1
,
1.0
));
queries
.
add
(
new
AABB
(
5.5
,
5.5
,
0.0
,
7.5
,
7.5
,
1.0
));
return
queries
;
}
private
static
Set
<
String
>
bruteForceCandidates
(
List
<
TestBox
>
boxes
,
AABB
query
)
{
Set
<
String
>
candidates
=
new
HashSet
<>();
for
(
TestBox
box
:
boxes
)
{
if
(
box
.
aabb
.
overlaps
(
query
))
{
candidates
.
add
(
box
.
name
);
}
}
return
candidates
;
}
private
static
Set
<
String
>
names
(
List
<
TestBox
>
boxes
)
{
Set
<
String
>
names
=
new
HashSet
<>();
for
(
TestBox
box
:
boxes
)
{
names
.
add
(
box
.
name
);
}
return
names
;
}
private
static
final
class
TestBox
{
final
String
name
;
final
AABB
aabb
;
TestBox
(
String
name
,
AABB
aabb
)
{
this
.
name
=
name
;
this
.
aabb
=
aabb
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/bht/SolidSelfIntCheckAABB.java
View file @
e6f4979d
...
@@ -88,11 +88,11 @@ public class SolidSelfIntCheckAABB extends Check {
...
@@ -88,11 +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
.
Builder
<
Polygon
>()
BoundingVolumeHierarchyTree
.<
Polygon
>
binaryBuilder
()
.
elements
(
polys
)
.
elements
(
polys
)
.
f
unction
(
p
->
AABB
.
of
(
p
.
getOriginal
()))
.
aabbF
unction
(
p
->
AABB
.
of
(
p
.
getOriginal
()))
.
build
();
.
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 @
e6f4979d
...
@@ -20,7 +20,10 @@ package de.hft.stuttgart.citydoctor2.checks.geometry;
...
@@ -20,7 +20,10 @@ package de.hft.stuttgart.citydoctor2.checks.geometry;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.Set
;
import
java.util.Set
;
import
de.hft.stuttgart.citydoctor2.check.Check
;
import
de.hft.stuttgart.citydoctor2.check.Check
;
...
@@ -36,6 +39,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
...
@@ -36,6 +39,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
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.SplitStrategy
;
/**
/**
* Checks whether a inner ring is completely contained in another inner ring
* Checks whether a inner ring is completely contained in another inner ring
...
@@ -59,32 +64,77 @@ public class NestedRingsCheck extends Check {
...
@@ -59,32 +64,77 @@ public class NestedRingsCheck extends Check {
dependencies
=
Collections
.
unmodifiableList
(
deps
);
dependencies
=
Collections
.
unmodifiableList
(
deps
);
}
}
/**
public
enum
Variant
{
* FilterSwitch @Numanoglu
AUTO
,
* true -> use AABB prefilter
OLD
,
* false -> use original double-loop exact version
AABB_FILTER
,
*/
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
private
boolean
useAabbFilter
=
true
;
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
BVH_BINARY_SPATIAL_MEDIAN
(
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
),
BVH_OCTONARY_OBJECT_MEDIAN
(
SplitStrategy
.
OCTONARY_OBJECT_MEDIAN
),
BVH_OCTONARY_OBJECT_MEAN
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
),
BVH_OCTONARY_SPATIAL_MEDIAN
(
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
);
private
final
SplitStrategy
splitStrategy
;
Variant
()
{
this
.
splitStrategy
=
null
;
}
Variant
(
SplitStrategy
splitStrategy
)
{
this
.
splitStrategy
=
splitStrategy
;
}
public
boolean
isBvh
()
{
return
splitStrategy
!=
null
;
}
public
SplitStrategy
getSplitStrategy
()
{
if
(
splitStrategy
==
null
)
{
throw
new
IllegalStateException
(
"Variant "
+
this
+
" has no BVH split strategy."
);
}
return
splitStrategy
;
}
}
private
Variant
variant
=
Variant
.
AUTO
;
public
NestedRingsCheck
()
{
public
NestedRingsCheck
()
{
}
}
public
NestedRingsCheck
(
boolean
useAabbFilter
)
{
public
NestedRingsCheck
(
boolean
useAabbFilter
)
{
this
.
useAabbFilter
=
useAabbFilter
;
this
.
variant
=
useAabbFilter
?
Variant
.
AABB_FILTER
:
Variant
.
OLD
;
}
public
NestedRingsCheck
(
Variant
variant
)
{
this
.
variant
=
Objects
.
requireNonNull
(
variant
,
"variant"
);
}
}
public
void
setUseAabbFilter
(
boolean
useAabbFilter
)
{
public
void
setUseAabbFilter
(
boolean
useAabbFilter
)
{
this
.
useAabbFilter
=
useAabbFilter
;
this
.
variant
=
useAabbFilter
?
Variant
.
AABB_FILTER
:
Variant
.
OLD
;
}
}
public
boolean
isUseAabbFilter
()
{
public
boolean
isUseAabbFilter
()
{
return
useAabbFilter
;
return
variant
!=
Variant
.
OLD
;
}
public
void
setVariant
(
Variant
variant
)
{
this
.
variant
=
Objects
.
requireNonNull
(
variant
,
"variant"
);
}
public
Variant
getVariant
()
{
return
variant
;
}
}
//
@Override
@Override
public
void
check
(
Polygon
p
)
{
public
void
check
(
Polygon
p
)
{
if
(
p
.
getInnerRings
().
size
()
>
3
)
{
if
(
variant
==
Variant
.
OLD
)
{
checkOriginal
(
p
);
}
else
if
(
variant
==
Variant
.
AABB_FILTER
)
{
checkWithBoundingBoxFilter
(
p
);
}
else
if
(
variant
.
isBvh
())
{
checkWithBvhFilter
(
p
,
variant
.
getSplitStrategy
());
}
else
if
(
p
.
getInnerRings
().
size
()
>
3
)
{
checkWithBoundingBoxFilter
(
p
);
checkWithBoundingBoxFilter
(
p
);
}
else
{
}
else
{
checkOriginal
(
p
);
checkOriginal
(
p
);
...
@@ -112,6 +162,46 @@ public class NestedRingsCheck extends Check {
...
@@ -112,6 +162,46 @@ public class NestedRingsCheck extends Check {
p
.
addCheckResult
(
cr
);
p
.
addCheckResult
(
cr
);
}
}
public
void
checkWithBvhFilter
(
Polygon
p
,
SplitStrategy
splitStrategy
)
{
Objects
.
requireNonNull
(
splitStrategy
,
"splitStrategy"
);
List
<
LinearRing
>
innerRings
=
p
.
getInnerRings
();
if
(
innerRings
==
null
||
innerRings
.
size
()
<
2
)
{
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
p
.
addCheckResult
(
cr
);
return
;
}
Map
<
LinearRing
,
AABB
>
ringBoxes
=
new
HashMap
<>(
innerRings
.
size
());
for
(
LinearRing
ring
:
innerRings
)
{
ringBoxes
.
put
(
ring
,
AABB
.
of
(
ring
));
}
BoundingVolumeHierarchyTree
<
LinearRing
>
ringTree
=
BoundingVolumeHierarchyTree
.
newWithStrategy
(
innerRings
,
ringBoxes:
:
get
,
splitStrategy
);
for
(
LinearRing
interiorRing
:
innerRings
)
{
AABB
interiorBox
=
ringBoxes
.
get
(
interiorRing
);
List
<
LinearRing
>
candidates
=
ringTree
.
getAllElementsContainedIn
(
interiorBox
);
for
(
LinearRing
checkRing
:
candidates
)
{
if
(
checkRing
==
interiorRing
)
{
continue
;
}
if
(
areAllPointsInside
(
interiorRing
,
checkRing
))
{
CheckError
err
=
new
NestedRingError
(
p
,
interiorRing
,
checkRing
);
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
p
.
addCheckResult
(
cr
);
return
;
}
}
}
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
p
.
addCheckResult
(
cr
);
}
/**
/**
* Alternative implementation using cached AABBs as broad-phase filter.
* Alternative implementation using cached AABBs as broad-phase filter.
* Exact geometry check is still done via areAllPointsInside(...).
* Exact geometry check is still done via areAllPointsInside(...).
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
View file @
e6f4979d
...
@@ -43,6 +43,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
...
@@ -43,6 +43,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
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.datastructure.bht.SplitStrategy
;
import
de.hft.stuttgart.citydoctor2.math.CovarianceMatrix
;
import
de.hft.stuttgart.citydoctor2.math.CovarianceMatrix
;
import
de.hft.stuttgart.citydoctor2.math.DistanceResult
;
import
de.hft.stuttgart.citydoctor2.math.DistanceResult
;
import
de.hft.stuttgart.citydoctor2.math.Matrix3x3d
;
import
de.hft.stuttgart.citydoctor2.math.Matrix3x3d
;
...
@@ -64,8 +65,8 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
...
@@ -64,8 +65,8 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
* Variants:
* Variants:
* - OLD: only BoundingBox replaced by AABB, logic otherwise unchanged
* - OLD: only BoundingBox replaced by AABB, logic otherwise unchanged
* (the most efficient for little models)
* (the most efficient for little models)
*
- TREE_1_EDGE_BVH: edge-edge broad phase via BVH
*
* -
TREE_2_EDGE_AND_VERTEX_
BVH: edge-edge
via edge BVH, point-edge via vertex BVH
* - BVH
_*
: edge-edge
and point-edge broad phase via BVH with the named split strategy
*
*
* @author Baris Numanoglu
* @author Baris Numanoglu
*/
*/
...
@@ -89,8 +90,33 @@ public class RingSelfIntCheck extends Check {
...
@@ -89,8 +90,33 @@ public class RingSelfIntCheck extends Check {
public
enum
Variant
{
public
enum
Variant
{
OLD
,
OLD
,
TREE_1_EDGE_BVH
,
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
TREE_2_EDGE_AND_VERTEX_BVH
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
BVH_BINARY_SPATIAL_MEDIAN
(
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
),
BVH_OCTONARY_OBJECT_MEDIAN
(
SplitStrategy
.
OCTONARY_OBJECT_MEDIAN
),
BVH_OCTONARY_OBJECT_MEAN
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
),
BVH_OCTONARY_SPATIAL_MEDIAN
(
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
);
private
final
SplitStrategy
splitStrategy
;
Variant
()
{
this
.
splitStrategy
=
null
;
}
Variant
(
SplitStrategy
splitStrategy
)
{
this
.
splitStrategy
=
splitStrategy
;
}
public
boolean
isBvh
()
{
return
splitStrategy
!=
null
;
}
public
SplitStrategy
getSplitStrategy
()
{
if
(
splitStrategy
==
null
)
{
throw
new
IllegalStateException
(
"Variant "
+
this
+
" has no BVH split strategy."
);
}
return
splitStrategy
;
}
}
}
private
Variant
variant
=
Variant
.
OLD
;
private
Variant
variant
=
Variant
.
OLD
;
...
@@ -123,20 +149,11 @@ public class RingSelfIntCheck extends Check {
...
@@ -123,20 +149,11 @@ public class RingSelfIntCheck extends Check {
@Override
@Override
public
void
check
(
LinearRing
lr
)
{
public
void
check
(
LinearRing
lr
)
{
switch
(
variant
)
{
if
(
variant
==
Variant
.
OLD
)
{
case
OLD:
checkRingOld
(
lr
);
break
;
case
TREE_1_EDGE_BVH:
checkRingTree1
(
lr
);
break
;
case
TREE_2_EDGE_AND_VERTEX_BVH:
checkRingTree2
(
lr
);
break
;
default
:
checkRingOld
(
lr
);
checkRingOld
(
lr
);
b
re
ak
;
re
turn
;
}
}
checkRingBvh
(
lr
,
variant
.
getSplitStrategy
());
}
}
/**
/**
...
@@ -184,75 +201,11 @@ public class RingSelfIntCheck extends Check {
...
@@ -184,75 +201,11 @@ public class RingSelfIntCheck extends Check {
}
}
/**
/**
* TREE_1:
* BVH:
* - point-edge remains old
* - edge-edge uses Edge-BVH
*/
private
void
checkRingTree1
(
LinearRing
lr
)
{
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
if
(
checkEigenvalues
(
lr
,
vertices
,
ed
))
{
return
;
}
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
for
(
Edge
e
:
edges
)
{
if
(
checkForPointsTouchingEdgeOld
(
lr
,
e
))
{
return
;
}
}
BoundingVolumeHierarchyTree
<
Edge
>
edgeTree
=
BoundingVolumeHierarchyTree
.
newBinary
(
edges
,
e
->
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
));
for
(
int
i
=
0
;
i
<
edges
.
size
();
i
++)
{
Edge
e1
=
edges
.
get
(
i
);
AABB
q
=
AABB
.
of
(
e1
.
getFrom
(),
e1
.
getTo
(),
epsilon
);
List
<
Edge
>
candidates
=
edgeTree
.
getAllIntersectingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
continue
;
}
Segment3d
s1
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
for
(
Edge
e2
:
candidates
)
{
if
(
e1
==
e2
)
{
continue
;
}
if
(
e1
.
getConnectionPoint
(
e2
)
!=
null
)
{
continue
;
}
// avoid double pairwise checks
int
j
=
edges
.
indexOf
(
e2
);
if
(
j
<=
i
)
{
continue
;
}
Segment3d
s2
=
new
Segment3d
(
e2
.
getFrom
(),
e2
.
getTo
());
DistanceResult
dr
=
s1
.
getDistanceResult
(
s2
);
if
(
dr
.
distance
()
<
epsilon
)
{
CheckError
err
=
new
RingEdgeIntersectionError
(
lr
,
e1
,
e2
,
dr
.
point1
());
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
lr
.
addCheckResult
(
cr
);
return
;
}
}
}
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
lr
.
addCheckResult
(
cr
);
}
/**
* TREE_2:
* - point-edge uses Vertex-BVH
* - point-edge uses Vertex-BVH
* - edge-edge uses Edge-BVH
* - edge-edge uses Edge-BVH
*/
*/
private
void
checkRing
Tree2
(
LinearRing
lr
)
{
private
void
checkRing
Bvh
(
LinearRing
lr
,
SplitStrategy
splitStrategy
)
{
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
...
@@ -263,7 +216,7 @@ public class RingSelfIntCheck extends Check {
...
@@ -263,7 +216,7 @@ public class RingSelfIntCheck extends Check {
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
BoundingVolumeHierarchyTree
<
Vertex
>
vertexTree
=
BoundingVolumeHierarchyTree
<
Vertex
>
vertexTree
=
BoundingVolumeHierarchyTree
.
new
Binar
y
(
vertices
,
v
->
AABB
.
of
(
v
,
epsilon
));
BoundingVolumeHierarchyTree
.
new
WithStrateg
y
(
vertices
,
v
->
AABB
.
of
(
v
,
epsilon
)
,
splitStrategy
);
for
(
Edge
e
:
edges
)
{
for
(
Edge
e
:
edges
)
{
if
(
checkForPointsTouchingEdgeTree
(
lr
,
e
,
vertexTree
))
{
if
(
checkForPointsTouchingEdgeTree
(
lr
,
e
,
vertexTree
))
{
...
@@ -271,8 +224,8 @@ public class RingSelfIntCheck extends Check {
...
@@ -271,8 +224,8 @@ public class RingSelfIntCheck extends Check {
}
}
}
}
BoundingVolumeHierarchyTree
<
Edge
>
edgeTree
=
BoundingVolumeHierarchyTree
.
new
Binar
y
(
BoundingVolumeHierarchyTree
<
Edge
>
edgeTree
=
BoundingVolumeHierarchyTree
.
new
WithStrateg
y
(
edges
,
e
->
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
));
edges
,
e
->
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
)
,
splitStrategy
);
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
);
...
@@ -421,4 +374,4 @@ public class RingSelfIntCheck extends Check {
...
@@ -421,4 +374,4 @@ public class RingSelfIntCheck extends Check {
public
CheckId
getCheckId
()
{
public
CheckId
getCheckId
()
{
return
CheckId
.
C_GE_R_SELF_INTERSECTION
;
return
CheckId
.
C_GE_R_SELF_INTERSECTION
;
}
}
}
}
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/SelfIntersectionUtil.java
View file @
e6f4979d
...
@@ -141,7 +141,7 @@ public class SelfIntersectionUtil {
...
@@ -141,7 +141,7 @@ public class SelfIntersectionUtil {
indices
.
add
(
i
);
indices
.
add
(
i
);
}
}
treeConfig
.
elements
(
indices
).
f
unction
(
index
->
AABB
.
of
(
tesselatedPolygons
.
get
(
index
).
getOriginal
()));
treeConfig
.
elements
(
indices
).
aabbF
unction
(
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
=
treeConfig
.
build
();
BoundingVolumeHierarchyTree
<
Integer
>
tree
=
treeConfig
.
build
();
...
@@ -184,7 +184,7 @@ public class SelfIntersectionUtil {
...
@@ -184,7 +184,7 @@ public class SelfIntersectionUtil {
return
calculateSolidSelfIntersectionWithTree
(
return
calculateSolidSelfIntersectionWithTree
(
g
,
g
,
delta
,
delta
,
new
BoundingVolumeHierarchyTree
.
Builder
<
Integer
>
().
binary
Default
()
BoundingVolumeHierarchyTree
.<
Integer
>
binary
Builder
()
);
);
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/GeometryChecksWithAABBTest.java
View file @
e6f4979d
...
@@ -135,7 +135,6 @@ public class GeometryChecksWithAABBTest {
...
@@ -135,7 +135,6 @@ public class GeometryChecksWithAABBTest {
));
));
p2
.
setExteriorRing
(
r2
);
p2
.
setExteriorRing
(
r2
);
// TODO Check Orientation<-OUTWARD again
Geometry
solid
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Geometry
.
Orientation
.
OUTWARD
);
Geometry
solid
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Geometry
.
Orientation
.
OUTWARD
);
solid
.
getPolygons
().
addAll
(
List
.
of
(
p1
,
p2
));
solid
.
getPolygons
().
addAll
(
List
.
of
(
p1
,
p2
));
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/NestedRingCheckBvhVariantCityGmlTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.IdentityHashMap
;
import
java.util.List
;
import
java.util.Set
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.ValidationConfiguration
;
import
de.hft.stuttgart.citydoctor2.check.error.NestedRingError
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParseException
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
public
class
NestedRingCheckBvhVariantCityGmlTest
{
private
static
final
String
ST_PETRUS_GML
=
"Extensions/CityDoctorHealer/src/test/resources/bht/LoD2_32_344_5675_1_NW.gml"
;
@Test
public
void
bvhVariantsMatchOriginalOnStPetrusChurch
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
List
<
Polygon
>
originalPolygons
=
parsePolygons
(
resolveStPetrusPath
());
assertFalse
(
"Expected polygons from St. Petrus test model"
,
originalPolygons
.
isEmpty
());
List
<
CheckSummary
>
originalSummaries
=
runCheck
(
originalPolygons
,
NestedRingsCheck
.
Variant
.
OLD
);
for
(
NestedRingsCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
List
<
CheckSummary
>
bvhSummaries
=
runCheck
(
originalPolygons
,
bvhVariant
);
assertEquals
(
"Different polygon count for OLD/"
+
bvhVariant
,
originalSummaries
.
size
(),
bvhSummaries
.
size
());
for
(
int
i
=
0
;
i
<
originalSummaries
.
size
();
i
++)
{
CheckSummary
original
=
originalSummaries
.
get
(
i
);
CheckSummary
bvh
=
bvhSummaries
.
get
(
i
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" status differs for polygon "
+
i
,
original
.
status
,
bvh
.
status
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" error type differs for polygon "
+
i
,
original
.
errorType
,
bvh
.
errorType
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" nested ring pair differs for polygon "
+
i
,
original
.
nestedPairKey
,
bvh
.
nestedPairKey
);
}
}
}
private
static
List
<
CheckSummary
>
runCheck
(
List
<
Polygon
>
polygons
,
NestedRingsCheck
.
Variant
variant
)
{
List
<
CheckSummary
>
summaries
=
new
ArrayList
<>(
polygons
.
size
());
for
(
Polygon
polygon
:
polygons
)
{
NestedRingsCheck
check
=
new
NestedRingsCheck
(
variant
);
check
.
check
(
polygon
);
CheckResult
result
=
polygon
.
getCheckResult
(
check
);
assertNotNull
(
"Expected NestedRingsCheck result for "
+
variant
,
result
);
summaries
.
add
(
summaryFromResult
(
polygon
,
result
));
}
return
summaries
;
}
private
static
CheckSummary
summaryFromResult
(
Polygon
polygon
,
CheckResult
result
)
{
CheckError
error
=
result
.
getError
();
Class
<?>
errorType
=
error
==
null
?
null
:
error
.
getClass
();
String
nestedPairKey
=
null
;
if
(
error
instanceof
NestedRingError
)
{
NestedRingError
nestedRingError
=
(
NestedRingError
)
error
;
nestedPairKey
=
nestedPairKey
(
polygon
,
nestedRingError
.
getInnerRing
(),
nestedRingError
.
getWithinRing
());
}
return
new
CheckSummary
(
result
.
getResultStatus
(),
errorType
,
nestedPairKey
);
}
private
static
String
nestedPairKey
(
Polygon
polygon
,
LinearRing
outerRing
,
LinearRing
innerRing
)
{
return
ringIndex
(
polygon
,
outerRing
)
+
"|"
+
ringIndex
(
polygon
,
innerRing
);
}
private
static
int
ringIndex
(
Polygon
polygon
,
LinearRing
ring
)
{
List
<
LinearRing
>
innerRings
=
polygon
.
getInnerRings
();
for
(
int
i
=
0
;
i
<
innerRings
.
size
();
i
++)
{
if
(
innerRings
.
get
(
i
)
==
ring
)
{
return
i
;
}
}
throw
new
AssertionError
(
"Nested-ring result references a ring outside the tested polygon."
);
}
private
static
List
<
Polygon
>
parsePolygons
(
String
gmlPath
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
CityDoctorModel
model
=
CityGmlParser
.
parseCityGmlFile
(
gmlPath
,
config
.
getParserConfiguration
());
List
<
Polygon
>
polygons
=
new
ArrayList
<>();
Set
<
Polygon
>
seenPolygons
=
Collections
.
newSetFromMap
(
new
IdentityHashMap
<>());
model
.
getBuildings
()
.
filter
(
building
->
building
!=
null
)
.
forEach
(
building
->
collectBuildingPolygons
(
building
,
polygons
,
seenPolygons
));
return
polygons
;
}
private
static
void
collectBuildingPolygons
(
Building
building
,
List
<
Polygon
>
polygons
,
Set
<
Polygon
>
seenPolygons
)
{
for
(
Geometry
geometry
:
building
.
getGeometries
())
{
collectGeometryPolygons
(
geometry
,
polygons
,
seenPolygons
);
}
building
.
getBuildingParts
().
forEach
(
part
->
{
if
(
part
==
null
)
{
return
;
}
for
(
Geometry
geometry
:
part
.
getGeometries
())
{
collectGeometryPolygons
(
geometry
,
polygons
,
seenPolygons
);
}
});
}
private
static
void
collectGeometryPolygons
(
Geometry
geometry
,
List
<
Polygon
>
polygons
,
Set
<
Polygon
>
seenPolygons
)
{
if
(
geometry
==
null
)
{
return
;
}
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
if
(
polygon
!=
null
&&
seenPolygons
.
add
(
polygon
))
{
polygons
.
add
(
polygon
);
}
}
}
private
static
String
resolveStPetrusPath
()
{
Path
parentRelative
=
Path
.
of
(
ST_PETRUS_GML
);
if
(
Files
.
exists
(
parentRelative
))
{
return
parentRelative
.
toString
();
}
Path
moduleRelative
=
Path
.
of
(
".."
,
ST_PETRUS_GML
);
if
(
Files
.
exists
(
moduleRelative
))
{
return
moduleRelative
.
toString
();
}
throw
new
AssertionError
(
"Could not find St. Petrus CityGML test model at "
+
ST_PETRUS_GML
);
}
private
static
NestedRingsCheck
.
Variant
[]
bvhVariants
()
{
return
new
NestedRingsCheck
.
Variant
[]
{
NestedRingsCheck
.
Variant
.
BVH_BINARY_OBJECT_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_BINARY_OBJECT_MEAN
,
NestedRingsCheck
.
Variant
.
BVH_BINARY_SPATIAL_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_SPATIAL_MEDIAN
};
}
private
static
final
class
CheckSummary
{
final
ResultStatus
status
;
final
Class
<?>
errorType
;
final
String
nestedPairKey
;
CheckSummary
(
ResultStatus
status
,
Class
<?>
errorType
,
String
nestedPairKey
)
{
this
.
status
=
status
;
this
.
errorType
=
errorType
;
this
.
nestedPairKey
=
nestedPairKey
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/NestedRingCheckBvhVariantSyntheticTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.NestedRingError
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
public
class
NestedRingCheckBvhVariantSyntheticTest
{
@Test
public
void
bvhVariantsMatchOriginalOnComplexSyntheticGeometry
()
{
Geometry
originalGeometry
=
SyntheticNestedRingGeometryFactory
.
complexNestedRingGeometry
();
List
<
Polygon
>
originalPolygons
=
originalGeometry
.
getPolygons
();
assertFalse
(
"Expected synthetic nested-ring polygons"
,
originalPolygons
.
isEmpty
());
List
<
CheckSummary
>
originalSummaries
=
runCheck
(
originalPolygons
,
NestedRingsCheck
.
Variant
.
OLD
);
assertEquals
(
"Synthetic fixture should contain three OK polygons"
,
3
,
countStatus
(
originalSummaries
,
ResultStatus
.
OK
));
assertEquals
(
"Synthetic fixture should contain three nested-ring errors"
,
3
,
countStatus
(
originalSummaries
,
ResultStatus
.
ERROR
));
for
(
NestedRingsCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
Geometry
bvhGeometry
=
SyntheticNestedRingGeometryFactory
.
complexNestedRingGeometry
();
List
<
Polygon
>
bvhPolygons
=
bvhGeometry
.
getPolygons
();
List
<
CheckSummary
>
bvhSummaries
=
runCheck
(
bvhPolygons
,
bvhVariant
);
assertEquals
(
"Different synthetic polygon count for OLD/"
+
bvhVariant
,
originalSummaries
.
size
(),
bvhSummaries
.
size
());
for
(
int
i
=
0
;
i
<
originalSummaries
.
size
();
i
++)
{
CheckSummary
original
=
originalSummaries
.
get
(
i
);
CheckSummary
bvh
=
bvhSummaries
.
get
(
i
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" status differs for synthetic polygon "
+
i
,
original
.
status
,
bvh
.
status
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" error type differs for synthetic polygon "
+
i
,
original
.
errorType
,
bvh
.
errorType
);
if
(
original
.
hasSingleNestedPair
)
{
assertEquals
(
"OLD vs "
+
bvhVariant
+
" nested ring pair differs for synthetic polygon "
+
i
,
original
.
nestedPairKey
,
bvh
.
nestedPairKey
);
}
}
}
}
private
static
List
<
CheckSummary
>
runCheck
(
List
<
Polygon
>
polygons
,
NestedRingsCheck
.
Variant
variant
)
{
List
<
CheckSummary
>
summaries
=
new
ArrayList
<>(
polygons
.
size
());
for
(
Polygon
polygon
:
polygons
)
{
NestedRingsCheck
check
=
new
NestedRingsCheck
(
variant
);
check
.
check
(
polygon
);
CheckResult
result
=
polygon
.
getCheckResult
(
check
);
assertNotNull
(
"Expected NestedRingsCheck result for "
+
variant
,
result
);
summaries
.
add
(
summaryFromResult
(
polygon
,
result
));
}
return
summaries
;
}
private
static
int
countStatus
(
List
<
CheckSummary
>
summaries
,
ResultStatus
status
)
{
int
count
=
0
;
for
(
CheckSummary
summary
:
summaries
)
{
if
(
summary
.
status
==
status
)
{
count
++;
}
}
return
count
;
}
private
static
CheckSummary
summaryFromResult
(
Polygon
polygon
,
CheckResult
result
)
{
CheckError
error
=
result
.
getError
();
Class
<?>
errorType
=
error
==
null
?
null
:
error
.
getClass
();
String
nestedPairKey
=
null
;
if
(
error
instanceof
NestedRingError
)
{
NestedRingError
nestedRingError
=
(
NestedRingError
)
error
;
nestedPairKey
=
nestedPairKey
(
polygon
,
nestedRingError
.
getInnerRing
(),
nestedRingError
.
getWithinRing
());
}
return
new
CheckSummary
(
result
.
getResultStatus
(),
errorType
,
nestedPairKey
,
countNestedPairs
(
polygon
)
==
1
);
}
private
static
int
countNestedPairs
(
Polygon
polygon
)
{
int
count
=
0
;
List
<
LinearRing
>
innerRings
=
polygon
.
getInnerRings
();
for
(
LinearRing
outerRing
:
innerRings
)
{
for
(
LinearRing
innerRing
:
innerRings
)
{
if
(
outerRing
==
innerRing
)
{
continue
;
}
if
(
areAllPointsInside
(
outerRing
,
innerRing
))
{
count
++;
}
}
}
return
count
;
}
private
static
boolean
areAllPointsInside
(
LinearRing
outerRing
,
LinearRing
innerRing
)
{
for
(
Vertex
vertex
:
innerRing
.
getVertices
())
{
if
(!
outerRing
.
isPointInside
(
vertex
))
{
return
false
;
}
}
return
true
;
}
private
static
String
nestedPairKey
(
Polygon
polygon
,
LinearRing
outerRing
,
LinearRing
innerRing
)
{
return
ringIndex
(
polygon
,
outerRing
)
+
"|"
+
ringIndex
(
polygon
,
innerRing
);
}
private
static
int
ringIndex
(
Polygon
polygon
,
LinearRing
ring
)
{
List
<
LinearRing
>
innerRings
=
polygon
.
getInnerRings
();
for
(
int
i
=
0
;
i
<
innerRings
.
size
();
i
++)
{
if
(
innerRings
.
get
(
i
)
==
ring
)
{
return
i
;
}
}
throw
new
AssertionError
(
"Nested-ring result references a ring outside the tested polygon."
);
}
private
static
NestedRingsCheck
.
Variant
[]
bvhVariants
()
{
return
new
NestedRingsCheck
.
Variant
[]
{
NestedRingsCheck
.
Variant
.
BVH_BINARY_OBJECT_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_BINARY_OBJECT_MEAN
,
NestedRingsCheck
.
Variant
.
BVH_BINARY_SPATIAL_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEDIAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEAN
,
NestedRingsCheck
.
Variant
.
BVH_OCTONARY_SPATIAL_MEDIAN
};
}
private
static
final
class
CheckSummary
{
final
ResultStatus
status
;
final
Class
<?>
errorType
;
final
String
nestedPairKey
;
final
boolean
hasSingleNestedPair
;
CheckSummary
(
ResultStatus
status
,
Class
<?>
errorType
,
String
nestedPairKey
,
boolean
hasSingleNestedPair
)
{
this
.
status
=
status
;
this
.
errorType
=
errorType
;
this
.
nestedPairKey
=
nestedPairKey
;
this
.
hasSingleNestedPair
=
hasSingleNestedPair
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/RingSelfIntCheckBvhVariantCityGmlTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.ValidationConfiguration
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.RingSelfIntCheck
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParseException
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
/**
* Compares RingSelfIntCheck variants on the same input model.
*
* Compared variants:
* - OLD
* - six concrete BVH split-strategy variants
*/
public
class
RingSelfIntCheckBvhVariantCityGmlTest
{
private
static
final
String
TEST_GML
=
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml"
;
private
static
final
double
EPSILON
=
0.001
;
@Test
public
void
oldVsBvhVariants_sameErrorCounts
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
long
start
=
System
.
nanoTime
();
int
oldCount
=
runCheckAndCountErrors
(
geometryOld
,
RingSelfIntCheck
.
Variant
.
OLD
);
long
oldTime
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"RingSelfIntCheck OLD count="
+
oldCount
+
" time(ns)="
+
oldTime
);
for
(
RingSelfIntCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
Geometry
geometryBvh
=
parseGeometry
(
TEST_GML
);
start
=
System
.
nanoTime
();
int
bvhCount
=
runCheckAndCountErrors
(
geometryBvh
,
bvhVariant
);
long
bvhTime
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"RingSelfIntCheck "
+
bvhVariant
+
" count="
+
bvhCount
+
" time(ns)="
+
bvhTime
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" differs"
,
oldCount
,
bvhCount
);
}
}
@Test
public
void
perRingResultsMatch_oldVsBvhVariants
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
List
<
LinearRing
>
oldRings
=
collectRings
(
geometryOld
);
List
<
CheckSummary
>
oldSummaries
=
new
ArrayList
<>();
for
(
LinearRing
oldRing
:
oldRings
)
{
oldSummaries
.
add
(
runCheck
(
oldRing
,
RingSelfIntCheck
.
Variant
.
OLD
));
}
for
(
RingSelfIntCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
Geometry
geometryBvh
=
parseGeometry
(
TEST_GML
);
List
<
LinearRing
>
bvhRings
=
collectRings
(
geometryBvh
);
assertEquals
(
"Different number of rings in old/"
+
bvhVariant
+
" geometry"
,
oldRings
.
size
(),
bvhRings
.
size
());
for
(
int
i
=
0
;
i
<
bvhRings
.
size
();
i
++)
{
CheckSummary
oldSummary
=
oldSummaries
.
get
(
i
);
CheckSummary
bvhSummary
=
runCheck
(
bvhRings
.
get
(
i
),
bvhVariant
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" status differs for ring index "
+
i
,
oldSummary
.
status
,
bvhSummary
.
status
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" error type differs for ring index "
+
i
,
oldSummary
.
errorType
,
bvhSummary
.
errorType
);
}
}
}
private
CheckSummary
runCheck
(
LinearRing
ring
,
RingSelfIntCheck
.
Variant
variant
)
{
RingSelfIntCheck
check
=
createCheck
(
variant
);
check
.
check
(
ring
);
CheckResult
result
=
ring
.
getCheckResult
(
check
);
assertNotNull
(
"CheckResult must not be null"
,
result
);
CheckError
error
=
result
.
getError
();
Class
<?>
errorType
=
error
==
null
?
null
:
error
.
getClass
();
return
new
CheckSummary
(
result
.
getResultStatus
(),
errorType
);
}
private
int
runCheckAndCountErrors
(
Geometry
geometry
,
RingSelfIntCheck
.
Variant
variant
)
{
int
count
=
0
;
for
(
LinearRing
ring
:
collectRings
(
geometry
))
{
RingSelfIntCheck
check
=
createCheck
(
variant
);
check
.
check
(
ring
);
CheckResult
result
=
ring
.
getCheckResult
(
check
);
assertNotNull
(
"CheckResult must not be null"
,
result
);
if
(
result
.
getResultStatus
()
==
ResultStatus
.
ERROR
)
{
count
++;
}
}
return
count
;
}
private
RingSelfIntCheck
createCheck
(
RingSelfIntCheck
.
Variant
variant
)
{
RingSelfIntCheck
check
=
new
RingSelfIntCheck
(
variant
);
check
.
init
(
Collections
.
singletonMap
(
"minVertexDistance"
,
String
.
valueOf
(
EPSILON
)),
null
);
return
check
;
}
private
RingSelfIntCheck
.
Variant
[]
bvhVariants
()
{
return
new
RingSelfIntCheck
.
Variant
[]
{
RingSelfIntCheck
.
Variant
.
BVH_BINARY_OBJECT_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_BINARY_OBJECT_MEAN
,
RingSelfIntCheck
.
Variant
.
BVH_BINARY_SPATIAL_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_SPATIAL_MEDIAN
};
}
private
List
<
LinearRing
>
collectRings
(
Geometry
geometry
)
{
assertNotNull
(
"geometry must not be null"
,
geometry
);
List
<
LinearRing
>
rings
=
new
ArrayList
<>();
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
if
(
polygon
.
getExteriorRing
()
!=
null
)
{
rings
.
add
(
polygon
.
getExteriorRing
());
}
rings
.
addAll
(
polygon
.
getInnerRings
());
}
return
rings
;
}
private
Geometry
parseGeometry
(
String
gmlPath
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
CityDoctorModel
model
=
CityGmlParser
.
parseCityGmlFile
(
gmlPath
,
config
.
getParserConfiguration
());
Building
building
=
model
.
getBuildings
().
findFirst
().
orElseThrow
();
Geometry
geometry
=
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
);
assertNotNull
(
"Expected SOLID LOD2 geometry in test model: "
+
gmlPath
,
geometry
);
return
geometry
;
}
private
static
final
class
CheckSummary
{
final
ResultStatus
status
;
final
Class
<?>
errorType
;
CheckSummary
(
ResultStatus
status
,
Class
<?>
errorType
)
{
this
.
status
=
status
;
this
.
errorType
=
errorType
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/RingSelfIntCheckBvhVariantSyntheticTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.RingSelfIntCheck
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
public
class
RingSelfIntCheckBvhVariantSyntheticTest
{
private
static
final
double
EPSILON
=
0.001
;
@Test
public
void
bvhVariantsMatchOriginalOnSyntheticRings
()
{
Geometry
oldGeometry
=
syntheticRingGeometry
();
List
<
LinearRing
>
oldRings
=
collectExteriorRings
(
oldGeometry
);
List
<
CheckSummary
>
oldSummaries
=
new
ArrayList
<>();
for
(
LinearRing
oldRing
:
oldRings
)
{
oldSummaries
.
add
(
runCheck
(
oldRing
,
RingSelfIntCheck
.
Variant
.
OLD
));
}
for
(
RingSelfIntCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
Geometry
bvhGeometry
=
syntheticRingGeometry
();
List
<
LinearRing
>
bvhRings
=
collectExteriorRings
(
bvhGeometry
);
assertEquals
(
oldRings
.
size
(),
bvhRings
.
size
());
for
(
int
i
=
0
;
i
<
bvhRings
.
size
();
i
++)
{
CheckSummary
oldSummary
=
oldSummaries
.
get
(
i
);
CheckSummary
bvhSummary
=
runCheck
(
bvhRings
.
get
(
i
),
bvhVariant
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" status differs for synthetic ring "
+
i
,
oldSummary
.
status
,
bvhSummary
.
status
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" error type differs for synthetic ring "
+
i
,
oldSummary
.
errorType
,
bvhSummary
.
errorType
);
}
}
}
private
static
RingSelfIntCheck
.
Variant
[]
bvhVariants
()
{
return
new
RingSelfIntCheck
.
Variant
[]
{
RingSelfIntCheck
.
Variant
.
BVH_BINARY_OBJECT_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_BINARY_OBJECT_MEAN
,
RingSelfIntCheck
.
Variant
.
BVH_BINARY_SPATIAL_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEDIAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_OBJECT_MEAN
,
RingSelfIntCheck
.
Variant
.
BVH_OCTONARY_SPATIAL_MEDIAN
};
}
private
static
CheckSummary
runCheck
(
LinearRing
ring
,
RingSelfIntCheck
.
Variant
variant
)
{
RingSelfIntCheck
check
=
new
RingSelfIntCheck
(
variant
);
check
.
init
(
Collections
.
singletonMap
(
"minVertexDistance"
,
String
.
valueOf
(
EPSILON
)),
null
);
check
.
check
(
ring
);
CheckResult
result
=
ring
.
getCheckResult
(
check
);
assertNotNull
(
"Expected CheckResult for "
+
variant
,
result
);
CheckError
error
=
result
.
getError
();
Class
<?>
errorType
=
error
==
null
?
null
:
error
.
getClass
();
return
new
CheckSummary
(
result
.
getResultStatus
(),
errorType
);
}
private
static
Geometry
syntheticRingGeometry
()
{
Geometry
geometry
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Orientation
.
OUTWARD
);
addRingPolygon
(
geometry
,
rectangle
());
addRingPolygon
(
geometry
,
bowTie
());
addRingPolygon
(
geometry
,
pointNearEdge
());
addRingPolygon
(
geometry
,
largeConvexRing
(
80
,
20.0
,
40.0
,
0.0
));
addRingPolygon
(
geometry
,
zigZagCorridor
(
30
));
geometry
.
updateEdgesAndVertices
();
return
geometry
;
}
private
static
List
<
LinearRing
>
collectExteriorRings
(
Geometry
geometry
)
{
List
<
LinearRing
>
rings
=
new
ArrayList
<>();
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
rings
.
add
(
polygon
.
getExteriorRing
());
}
return
rings
;
}
private
static
void
addRingPolygon
(
Geometry
geometry
,
double
[][]
coordinates
)
{
ConcretePolygon
polygon
=
new
ConcretePolygon
();
LinearRing
ring
=
new
LinearRing
(
LinearRingType
.
EXTERIOR
);
polygon
.
setExteriorRing
(
ring
);
geometry
.
addPolygon
(
polygon
);
Vertex
firstVertex
=
null
;
for
(
int
i
=
0
;
i
<
coordinates
.
length
;
i
++)
{
double
[]
coordinate
=
coordinates
[
i
];
if
(
i
==
coordinates
.
length
-
1
&&
sameCoordinate
(
coordinate
,
coordinates
[
0
]))
{
ring
.
addVertex
(
firstVertex
);
continue
;
}
Vertex
vertex
=
new
Vertex
(
coordinate
[
0
],
coordinate
[
1
],
coordinate
[
2
]);
if
(
i
==
0
)
{
firstVertex
=
vertex
;
}
ring
.
addVertex
(
vertex
);
}
}
private
static
boolean
sameCoordinate
(
double
[]
a
,
double
[]
b
)
{
return
Double
.
compare
(
a
[
0
],
b
[
0
])
==
0
&&
Double
.
compare
(
a
[
1
],
b
[
1
])
==
0
&&
Double
.
compare
(
a
[
2
],
b
[
2
])
==
0
;
}
private
static
double
[][]
rectangle
()
{
return
new
double
[][]
{
{
0.0
,
0.0
,
0.0
},
{
10.0
,
0.0
,
0.0
},
{
10.0
,
10.0
,
0.0
},
{
0.0
,
10.0
,
0.0
},
{
0.0
,
0.0
,
0.0
}
};
}
private
static
double
[][]
bowTie
()
{
return
new
double
[][]
{
{
20.0
,
0.0
,
0.0
},
{
30.0
,
10.0
,
0.0
},
{
30.0
,
0.0
,
0.0
},
{
20.0
,
10.0
,
0.0
},
{
20.0
,
0.0
,
0.0
}
};
}
private
static
double
[][]
pointNearEdge
()
{
return
new
double
[][]
{
{
40.0
,
0.0
,
0.0
},
{
50.0
,
0.0
,
0.0
},
{
50.0
,
10.0
,
0.0
},
{
45.0
,
EPSILON
*
0.5
,
0.0
},
{
40.0
,
10.0
,
0.0
},
{
40.0
,
0.0
,
0.0
}
};
}
private
static
double
[][]
largeConvexRing
(
int
vertexCount
,
double
centerX
,
double
centerY
,
double
z
)
{
double
[][]
coordinates
=
new
double
[
vertexCount
+
1
][
3
];
for
(
int
i
=
0
;
i
<
vertexCount
;
i
++)
{
double
angle
=
2.0
*
Math
.
PI
*
i
/
vertexCount
;
coordinates
[
i
][
0
]
=
centerX
+
Math
.
cos
(
angle
)
*
8.0
;
coordinates
[
i
][
1
]
=
centerY
+
Math
.
sin
(
angle
)
*
5.0
;
coordinates
[
i
][
2
]
=
z
;
}
coordinates
[
vertexCount
][
0
]
=
coordinates
[
0
][
0
];
coordinates
[
vertexCount
][
1
]
=
coordinates
[
0
][
1
];
coordinates
[
vertexCount
][
2
]
=
coordinates
[
0
][
2
];
return
coordinates
;
}
private
static
double
[][]
zigZagCorridor
(
int
segments
)
{
double
[][]
coordinates
=
new
double
[(
segments
*
2
)
+
3
][
3
];
int
index
=
0
;
for
(
int
i
=
0
;
i
<=
segments
;
i
++)
{
coordinates
[
index
++]
=
new
double
[]
{
60.0
+
i
,
i
%
2
==
0
?
0.0
:
1.0
,
0.0
};
}
for
(
int
i
=
segments
;
i
>=
0
;
i
--)
{
coordinates
[
index
++]
=
new
double
[]
{
60.0
+
i
,
i
%
2
==
0
?
4.0
:
5.0
,
0.0
};
}
coordinates
[
index
]
=
new
double
[]
{
60.0
,
0.0
,
0.0
};
return
coordinates
;
}
private
static
final
class
CheckSummary
{
final
ResultStatus
status
;
final
Class
<?>
errorType
;
CheckSummary
(
ResultStatus
status
,
Class
<?>
errorType
)
{
this
.
status
=
status
;
this
.
errorType
=
errorType
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/SolidSelfIntersectionBVHUtilTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.io.File
;
import
java.util.List
;
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.database.UnconnectedCache
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException
;
import
de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParseException
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
import
de.hft.stuttgart.citydoctor2.utils.PolygonIntersection
;
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
public
void
testBVHCalculateOnKnownGoodModel
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
CityDoctorModel
m
=
CityGmlParser
.
parseCityGmlFile
(
"src/test/resources/SolidSelfIntTest1.gml"
,
config
.
getParserConfiguration
()
);
Building
building
=
m
.
getBuildings
().
findFirst
().
orElseThrow
();
Geometry
g
=
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
);
assertNotNull
(
"Expected SOLID LOD2 geometry in test model"
,
g
);
List
<
Polygon
>
polys
=
g
.
getPolygons
();
assertNotNull
(
polys
);
assertTrue
(
"Expected at least 2 polygons"
,
polys
.
size
()
>
1
);
BoundingVolumeHierarchyTree
<
Polygon
>
tree
=
BoundingVolumeHierarchyTree
.
newBinary
(
polys
,
p
->
AABB
.
of
(
p
.
getOriginal
()));
double
delta
=
0.001
;
// calls new method with trees
List
<
PolygonIntersection
>
intersections
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
,
tree
);
// This file is a good example (no self intersection)
assertTrue
(
"No self-intersections expected for SolidSelfIntTest1.gml"
,
intersections
.
isEmpty
());
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/bht/SolidSelfIntersectionBuildingTest.java
0 → 100644
View file @
e6f4979d
package
de.hft.stuttgart.citydoctor2.checks.bht
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.utils.PolygonIntersection
;
public
class
SolidSelfIntersectionBuildingTest
{
private
static
final
double
DELTA
=
0.001
;
private
static
final
int
MIN_EXPECTED_POLYGONS
=
60
;
@Test
public
void
testBvhBuildAndQueryOnLod2AndLod3
()
{
Building
building
=
createDenseBuildingWithoutIntersections
();
assertBvhBuildAndQuery
(
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
),
"LOD2"
);
assertBvhBuildAndQuery
(
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD3
),
"LOD3"
);
}
@Test
public
void
testOldVsNewSameResultCountOnIntersectingLod2AndLod3
()
{
Building
building
=
createDenseBuildingWithIntersections
();
assertOldVsNewComparison
(
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
),
"LOD2"
);
assertOldVsNewComparison
(
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD3
),
"LOD3"
);
}
private
void
assertBvhBuildAndQuery
(
Geometry
geometry
,
String
lodLabel
)
{
List
<
Polygon
>
polygons
=
requireValidGeometry
(
geometry
,
lodLabel
);
BoundingVolumeHierarchyTree
<
Polygon
>
tree
=
BoundingVolumeHierarchyTree
.
newBinary
(
polygons
,
p
->
AABB
.
of
(
p
.
getOriginal
()));
Polygon
probe
=
polygons
.
get
(
0
);
AABB
probeAabb
=
AABB
.
of
(
probe
.
getOriginal
());
assertNotNull
(
"Probe AABB must not be null for "
+
lodLabel
,
probeAabb
);
List
<
Polygon
>
candidates
=
tree
.
getAllIntersectingElements
(
probeAabb
);
int
nonEmptyQueries
=
0
;
int
totalCandidates
=
0
;
int
maxCandidates
=
0
;
for
(
Polygon
polygon
:
polygons
)
{
AABB
query
=
AABB
.
of
(
polygon
.
getOriginal
());
assertNotNull
(
"Query AABB must not be null for "
+
lodLabel
,
query
);
List
<
Polygon
>
perPolygonCandidates
=
tree
.
getAllIntersectingElements
(
query
);
if
(!
perPolygonCandidates
.
isEmpty
())
{
nonEmptyQueries
++;
}
int
currentSize
=
perPolygonCandidates
.
size
();
totalCandidates
+=
currentSize
;
if
(
currentSize
>
maxCandidates
)
{
maxCandidates
=
currentSize
;
}
}
printBvhStats
(
lodLabel
,
polygons
.
size
(),
candidates
.
size
(),
nonEmptyQueries
,
totalCandidates
,
maxCandidates
);
assertTrue
(
"Expected at least one non-empty BVH query on "
+
lodLabel
,
nonEmptyQueries
>
0
);
}
private
void
assertOldVsNewComparison
(
Geometry
geometry
,
String
lodLabel
)
{
List
<
Polygon
>
polygons
=
requireValidGeometry
(
geometry
,
lodLabel
);
List
<
PolygonIntersection
>
oldRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection0
(
geometry
,
DELTA
);
assertNotNull
(
"Old result list must not be null for "
+
lodLabel
,
oldRes
);
BoundingVolumeHierarchyTree
<
Polygon
>
externalTree
=
BoundingVolumeHierarchyTree
.
newBinary
(
polygons
,
p
->
AABB
.
of
(
p
.
getOriginal
()));
List
<
PolygonIntersection
>
oldTreeRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
geometry
,
DELTA
,
externalTree
);
assertNotNull
(
"Old+tree result list must not be null for "
+
lodLabel
,
oldTreeRes
);
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
geometry
,
DELTA
);
assertNotNull
(
"New result list must not be null for "
+
lodLabel
,
newRes
);
printComparisonStats
(
lodLabel
,
polygons
.
size
(),
oldRes
.
size
(),
oldTreeRes
.
size
(),
newRes
.
size
());
assertEquals
(
"Old vs external-tree differs for "
+
lodLabel
,
oldRes
.
size
(),
oldTreeRes
.
size
());
assertEquals
(
"Old vs new-tree differs for "
+
lodLabel
,
oldRes
.
size
(),
newRes
.
size
());
}
private
List
<
Polygon
>
requireValidGeometry
(
Geometry
geometry
,
String
lodLabel
)
{
assertNotNull
(
"Expected geometry for "
+
lodLabel
,
geometry
);
List
<
Polygon
>
polygons
=
geometry
.
getPolygons
();
assertNotNull
(
"Polygon list must not be null for "
+
lodLabel
,
polygons
);
assertFalse
(
"Polygon list must not be empty for "
+
lodLabel
,
polygons
.
isEmpty
());
assertTrue
(
"Expected many polygons for "
+
lodLabel
+
" (got "
+
polygons
.
size
()
+
")"
,
polygons
.
size
()
>=
MIN_EXPECTED_POLYGONS
);
return
polygons
;
}
private
void
printBvhStats
(
String
lodLabel
,
int
polygonCount
,
int
probeCandidateCount
,
int
nonEmptyQueries
,
int
totalCandidates
,
int
maxCandidates
)
{
System
.
out
.
printf
(
"[BVH][%s] polygons=%d, probeCandidates=%d, nonEmptyQueries=%d/%d, totalCandidates=%d, maxPerQuery=%d%n"
,
lodLabel
,
polygonCount
,
probeCandidateCount
,
nonEmptyQueries
,
polygonCount
,
totalCandidates
,
maxCandidates
);
}
private
void
printComparisonStats
(
String
lodLabel
,
int
polygonCount
,
int
oldCount
,
int
oldTreeCount
,
int
newCount
)
{
System
.
out
.
printf
(
"[SelfInt][%s] polygons=%d, old=%d, old+tree=%d, new=%d%n"
,
lodLabel
,
polygonCount
,
oldCount
,
oldTreeCount
,
newCount
);
}
private
Building
createDenseBuildingWithoutIntersections
()
{
Building
b
=
new
Building
();
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD2
,
4
,
4
,
false
));
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD3
,
6
,
6
,
false
));
return
b
;
}
private
Building
createDenseBuildingWithIntersections
()
{
Building
b
=
new
Building
();
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD2
,
4
,
4
,
true
));
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD3
,
6
,
6
,
true
));
return
b
;
}
private
Geometry
createGridGeometry
(
Lod
lod
,
int
xCount
,
int
yCount
,
boolean
addIntersections
)
{
Geometry
g
=
new
Geometry
(
GeometryType
.
SOLID
,
lod
,
Orientation
.
OUTWARD
);
double
spacing
=
6.0
;
double
width
=
4.0
;
double
depth
=
4.0
;
double
height
=
4.0
;
for
(
int
ix
=
0
;
ix
<
xCount
;
ix
++)
{
for
(
int
iy
=
0
;
iy
<
yCount
;
iy
++)
{
double
x
=
ix
*
spacing
;
double
y
=
iy
*
spacing
;
addBox
(
g
,
x
,
y
,
0.0
,
width
,
depth
,
height
);
}
}
// TODO Re-validate the Intersction-Idee
if
(
addIntersections
)
{
// Two additional boxes overlap each other and the grid neighborhood.
addBox
(
g
,
spacing
*
1.2
,
spacing
*
1.2
,
0.5
,
6.0
,
2.8
,
3.5
);
addBox
(
g
,
spacing
*
1.4
,
spacing
*
1.0
,
0.0
,
2.8
,
6.0
,
4.2
);
}
g
.
updateEdgesAndVertices
();
return
g
;
}
///----------------------------------- add geometric sub-entities ---------------------------///
private
void
addBox
(
Geometry
geometry
,
double
x
,
double
y
,
double
z
,
double
width
,
double
depth
,
double
height
)
{
Vertex
v000
=
new
Vertex
(
x
,
y
,
z
);
Vertex
v100
=
new
Vertex
(
x
+
width
,
y
,
z
);
Vertex
v110
=
new
Vertex
(
x
+
width
,
y
+
depth
,
z
);
Vertex
v010
=
new
Vertex
(
x
,
y
+
depth
,
z
);
Vertex
v001
=
new
Vertex
(
x
,
y
,
z
+
height
);
Vertex
v101
=
new
Vertex
(
x
+
width
,
y
,
z
+
height
);
Vertex
v111
=
new
Vertex
(
x
+
width
,
y
+
depth
,
z
+
height
);
Vertex
v011
=
new
Vertex
(
x
,
y
+
depth
,
z
+
height
);
addQuad
(
geometry
,
v000
,
v100
,
v110
,
v010
);
// bottom
addQuad
(
geometry
,
v001
,
v011
,
v111
,
v101
);
// top
addQuad
(
geometry
,
v000
,
v001
,
v101
,
v100
);
// front
addQuad
(
geometry
,
v100
,
v101
,
v111
,
v110
);
// right
addQuad
(
geometry
,
v110
,
v111
,
v011
,
v010
);
// back
addQuad
(
geometry
,
v010
,
v011
,
v001
,
v000
);
// left
}
private
void
addQuad
(
Geometry
geometry
,
Vertex
a
,
Vertex
b
,
Vertex
c
,
Vertex
d
)
{
ConcretePolygon
polygon
=
new
ConcretePolygon
();
LinearRing
ring
=
new
LinearRing
(
LinearRingType
.
EXTERIOR
);
polygon
.
setExteriorRing
(
ring
);
// ACHTUNG :Ensure polygon->geometry parent relation exists before adding vertices.
// LinearRing.addVertex() updates vertex adjacency via parent geometry.
geometry
.
addPolygon
(
polygon
);
ring
.
addVertex
(
a
);
ring
.
addVertex
(
b
);
ring
.
addVertex
(
c
);
ring
.
addVertex
(
d
);
ring
.
addVertex
(
a
);
}
}
Prev
1
2
Next
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