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
a7ed4b64
Commit
a7ed4b64
authored
Sep 07, 2026
by
Numanoglu
Browse files
Prepare mergeable AABB BVH variant
parent
1d9c7810
Pipeline
#12438
passed with stage
in 2 minutes and 11 seconds
Changes
28
Pipelines
1
Expand all
Show whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/aabb/BoundingVolumeHierarchyTree.java
View file @
a7ed4b64
...
@@ -10,6 +10,8 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -10,6 +10,8 @@ public class BoundingVolumeHierarchyTree<E> {
private
final
Node
<
E
>
root
;
private
final
Node
<
E
>
root
;
static
final
int
BINARY_TREE_DEGREE
=
2
;
static
final
int
OCTONARY_TREE_DEGREE
=
8
;
static
final
int
DEFAULT_MIN_DEPTH
=
4
;
static
final
int
DEFAULT_MIN_DEPTH
=
4
;
static
final
int
DEFAULT_MAX_DEPTH
=
32
;
static
final
int
DEFAULT_MAX_DEPTH
=
32
;
static
final
int
DEFAULT_BINARY_LEAF_SIZE
=
1
;
static
final
int
DEFAULT_BINARY_LEAF_SIZE
=
1
;
...
@@ -88,11 +90,11 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -88,11 +90,11 @@ public class BoundingVolumeHierarchyTree<E> {
case
BINARY_OBJECT_MEDIAN:
case
BINARY_OBJECT_MEDIAN:
case
BINARY_OBJECT_MEAN:
case
BINARY_OBJECT_MEAN:
case
BINARY_SPATIAL_MEDIAN:
case
BINARY_SPATIAL_MEDIAN:
return
2
;
return
BINARY_TREE_DEGREE
;
case
OCTONARY_OBJECT_MEDIAN:
case
OCTONARY_OBJECT_MEDIAN:
case
OCTONARY_OBJECT_MEAN:
case
OCTONARY_OBJECT_MEAN:
case
OCTONARY_SPATIAL_MEDIAN:
case
OCTONARY_SPATIAL_MEDIAN:
return
8
;
return
OCTONARY_TREE_DEGREE
;
case
AUTO:
case
AUTO:
default
:
default
:
throw
new
IllegalArgumentException
(
"AUTO does not define a concrete BVH variant."
);
throw
new
IllegalArgumentException
(
"AUTO does not define a concrete BVH variant."
);
...
@@ -100,7 +102,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -100,7 +102,7 @@ public class BoundingVolumeHierarchyTree<E> {
}
}
private
static
int
defaultMaxLeafSizeFor
(
SplitStrategy
splitStrategy
)
{
private
static
int
defaultMaxLeafSizeFor
(
SplitStrategy
splitStrategy
)
{
return
degreeFor
(
splitStrategy
)
==
2
return
degreeFor
(
splitStrategy
)
==
BINARY_TREE_DEGREE
?
DEFAULT_BINARY_LEAF_SIZE
?
DEFAULT_BINARY_LEAF_SIZE
:
DEFAULT_OCTONARY_LEAF_SIZE
;
:
DEFAULT_OCTONARY_LEAF_SIZE
;
}
}
...
@@ -200,7 +202,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -200,7 +202,7 @@ public class BoundingVolumeHierarchyTree<E> {
}
}
public
static
final
class
Builder
<
E
>
{
public
static
final
class
Builder
<
E
>
{
private
int
degree
=
2
;
private
int
degree
=
BINARY_TREE_DEGREE
;
private
SplitStrategy
splitStrategy
=
SplitStrategy
.
AUTO
;
private
SplitStrategy
splitStrategy
=
SplitStrategy
.
AUTO
;
private
int
maxLeafSize
=
DEFAULT_BINARY_LEAF_SIZE
;
private
int
maxLeafSize
=
DEFAULT_BINARY_LEAF_SIZE
;
private
int
maxDepth
=
DEFAULT_MAX_DEPTH
;
private
int
maxDepth
=
DEFAULT_MAX_DEPTH
;
...
@@ -251,8 +253,8 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -251,8 +253,8 @@ public class BoundingVolumeHierarchyTree<E> {
Objects
.
requireNonNull
(
elements
,
"elements"
);
Objects
.
requireNonNull
(
elements
,
"elements"
);
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
if
(
degree
!=
2
&&
degree
!=
8
)
{
if
(
degree
!=
BINARY_TREE_DEGREE
&&
degree
!=
OCTONARY_TREE_DEGREE
)
{
throw
new
IllegalArgumentException
(
"Only
degree 2 and 8
are supported."
);
throw
new
IllegalArgumentException
(
"Only
binary and octonary tree degrees
are supported."
);
}
}
if
(
maxLeafSize
<
1
)
{
if
(
maxLeafSize
<
1
)
{
throw
new
IllegalArgumentException
(
"maxLeafSize must be >= 1."
);
throw
new
IllegalArgumentException
(
"maxLeafSize must be >= 1."
);
...
@@ -274,7 +276,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -274,7 +276,7 @@ public class BoundingVolumeHierarchyTree<E> {
List
<
BvhBuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
List
<
BvhBuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
();
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
();
return
degree
==
2
return
degree
==
BINARY_TREE_DEGREE
?
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
)
?
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
)
:
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
);
:
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
);
}
}
...
@@ -293,7 +295,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -293,7 +295,7 @@ public class BoundingVolumeHierarchyTree<E> {
validateStrategyMatchesDegree
(
degree
,
splitStrategy
);
validateStrategyMatchesDegree
(
degree
,
splitStrategy
);
return
splitStrategy
;
return
splitStrategy
;
}
}
return
degree
==
2
return
degree
==
BINARY_TREE_DEGREE
?
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
?
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
:
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
:
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
}
}
...
@@ -307,11 +309,11 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -307,11 +309,11 @@ public class BoundingVolumeHierarchyTree<E> {
||
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEAN
||
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEAN
||
strategy
==
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
;
||
strategy
==
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
;
if
(
treeDegree
==
2
&&
!
binary
)
{
if
(
treeDegree
==
BINARY_TREE_DEGREE
&&
!
binary
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree
2
."
);
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match
binary tree
degree."
);
}
}
if
(
treeDegree
==
8
&&
!
octonary
)
{
if
(
treeDegree
==
OCTONARY_TREE_DEGREE
&&
!
octonary
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree
8
."
);
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match
octonary tree
degree."
);
}
}
}
}
...
@@ -384,7 +386,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -384,7 +386,7 @@ public class BoundingVolumeHierarchyTree<E> {
}
}
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
for
(
int
i
=
0
;
i
<
OCTONARY_TREE_DEGREE
;
i
++)
{
List
<
BvhBuildItem
<
E
>>
bucket
=
split
.
buckets
[
i
];
List
<
BvhBuildItem
<
E
>>
bucket
=
split
.
buckets
[
i
];
if
(
bucket
.
isEmpty
())
{
if
(
bucket
.
isEmpty
())
{
continue
;
continue
;
...
@@ -440,7 +442,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -440,7 +442,7 @@ public class BoundingVolumeHierarchyTree<E> {
static
<
E
>
Builder
<
E
>
binaryDefault
()
{
static
<
E
>
Builder
<
E
>
binaryDefault
()
{
return
new
Builder
<
E
>()
return
new
Builder
<
E
>()
.
degree
(
2
)
.
degree
(
BINARY_TREE_DEGREE
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
DEFAULT_BINARY_LEAF_SIZE
)
.
maxLeafSize
(
DEFAULT_BINARY_LEAF_SIZE
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
...
@@ -449,7 +451,7 @@ public class BoundingVolumeHierarchyTree<E> {
...
@@ -449,7 +451,7 @@ public class BoundingVolumeHierarchyTree<E> {
static
<
E
>
Builder
<
E
>
octonaryDefault
()
{
static
<
E
>
Builder
<
E
>
octonaryDefault
()
{
return
new
Builder
<
E
>()
return
new
Builder
<
E
>()
.
degree
(
8
)
.
degree
(
OCTONARY_TREE_DEGREE
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
DEFAULT_OCTONARY_LEAF_SIZE
)
.
maxLeafSize
(
DEFAULT_OCTONARY_LEAF_SIZE
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
...
...
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/aabb/OctonarySplitters.java
View file @
a7ed4b64
...
@@ -68,13 +68,13 @@ final class OctonarySplitters {
...
@@ -68,13 +68,13 @@ final class OctonarySplitters {
@SuppressWarnings
(
"unchecked"
)
@SuppressWarnings
(
"unchecked"
)
private
static
<
E
>
OctonarySplitResult
<
E
>
bucketize
(
List
<
BvhBuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
)
{
private
static
<
E
>
OctonarySplitResult
<
E
>
bucketize
(
List
<
BvhBuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
)
{
List
<
BvhBuildItem
<
E
>>[]
buckets
=
new
List
[
8
];
List
<
BvhBuildItem
<
E
>>[]
buckets
=
new
List
[
BoundingVolumeHierarchyTree
.
OCTONARY_TREE_DEGREE
];
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
for
(
int
i
=
0
;
i
<
BoundingVolumeHierarchyTree
.
OCTONARY_TREE_DEGREE
;
i
++)
{
buckets
[
i
]
=
new
ArrayList
<>();
buckets
[
i
]
=
new
ArrayList
<>();
}
}
int
nonEmptyCount
=
0
;
int
nonEmptyCount
=
0
;
boolean
[]
seen
=
new
boolean
[
8
];
boolean
[]
seen
=
new
boolean
[
BoundingVolumeHierarchyTree
.
OCTONARY_TREE_DEGREE
];
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
for
(
BvhBuildItem
<
E
>
item
:
items
)
{
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/aabb/NestedRingsCheckAABB.java
deleted
100644 → 0
View file @
1d9c7810
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package
de.hft.stuttgart.citydoctor2.checks.aabb
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.HashMap
;
import
java.util.Map
;
import
de.hft.stuttgart.citydoctor2.check.Check
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckId
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.RequirementType
;
import
de.hft.stuttgart.citydoctor2.check.Requirement
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.NestedRingError
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABBUtils
;
/**
* Checks whether a inner ring is completely contained in another inner ring
* within the same polygon
*
* @author Baris Numanoglu
*
*/
// TODO extend check with tree
public
class
NestedRingsCheckAABB
extends
Check
{
private
static
final
List
<
CheckId
>
dependencies
;
static
{
ArrayList
<
CheckId
>
deps
=
new
ArrayList
<>();
deps
.
add
(
CheckId
.
C_GE_R_TOO_FEW_POINTS
);
deps
.
add
(
CheckId
.
C_GE_R_NOT_CLOSED
);
deps
.
add
(
CheckId
.
C_GE_R_DUPLICATE_POINT
);
deps
.
add
(
CheckId
.
C_GE_R_SELF_INTERSECTION
);
deps
.
add
(
CheckId
.
C_GE_P_NON_PLANAR
);
deps
.
add
(
CheckId
.
C_GE_P_ORIENTATION_RINGS_SAME
);
dependencies
=
Collections
.
unmodifiableList
(
deps
);
}
@Override
public
void
check
(
Polygon
p
)
{
List
<
LinearRing
>
inner
=
p
.
getInnerRings
();
if
(
inner
==
null
||
inner
.
isEmpty
())
{
p
.
addCheckResult
(
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
));
return
;
}
// --- NEW: cache AABBs of inner rings for broad-phase ---
Map
<
LinearRing
,
AABB
>
ringBoxes
=
new
HashMap
<>(
inner
.
size
());
for
(
LinearRing
r
:
inner
)
{
ringBoxes
.
put
(
r
,
AABB
.
of
(
r
));
}
for
(
LinearRing
interiorRing
:
inner
)
{
AABB
interiorBox
=
ringBoxes
.
get
(
interiorRing
);
for
(
LinearRing
checkRing
:
inner
)
{
if
(
checkRing
==
interiorRing
)
{
// do not compare with itself
continue
;
}
// --- NEW: Broad-phase rejection ---
AABB
checkBox
=
ringBoxes
.
get
(
checkRing
);
// If the inner AABB is not fully inside the other ring's AABB,
// "nested" is impossible -> skip narrow phase.
if
(!
interiorBox
.
contains
(
checkBox
))
{
continue
;
}
// are all points from checkRing inside interiorRing?
if
(
areAllPointsInside
(
interiorRing
,
checkRing
))
{
// found all vertices inside another interior ring
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
);
}
private
boolean
areAllPointsInside
(
LinearRing
ring
,
LinearRing
checkRing
)
{
boolean
isInside
=
true
;
for
(
Vertex
v
:
checkRing
.
getVertices
())
{
if
(!
ring
.
isPointInside
(
v
))
{
isInside
=
false
;
break
;
}
}
return
isInside
;
}
@Override
public
List
<
CheckId
>
getDependencies
()
{
return
dependencies
;
}
@Override
public
Set
<
Requirement
>
appliesToRequirements
()
{
return
CollectionUtils
.
singletonSet
(
Requirement
.
R_GE_P_INNER_RINGS_NESTED
);
}
@Override
public
RequirementType
getType
()
{
return
RequirementType
.
GEOMETRY
;
}
@Override
public
Check
createNewInstance
()
{
return
new
NestedRingsCheckAABB
();
}
@Override
public
CheckId
getCheckId
()
{
return
CheckId
.
C_GE_P_INNER_RINGS_NESTED
;
}
}
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/aabb/RingSelfIntCheckAABB.java
deleted
100644 → 0
View file @
1d9c7810
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package
de.hft.stuttgart.citydoctor2.checks.aabb
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
Jama.EigenvalueDecomposition
;
import
de.hft.stuttgart.citydoctor2.check.Check
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckId
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.Requirement
;
import
de.hft.stuttgart.citydoctor2.check.RequirementType
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError
;
import
de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError
;
import
de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError
;
import
de.hft.stuttgart.citydoctor2.checks.Checks
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.datastructure.BoundingBox
;
import
de.hft.stuttgart.citydoctor2.datastructure.Edge
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.math.CovarianceMatrix
;
import
de.hft.stuttgart.citydoctor2.math.DistanceResult
;
import
de.hft.stuttgart.citydoctor2.math.Matrix3x3d
;
import
de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane
;
import
de.hft.stuttgart.citydoctor2.math.Segment3d
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABBUtils
;
/**
* Checks whether a ring self intersects. Also checks if a point is too close to
* an edge of the ring.
*
* Axis Aligned Boundin Box Early-Out-Version of Matthias Betz's Check
* @author Baris Numanoglu
*/
// TODO extend check with tree
public
class
RingSelfIntCheckAABB
extends
Check
{
private
static
final
String
EPSILON_NAME
=
"minVertexDistance"
;
// check requirement class for default parameters
private
double
degeneratedRingTolerance
=
0.01
;
private
double
epsilon
=
Checks
.
MIN_VERTEX_DISTANCE_DEFAULT
;
private
static
final
List
<
CheckId
>
dependencies
;
static
{
ArrayList
<
CheckId
>
deps
=
new
ArrayList
<>();
deps
.
add
(
CheckId
.
C_GE_R_TOO_FEW_POINTS
);
deps
.
add
(
CheckId
.
C_GE_R_NOT_CLOSED
);
deps
.
add
(
CheckId
.
C_GE_R_DUPLICATE_POINT
);
dependencies
=
Collections
.
unmodifiableList
(
deps
);
}
@Override
public
void
init
(
Map
<
String
,
String
>
parameters
,
ParserConfiguration
config
)
{
String
epsilonString
=
parameters
.
get
(
EPSILON_NAME
);
if
(
epsilonString
!=
null
)
{
epsilon
=
Double
.
parseDouble
(
epsilonString
);
}
if
(
parameters
.
containsKey
(
Requirement
.
DEGENERATED_RING_TOLERANCE
))
{
degeneratedRingTolerance
=
Double
.
parseDouble
(
parameters
.
get
(
Requirement
.
DEGENERATED_RING_TOLERANCE
));
}
}
@Override
public
void
check
(
LinearRing
lr
)
{
checkRingJava
(
lr
);
}
private
void
checkRingJava
(
LinearRing
lr
)
{
// keep original behavior (tiny/degenerate detection first)
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
if
(
checkEigenvalues
(
lr
,
vertices
,
ed
))
{
return
;
// error added, stop here
}
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
// --- NEW: Broad-phase AABB for "point touches edge" test ---
// Precompute padded AABBs for all edges (padding = epsilon)
List
<
EdgeBox
>
edgeBoxes
=
new
ArrayList
<>(
edges
.
size
());
for
(
Edge
e
:
edges
)
{
edgeBoxes
.
add
(
new
EdgeBox
(
e
,
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
)));
}
// Optimized "point touches edge": first filter by padded edge AABB
for
(
EdgeBox
eb
:
edgeBoxes
)
{
if
(
checkForPointsTouchingEdgeBroadPhase
(
lr
,
eb
))
{
return
;
}
}
// --- Original pairwise edge test, now with AABB early-out ---
for
(
int
i
=
0
;
i
<
edges
.
size
()
-
1
;
i
++)
{
Edge
e1
=
edges
.
get
(
i
);
EdgeBox
eb1
=
edgeBoxes
.
get
(
i
);
for
(
int
j
=
i
+
1
;
j
<
edges
.
size
();
j
++)
{
Edge
e2
=
edges
.
get
(
j
);
// share a vertex? skip
if
(
e1
.
getConnectionPoint
(
e2
)
!=
null
)
{
continue
;
}
//skip if AABBs don't overlap
EdgeBox
eb2
=
edgeBoxes
.
get
(
j
);
if
(!
eb1
.
box
.
overlaps
(
eb2
.
box
))
{
continue
;
}
// Narrow-phase distance test (unchanged)
Segment3d
s1
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
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
;
}
}
}
// no errors detected
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
lr
.
addCheckResult
(
cr
);
}
private
boolean
checkEigenvalues
(
LinearRing
lr
,
List
<
Vertex
>
points
,
EigenvalueDecomposition
ed
)
{
Matrix3x3d
mat
=
new
Matrix3x3d
(
ed
.
getV
().
getArray
());
List
<
Vector3d
>
rotatedVertices
=
new
ArrayList
<>();
for
(
Vertex
v
:
points
)
{
rotatedVertices
.
add
(
mat
.
mult
(
v
));
}
// --- AABB replacement for BoundingBox.ofPoints(rotatedVertices) ---
// Computes the axis-aligned extents of the rotated point cloud.
AABB
aabb
=
AABB
.
ofPoints
(
rotatedVertices
);
double
dx
=
aabb
.
getMaxX
()
-
aabb
.
getMinX
();
double
dy
=
aabb
.
getMaxY
()
-
aabb
.
getMinY
();
double
dz
=
aabb
.
getMaxZ
()
-
aabb
.
getMinZ
();
int
nrOfEigenvaluesBelowTolerance
=
0
;
if
(
dx
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
if
(
dy
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
if
(
dz
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
if
(
nrOfEigenvaluesBelowTolerance
>=
2
)
{
CheckError
err
=
new
DegeneratedRingError
(
lr
);
lr
.
addCheckResult
(
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
));
return
true
;
}
return
false
;
}
/**
* Original narrow-phase "point near edge" with a broad-phase AABB filter.
*/
private
boolean
checkForPointsTouchingEdgeBroadPhase
(
LinearRing
lr
,
EdgeBox
eb
)
{
Segment3d
seg
=
new
Segment3d
(
eb
.
edge
.
getFrom
(),
eb
.
edge
.
getTo
());
for
(
Vertex
v
:
lr
.
getVertices
())
{
// skip endpoints
if
(
v
==
eb
.
edge
.
getFrom
()
||
v
==
eb
.
edge
.
getTo
())
{
continue
;
}
// Broad-phase: reject if vertex is outside padded edge AABB
if
(!
eb
.
box
.
contains
(
v
.
getX
(),
v
.
getY
(),
v
.
getZ
()))
{
continue
;
}
// Narrow-phase: exact segment-point distance
if
(
seg
.
getDistance
(
v
)
<
epsilon
)
{
CheckError
err
=
new
PointTouchesEdgeError
(
lr
,
eb
.
edge
,
v
);
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
lr
.
addCheckResult
(
cr
);
return
true
;
}
}
return
false
;
}
private
List
<
Edge
>
getEdgesForRing
(
LinearRing
lr
)
{
List
<
Edge
>
edges
=
new
ArrayList
<>();
Geometry
geom
=
lr
.
getParent
().
getParent
();
for
(
int
i
=
0
;
i
<
lr
.
getVertices
().
size
()
-
1
;
i
++)
{
Vertex
v1
=
lr
.
getVertices
().
get
(
i
);
Vertex
v2
=
lr
.
getVertices
().
get
(
i
+
1
);
Edge
e
=
geom
.
getEdge
(
v1
,
v2
);
if
(
e
==
null
)
{
throw
new
IllegalStateException
(
"Edge between v1="
+
v1
+
" to v2="
+
v2
+
" is missing"
);
}
edges
.
add
(
e
);
}
return
edges
;
}
private
static
final
class
EdgeBox
{
final
Edge
edge
;
final
AABB
box
;
EdgeBox
(
Edge
edge
,
AABB
box
)
{
this
.
edge
=
edge
;
this
.
box
=
box
;
}
}
@Override
public
List
<
CheckId
>
getDependencies
()
{
return
dependencies
;
}
@Override
public
Set
<
Requirement
>
appliesToRequirements
()
{
return
CollectionUtils
.
singletonSet
(
Requirement
.
R_GE_R_SELF_INTERSECTION
);
}
@Override
public
RequirementType
getType
()
{
return
RequirementType
.
GEOMETRY
;
}
@Override
public
Check
createNewInstance
()
{
return
new
RingSelfIntCheckAABB
();
}
@Override
public
CheckId
getCheckId
()
{
return
CheckId
.
C_GE_R_SELF_INTERSECTION
;
}
}
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/aabb/SolidSelfIntCheckAABB.java
deleted
100644 → 0
View file @
1d9c7810
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package
de.hft.stuttgart.citydoctor2.checks.aabb
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Set
;
import
de.hft.stuttgart.citydoctor2.check.Check
;
import
de.hft.stuttgart.citydoctor2.check.CheckError
;
import
de.hft.stuttgart.citydoctor2.check.CheckId
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.Requirement
;
import
de.hft.stuttgart.citydoctor2.check.RequirementType
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.utils.PolygonIntersection
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree
;
/**
* Check for self intersecting solids
*
* @author Baris Numanoglu
*/
public
class
SolidSelfIntCheckAABB
extends
Check
{
private
static
final
List
<
CheckId
>
dependencies
;
static
{
ArrayList
<
CheckId
>
deps
=
new
ArrayList
<>();
dependencies
=
Collections
.
unmodifiableList
(
deps
);
deps
.
add
(
CheckId
.
C_GE_R_TOO_FEW_POINTS
);
deps
.
add
(
CheckId
.
C_GE_R_NOT_CLOSED
);
deps
.
add
(
CheckId
.
C_GE_R_DUPLICATE_POINT
);
deps
.
add
(
CheckId
.
C_GE_R_SELF_INTERSECTION
);
deps
.
add
(
CheckId
.
C_GE_P_HOLE_OUTSIDE
);
deps
.
add
(
CheckId
.
C_GE_P_INNER_RINGS_NESTED
);
deps
.
add
(
CheckId
.
C_GE_P_INTERIOR_DISCONNECTED
);
deps
.
add
(
CheckId
.
C_GE_P_INTERSECTING_RINGS
);
deps
.
add
(
CheckId
.
C_GE_P_ORIENTATION_RINGS_SAME
);
deps
.
add
(
CheckId
.
C_GE_P_NON_PLANAR
);
deps
.
add
(
CheckId
.
C_GE_S_TOO_FEW_POLYGONS
);
deps
.
add
(
CheckId
.
C_GE_S_MULTIPLE_CONNECTED_COMPONENTS
);
deps
.
add
(
CheckId
.
C_GE_S_NON_MANIFOLD_EDGE
);
deps
.
add
(
CheckId
.
C_GE_S_NON_MANIFOLD_VERTEX
);
deps
.
add
(
CheckId
.
C_GE_S_POLYGON_WRONG_ORIENTATION
);
}
@Override
public
void
check
(
Geometry
g
)
{
if
(
g
.
getType
()
!=
GeometryType
.
SOLID
&&
g
.
getType
()
!=
GeometryType
.
COMPOSITE_SURFACE
)
{
return
;
}
// --- broad-phase via BVH (AABB tree) ---
List
<
Polygon
>
polys
=
g
.
getPolygons
();
if
(
polys
==
null
||
polys
.
size
()
<=
1
)
{
g
.
addCheckResult
(
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
));
return
;
}
CheckResult
cr
;
// Build BVH on polygons, but compute AABBs from the *original* polygons
BoundingVolumeHierarchyTree
<
Polygon
>
tree
=
BoundingVolumeHierarchyTree
.<
Polygon
>
binaryBuilder
()
.
elements
(
polys
)
.
aabbFunction
(
p
->
AABB
.
of
(
p
.
getOriginal
()))
.
build
();
// TODO: comparison with older version without tree
List
<
PolygonIntersection
>
intersections
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
0.001
,
tree
);
if
(
intersections
.
isEmpty
())
{
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
}
else
{
CheckError
e
=
new
SolidSelfIntError
(
g
,
intersections
);
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
e
);
}
g
.
addCheckResult
(
cr
);
}
@Override
public
List
<
CheckId
>
getDependencies
()
{
return
dependencies
;
}
@Override
public
Set
<
Requirement
>
appliesToRequirements
()
{
return
CollectionUtils
.
singletonSet
(
Requirement
.
R_GE_S_SELF_INTERSECTION
);
}
@Override
public
RequirementType
getType
()
{
return
RequirementType
.
GEOMETRY
;
}
@Override
public
Check
createNewInstance
()
{
return
new
SolidSelfIntCheckAABB
();
}
@Override
public
CheckId
getCheckId
()
{
return
CheckId
.
C_GE_S_SELF_INTERSECTION
;
}
}
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NestedRingsCheck.java
View file @
a7ed4b64
...
@@ -34,7 +34,6 @@ import de.hft.stuttgart.citydoctor2.check.RequirementType;
...
@@ -34,7 +34,6 @@ import de.hft.stuttgart.citydoctor2.check.RequirementType;
import
de.hft.stuttgart.citydoctor2.check.Requirement
;
import
de.hft.stuttgart.citydoctor2.check.Requirement
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.NestedRingError
;
import
de.hft.stuttgart.citydoctor2.check.error.NestedRingError
;
import
de.hft.stuttgart.citydoctor2.checks.util.BvhUsagePolicy
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
...
@@ -66,7 +65,7 @@ public class NestedRingsCheck extends Check {
...
@@ -66,7 +65,7 @@ public class NestedRingsCheck extends Check {
}
}
public
enum
Variant
{
public
enum
Variant
{
AUTO
,
AUTO
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
)
,
OLD
,
OLD
,
AABB_FILTER
,
AABB_FILTER
,
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
...
@@ -136,34 +135,7 @@ public class NestedRingsCheck extends Check {
...
@@ -136,34 +135,7 @@ public class NestedRingsCheck extends Check {
}
else
if
(
variant
.
isBvh
())
{
}
else
if
(
variant
.
isBvh
())
{
checkWithBvhFilter
(
p
,
variant
.
getSplitStrategy
());
checkWithBvhFilter
(
p
,
variant
.
getSplitStrategy
());
}
else
{
}
else
{
checkAuto
(
p
);
throw
new
IllegalStateException
(
"Unsupported nested rings variant: "
+
variant
);
}
}
private
void
checkAuto
(
Polygon
p
)
{
List
<
LinearRing
>
innerRings
=
p
.
getInnerRings
();
if
(
innerRings
.
size
()
<
2
)
{
checkOriginal
(
p
);
return
;
}
Map
<
LinearRing
,
AABB
>
ringBoxes
=
new
HashMap
<>(
innerRings
.
size
());
List
<
AABB
>
boxes
=
new
ArrayList
<>(
innerRings
.
size
());
for
(
LinearRing
ring
:
innerRings
)
{
AABB
box
=
AABB
.
of
(
ring
);
ringBoxes
.
put
(
ring
,
box
);
boxes
.
add
(
box
);
}
BvhUsagePolicy
.
BvhInputSummary
summary
=
BvhUsagePolicy
.
BvhInputSummary
.
ofAabbs
(
boxes
);
if
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
NESTED_RINGS
,
summary
))
{
SplitStrategy
splitStrategy
=
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
NESTED_RINGS
,
summary
);
checkWithBvhFilter
(
p
,
splitStrategy
,
ringBoxes
);
}
else
if
(
innerRings
.
size
()
>
3
)
{
checkWithBoundingBoxFilter
(
p
);
}
else
{
checkOriginal
(
p
);
}
}
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
View file @
a7ed4b64
...
@@ -37,7 +37,6 @@ import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
...
@@ -37,7 +37,6 @@ import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import
de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError
;
import
de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError
;
import
de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError
;
import
de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError
;
import
de.hft.stuttgart.citydoctor2.checks.Checks
;
import
de.hft.stuttgart.citydoctor2.checks.Checks
;
import
de.hft.stuttgart.citydoctor2.checks.util.BvhUsagePolicy
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.datastructure.Edge
;
import
de.hft.stuttgart.citydoctor2.datastructure.Edge
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
...
@@ -91,7 +90,7 @@ public class RingSelfIntCheck extends Check {
...
@@ -91,7 +90,7 @@ public class RingSelfIntCheck extends Check {
}
}
public
enum
Variant
{
public
enum
Variant
{
AUTO
,
AUTO
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
)
,
OLD
,
OLD
,
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
...
@@ -156,18 +155,11 @@ public class RingSelfIntCheck extends Check {
...
@@ -156,18 +155,11 @@ public class RingSelfIntCheck extends Check {
checkRingOld
(
lr
);
checkRingOld
(
lr
);
return
;
return
;
}
}
if
(
variant
==
Variant
.
AUTO
)
{
if
(
variant
.
isBvh
())
{
int
edgeCount
=
Math
.
max
(
0
,
lr
.
getVertices
().
size
()
-
1
);
checkRingBvh
(
lr
,
variant
.
getSplitStrategy
());
if
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
RING_SELF_INTERSECTION
,
edgeCount
))
{
SplitStrategy
splitStrategy
=
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
RING_SELF_INTERSECTION
,
edgeCount
);
checkRingBvh
(
lr
,
splitStrategy
);
}
else
{
checkRingOld
(
lr
);
}
return
;
return
;
}
}
checkRingBvh
(
lr
,
variant
.
getSplitStrategy
()
);
throw
new
IllegalStateException
(
"Unsupported ring self-intersection variant: "
+
variant
);
}
}
/**
/**
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/SolidSelfIntCheck.java
View file @
a7ed4b64
...
@@ -32,12 +32,10 @@ import de.hft.stuttgart.citydoctor2.check.Requirement;
...
@@ -32,12 +32,10 @@ import de.hft.stuttgart.citydoctor2.check.Requirement;
import
de.hft.stuttgart.citydoctor2.check.RequirementType
;
import
de.hft.stuttgart.citydoctor2.check.RequirementType
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError
;
import
de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError
;
import
de.hft.stuttgart.citydoctor2.checks.util.BvhUsagePolicy
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
...
@@ -57,7 +55,7 @@ public class SolidSelfIntCheck extends Check {
...
@@ -57,7 +55,7 @@ public class SolidSelfIntCheck extends Check {
private
Variant
variant
=
Variant
.
OLD
;
private
Variant
variant
=
Variant
.
OLD
;
public
enum
Variant
{
public
enum
Variant
{
AUTO
,
AUTO
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
)
,
OLD
,
OLD
,
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
BVH_BINARY_OBJECT_MEDIAN
(
SplitStrategy
.
BINARY_OBJECT_MEDIAN
),
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
BVH_BINARY_OBJECT_MEAN
(
SplitStrategy
.
BINARY_OBJECT_MEAN
),
...
@@ -154,20 +152,14 @@ public class SolidSelfIntCheck extends Check {
...
@@ -154,20 +152,14 @@ public class SolidSelfIntCheck extends Check {
if
(
variant
.
isBvh
())
{
if
(
variant
.
isBvh
())
{
return
calculateIntersectionsWithTree
(
g
,
variant
.
getSplitStrategy
());
return
calculateIntersectionsWithTree
(
g
,
variant
.
getSplitStrategy
());
}
}
throw
new
IllegalStateException
(
"Unsupported solid self-intersection variant: "
+
variant
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
BvhUsagePolicy
.
BvhInputSummary
.
ofElements
(
g
.
getPolygons
(),
AABB:
:
of
);
if
(!
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
))
{
return
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
);
}
SplitStrategy
splitStrategy
=
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
);
return
calculateIntersectionsWithTree
(
g
,
splitStrategy
);
}
}
private
List
<
PolygonIntersection
>
calculateIntersectionsWithTree
(
Geometry
g
,
SplitStrategy
splitStrategy
)
{
private
List
<
PolygonIntersection
>
calculateIntersectionsWithTree
(
Geometry
g
,
SplitStrategy
splitStrategy
)
{
BoundingVolumeHierarchyTree
.
Builder
<
Integer
>
treeConfig
=
BoundingVolumeHierarchyTree
.<
Integer
>
builder
()
BoundingVolumeHierarchyTree
.
Builder
<
Integer
>
treeConfig
=
isBinary
(
splitStrategy
)
.
degree
(
isBinary
(
splitStrategy
)
?
2
:
8
)
?
BoundingVolumeHierarchyTree
.<
Integer
>
binaryBuilder
()
.
splitStrategy
(
splitStrategy
);
:
BoundingVolumeHierarchyTree
.<
Integer
>
octonaryBuilder
();
treeConfig
.
splitStrategy
(
splitStrategy
);
return
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
,
treeConfig
);
return
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
,
treeConfig
);
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/BvhUsagePolicy.java
deleted
100644 → 0
View file @
1d9c7810
package
de.hft.stuttgart.citydoctor2.checks.util
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.function.Function
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy
;
/**
* Central policy for cheap BVH decisions in geometry checks.
*/
public
final
class
BvhUsagePolicy
{
private
static
final
int
SOLID_SELF_INTERSECTION_TREE_THRESHOLD
=
16
;
private
static
final
int
NESTED_RINGS_TREE_THRESHOLD
=
8
;
private
static
final
int
RING_SELF_INTERSECTION_TREE_THRESHOLD
=
32
;
private
static
final
double
THIN_BOX_ASPECT_RATIO
=
20.0
;
private
static
final
double
DEGENERATE_TOLERANCE
=
1
e
-
12
;
private
BvhUsagePolicy
()
{
}
public
enum
BvhCheckType
{
SOLID_SELF_INTERSECTION
,
NESTED_RINGS
,
RING_SELF_INTERSECTION
}
public
static
final
class
BvhInputSummary
{
private
final
int
elementCount
;
private
final
double
extentX
;
private
final
double
extentY
;
private
final
double
extentZ
;
private
final
double
flatnessRatio
;
private
final
double
averageRelativeBoxExtent
;
private
final
double
thinBoxRatio
;
private
final
double
averageRelativeBoxVolume
;
private
final
double
centerSpreadRatio
;
private
BvhInputSummary
(
int
elementCount
,
double
extentX
,
double
extentY
,
double
extentZ
,
double
flatnessRatio
,
double
averageRelativeBoxExtent
,
double
thinBoxRatio
,
double
averageRelativeBoxVolume
,
double
centerSpreadRatio
)
{
this
.
elementCount
=
elementCount
;
this
.
extentX
=
extentX
;
this
.
extentY
=
extentY
;
this
.
extentZ
=
extentZ
;
this
.
flatnessRatio
=
flatnessRatio
;
this
.
averageRelativeBoxExtent
=
averageRelativeBoxExtent
;
this
.
thinBoxRatio
=
thinBoxRatio
;
this
.
averageRelativeBoxVolume
=
averageRelativeBoxVolume
;
this
.
centerSpreadRatio
=
centerSpreadRatio
;
}
public
static
BvhInputSummary
ofAabbs
(
List
<
AABB
>
boxes
)
{
Objects
.
requireNonNull
(
boxes
,
"boxes"
);
if
(
boxes
.
isEmpty
())
{
return
empty
();
}
AABB
totalBox
=
AABB
.
enclosing
(
boxes
);
double
extentX
=
totalBox
.
getExtentX
();
double
extentY
=
totalBox
.
getExtentY
();
double
extentZ
=
totalBox
.
getExtentZ
();
double
relativeExtentSum
=
0.0
;
double
relativeVolumeSum
=
0.0
;
double
centerXSum
=
0.0
;
double
centerYSum
=
0.0
;
double
centerZSum
=
0.0
;
int
thinBoxCount
=
0
;
for
(
AABB
box
:
boxes
)
{
relativeExtentSum
+=
relativeExtent
(
box
.
getExtentX
(),
extentX
);
relativeExtentSum
+=
relativeExtent
(
box
.
getExtentY
(),
extentY
);
relativeExtentSum
+=
relativeExtent
(
box
.
getExtentZ
(),
extentZ
);
relativeVolumeSum
+=
relativeVolume
(
box
,
totalBox
);
if
(
aspectRatio
(
box
)
>=
THIN_BOX_ASPECT_RATIO
)
{
thinBoxCount
++;
}
centerXSum
+=
box
.
getCenterX
();
centerYSum
+=
box
.
getCenterY
();
centerZSum
+=
box
.
getCenterZ
();
}
double
maxExtent
=
totalBox
.
getLongestExtent
();
double
minExtent
=
totalBox
.
getShortestExtent
();
double
flatnessRatio
=
maxExtent
==
0.0
?
0.0
:
minExtent
/
maxExtent
;
double
averageRelativeBoxExtent
=
relativeExtentSum
/
(
boxes
.
size
()
*
3.0
);
double
thinBoxRatio
=
(
double
)
thinBoxCount
/
boxes
.
size
();
double
averageRelativeBoxVolume
=
relativeVolumeSum
/
boxes
.
size
();
double
centerSpreadRatio
=
centerSpreadRatio
(
boxes
,
centerXSum
/
boxes
.
size
(),
centerYSum
/
boxes
.
size
(),
centerZSum
/
boxes
.
size
(),
totalBox
);
return
new
BvhInputSummary
(
boxes
.
size
(),
extentX
,
extentY
,
extentZ
,
flatnessRatio
,
averageRelativeBoxExtent
,
thinBoxRatio
,
averageRelativeBoxVolume
,
centerSpreadRatio
);
}
public
static
<
E
>
BvhInputSummary
ofElements
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
Objects
.
requireNonNull
(
elements
,
"elements"
);
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
return
ofAabbs
(
elements
.
stream
().
map
(
aabbFunction
).
toList
());
}
public
static
BvhInputSummary
countOnly
(
int
elementCount
)
{
return
new
BvhInputSummary
(
elementCount
,
0.0
,
0.0
,
0.0
,
1.0
,
0.0
,
0.0
,
0.0
,
0.0
);
}
private
static
BvhInputSummary
empty
()
{
return
countOnly
(
0
);
}
private
static
double
relativeExtent
(
double
localExtent
,
double
totalExtent
)
{
return
totalExtent
==
0.0
?
0.0
:
localExtent
/
totalExtent
;
}
private
static
double
relativeVolume
(
AABB
box
,
AABB
totalBox
)
{
double
localMeasure
=
1.0
;
double
totalMeasure
=
1.0
;
boolean
hasActiveExtent
=
false
;
if
(
box
.
getExtentX
()
>
DEGENERATE_TOLERANCE
)
{
localMeasure
*=
box
.
getExtentX
();
totalMeasure
*=
totalBox
.
getExtentX
();
hasActiveExtent
=
true
;
}
if
(
box
.
getExtentY
()
>
DEGENERATE_TOLERANCE
)
{
localMeasure
*=
box
.
getExtentY
();
totalMeasure
*=
totalBox
.
getExtentY
();
hasActiveExtent
=
true
;
}
if
(
box
.
getExtentZ
()
>
DEGENERATE_TOLERANCE
)
{
localMeasure
*=
box
.
getExtentZ
();
totalMeasure
*=
totalBox
.
getExtentZ
();
hasActiveExtent
=
true
;
}
if
(!
hasActiveExtent
||
totalMeasure
<=
DEGENERATE_TOLERANCE
)
{
return
0.0
;
}
return
localMeasure
/
totalMeasure
;
}
private
static
double
aspectRatio
(
AABB
box
)
{
double
longest
=
box
.
getLongestExtent
();
double
shortest
=
shortestPositiveExtent
(
box
);
return
shortest
==
0.0
?
Double
.
POSITIVE_INFINITY
:
longest
/
shortest
;
}
private
static
double
shortestPositiveExtent
(
AABB
box
)
{
double
shortest
=
Double
.
POSITIVE_INFINITY
;
if
(
box
.
getExtentX
()
>
0.0
)
{
shortest
=
Math
.
min
(
shortest
,
box
.
getExtentX
());
}
if
(
box
.
getExtentY
()
>
0.0
)
{
shortest
=
Math
.
min
(
shortest
,
box
.
getExtentY
());
}
if
(
box
.
getExtentZ
()
>
0.0
)
{
shortest
=
Math
.
min
(
shortest
,
box
.
getExtentZ
());
}
return
shortest
==
Double
.
POSITIVE_INFINITY
?
0.0
:
shortest
;
}
private
static
double
centerSpreadRatio
(
List
<
AABB
>
boxes
,
double
meanCenterX
,
double
meanCenterY
,
double
meanCenterZ
,
AABB
totalBox
)
{
double
varianceX
=
0.0
;
double
varianceY
=
0.0
;
double
varianceZ
=
0.0
;
for
(
AABB
box
:
boxes
)
{
varianceX
+=
square
(
box
.
getCenterX
()
-
meanCenterX
);
varianceY
+=
square
(
box
.
getCenterY
()
-
meanCenterY
);
varianceZ
+=
square
(
box
.
getCenterZ
()
-
meanCenterZ
);
}
double
spreadX
=
normalizedStdDev
(
varianceX
,
boxes
.
size
(),
totalBox
.
getExtentX
());
double
spreadY
=
normalizedStdDev
(
varianceY
,
boxes
.
size
(),
totalBox
.
getExtentY
());
double
spreadZ
=
normalizedStdDev
(
varianceZ
,
boxes
.
size
(),
totalBox
.
getExtentZ
());
return
(
spreadX
+
spreadY
+
spreadZ
)
/
3.0
;
}
private
static
double
normalizedStdDev
(
double
varianceSum
,
int
count
,
double
extent
)
{
if
(
extent
==
0.0
)
{
return
0.0
;
}
return
Math
.
sqrt
(
varianceSum
/
count
)
/
extent
;
}
private
static
double
square
(
double
value
)
{
return
value
*
value
;
}
public
int
getElementCount
()
{
return
elementCount
;
}
public
double
getExtentX
()
{
return
extentX
;
}
public
double
getExtentY
()
{
return
extentY
;
}
public
double
getExtentZ
()
{
return
extentZ
;
}
public
double
getFlatnessRatio
()
{
return
flatnessRatio
;
}
public
double
getAverageRelativeBoxExtent
()
{
return
averageRelativeBoxExtent
;
}
public
double
getThinBoxRatio
()
{
return
thinBoxRatio
;
}
public
double
getAverageRelativeBoxVolume
()
{
return
averageRelativeBoxVolume
;
}
public
double
getCenterSpreadRatio
()
{
return
centerSpreadRatio
;
}
}
public
static
boolean
shouldUseTree
(
BvhCheckType
checkType
,
int
elementCount
)
{
return
shouldUseTree
(
checkType
,
BvhInputSummary
.
countOnly
(
elementCount
));
}
public
static
boolean
shouldUseTree
(
BvhCheckType
checkType
,
BvhInputSummary
summary
)
{
int
elementCount
=
summary
.
getElementCount
();
if
(
elementCount
<
20
)
{
return
false
;
}
if
(
elementCount
<
thresholdFor
(
checkType
))
{
return
false
;
}
if
(
summary
.
getAverageRelativeBoxExtent
()
>
0.65
&&
elementCount
<
128
)
{
return
false
;
}
return
true
;
}
public
static
SplitStrategy
chooseSplitStrategy
(
BvhCheckType
checkType
,
int
elementCount
)
{
return
chooseSplitStrategy
(
checkType
,
BvhInputSummary
.
countOnly
(
elementCount
));
}
public
static
SplitStrategy
chooseSplitStrategy
(
BvhCheckType
checkType
,
BvhInputSummary
summary
)
{
if
(
checkType
==
BvhCheckType
.
RING_SELF_INTERSECTION
)
{
return
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
;
}
if
(
summary
.
getFlatnessRatio
()
<
0.02
)
{
return
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
;
}
if
(
summary
.
getElementCount
()
>=
128
)
{
return
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
}
return
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
;
}
private
static
int
thresholdFor
(
BvhCheckType
checkType
)
{
switch
(
checkType
)
{
case
SOLID_SELF_INTERSECTION:
return
SOLID_SELF_INTERSECTION_TREE_THRESHOLD
;
case
NESTED_RINGS:
return
NESTED_RINGS_TREE_THRESHOLD
;
case
RING_SELF_INTERSECTION:
return
RING_SELF_INTERSECTION_TREE_THRESHOLD
;
default
:
throw
new
IllegalArgumentException
(
"Unsupported BVH check type: "
+
checkType
);
}
}
}
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/SelfIntersectionUtil.java
View file @
a7ed4b64
...
@@ -184,7 +184,7 @@ public class SelfIntersectionUtil {
...
@@ -184,7 +184,7 @@ public class SelfIntersectionUtil {
return
calculateSolidSelfIntersectionWithTree
(
return
calculateSolidSelfIntersectionWithTree
(
g
,
g
,
delta
,
delta
,
BoundingVolumeHierarchyTree
.<
Integer
>
bi
naryBuilder
()
BoundingVolumeHierarchyTree
.<
Integer
>
octo
naryBuilder
()
);
);
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/core/BvhUsagePolicyTest.java
deleted
100644 → 0
View file @
1d9c7810
package
de.hft.stuttgart.citydoctor2.checks.aabb.core
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticCityGmlLikeGeometryFactory
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticRingGeometryFactory
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticSolidGeometryFactory
;
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.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.checks.util.BvhUsagePolicy
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
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.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy
;
/**
* Covers the current tree-usage policy with small, readable synthetic inputs.
*
* These tests are not meant to prove an optimal heuristic. They guard the
* conservative decisions that are currently wired into BvhUsagePolicy.
*
* @author Numanoglu
*/
public
class
BvhUsagePolicyTest
{
@Test
public
void
smallInputsStayWithoutTree
()
{
Geometry
smallSolid
=
SyntheticSolidGeometryFactory
.
separatedBoxGrid
(
Lod
.
LOD2
,
1
,
1
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
summaryForPolygons
(
smallSolid
);
assertFalse
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
));
assertFalse
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
NESTED_RINGS
,
4
));
assertFalse
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
RING_SELF_INTERSECTION
,
16
));
}
@Test
public
void
largeDistributedInputsUseOctonaryObjectMean
()
{
Geometry
cityLikeGeometry
=
SyntheticCityGmlLikeGeometryFactory
.
variedRoofDistrict
(
Lod
.
LOD2
,
180
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
summaryForPolygons
(
cityLikeGeometry
);
assertTrue
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
));
assertEquals
(
SplitStrategy
.
OCTONARY_OBJECT_MEAN
,
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
));
}
@Test
public
void
flatInputsUseBinarySpatialMedian
()
{
Geometry
flatGeometry
=
SyntheticSolidGeometryFactory
.
flatBoxGrid
(
Lod
.
LOD2
,
200
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
summaryForPolygons
(
flatGeometry
);
assertTrue
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
));
assertEquals
(
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
,
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
SOLID_SELF_INTERSECTION
,
summary
));
}
@Test
public
void
ringSelfIntersectionUsesBinarySpatialMedian
()
{
Geometry
ringGeometry
=
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
200
,
100
,
0.001
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
BvhUsagePolicy
.
BvhInputSummary
.
ofAabbs
(
edgeBoxes
(
ringGeometry
));
assertTrue
(
BvhUsagePolicy
.
shouldUseTree
(
BvhUsagePolicy
.
BvhCheckType
.
RING_SELF_INTERSECTION
,
summary
));
assertEquals
(
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
,
BvhUsagePolicy
.
chooseSplitStrategy
(
BvhUsagePolicy
.
BvhCheckType
.
RING_SELF_INTERSECTION
,
summary
));
}
@Test
public
void
summaryExposesThinVolumeAndSpreadMetrics
()
{
Geometry
slabGeometry
=
SyntheticSolidGeometryFactory
.
longThinSlabs
(
Lod
.
LOD2
,
160
);
BvhUsagePolicy
.
BvhInputSummary
summary
=
summaryForPolygons
(
slabGeometry
);
assertTrue
(
"Expected long-thin fixture to contain thin boxes"
,
summary
.
getThinBoxRatio
()
>
0.0
);
assertTrue
(
"Relative box volume must be non-negative"
,
summary
.
getAverageRelativeBoxVolume
()
>=
0.0
);
assertTrue
(
"Expected distributed slab centers"
,
summary
.
getCenterSpreadRatio
()
>
0.0
);
}
private
static
BvhUsagePolicy
.
BvhInputSummary
summaryForPolygons
(
Geometry
geometry
)
{
// Production code computes the same cheap summary from each element's AABB.
return
BvhUsagePolicy
.
BvhInputSummary
.
ofElements
(
geometry
.
getPolygons
(),
polygon
->
AABB
.
of
(
polygon
.
getOriginal
()));
}
private
static
List
<
AABB
>
edgeBoxes
(
Geometry
geometry
)
{
// Ring self-intersection uses segment boxes rather than whole polygon boxes.
List
<
AABB
>
boxes
=
new
ArrayList
<>();
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
LinearRing
ring
=
polygon
.
getExteriorRing
();
List
<
Vertex
>
vertices
=
ring
.
getVertices
();
for
(
int
i
=
0
;
i
<
vertices
.
size
()
-
1
;
i
++)
{
boxes
.
add
(
AABB
.
of
(
vertices
.
get
(
i
),
vertices
.
get
(
i
+
1
),
0.001
));
}
}
return
boxes
;
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/core/GeometryChecksWithAABBTest.java
deleted
100644 → 0
View file @
1d9c7810
package
de.hft.stuttgart.citydoctor2.checks.aabb.core
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
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.Vertex
;
import
java.util.Arrays
;
import
java.util.List
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.NestedRingsCheckAABB
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.RingSelfIntCheckAABB
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.SolidSelfIntCheckAABB
;
/**
* Manual smoke runner for the older AABB check classes.
*
* The class is intentionally simple and prints its result to the console. It is
* useful when the checks are inspected locally, while systematic assertions live
* in the dedicated correctness tests.
*
* @author Numanoglu
*/
public
class
GeometryChecksWithAABBTest
{
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
"=== Geometry Checks with AABB - Manual Test Runner ==="
);
//testTooFewPoints();
testNestedRings
();
testRingSelfIntersection
();
testSolidSelfIntersection
();
System
.
out
.
println
(
"\n=== Test run finished ==="
);
}
// --------------------------------------------------------------
// NestedRingsCheck
// --------------------------------------------------------------
private
static
void
testNestedRings
()
{
System
.
out
.
println
(
"\n[TEST] NestedRingsCheck"
);
// outer ring
LinearRing
outer
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
EXTERIOR
);
outer
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0
,
0
,
0
),
new
Vertex
(
1
,
0
,
0
),
new
Vertex
(
1
,
1
,
0
),
new
Vertex
(
0
,
1
,
0
),
new
Vertex
(
0
,
0
,
0
)
));
ConcretePolygon
p
=
new
ConcretePolygon
();
p
.
setExteriorRing
(
outer
);
// inner ring 1
LinearRing
inner1
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
INTERIOR
);
inner1
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0.2
,
0.2
,
0
),
new
Vertex
(
0.8
,
0.2
,
0
),
new
Vertex
(
0.8
,
0.8
,
0
),
new
Vertex
(
0.2
,
0.8
,
0
),
new
Vertex
(
0.2
,
0.2
,
0
)
));
// inner ring 2 inside inner ring 1
LinearRing
inner2
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
INTERIOR
);
inner2
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0.4
,
0.4
,
0
),
new
Vertex
(
0.6
,
0.4
,
0
),
new
Vertex
(
0.6
,
0.6
,
0
),
new
Vertex
(
0.4
,
0.6
,
0
),
new
Vertex
(
0.4
,
0.4
,
0
)
));
p
.
addInteriorRing
(
inner1
);
p
.
addInteriorRing
(
inner2
);
NestedRingsCheckAABB
check
=
new
NestedRingsCheckAABB
();
check
.
check
(
p
);
System
.
out
.
println
(
" - Should detect nested rings: "
+
p
.
containsAnyError
());
}
// --------------------------------------------------------------
// RingSelfIntCheck
// --------------------------------------------------------------
private
static
void
testRingSelfIntersection
()
{
System
.
out
.
println
(
"\n[TEST] RingSelfIntCheck"
);
LinearRing
ring
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
EXTERIOR
);
ring
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0
,
0
,
0
),
new
Vertex
(
1
,
1
,
0
),
new
Vertex
(
1
,
0
,
0
),
new
Vertex
(
0
,
1
,
0
),
new
Vertex
(
0
,
0
,
0
)
));
ConcretePolygon
p
=
new
ConcretePolygon
();
p
.
setExteriorRing
(
ring
);
// Orientation is not the focus here; the geometry mainly provides parent links.
Geometry
g
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Geometry
.
Orientation
.
OUTWARD
);
g
.
addPolygon
(
p
);
// ensures parent links exist
g
.
updateEdgesAndVertices
();
RingSelfIntCheckAABB
check
=
new
RingSelfIntCheckAABB
();
check
.
check
(
ring
);
System
.
out
.
println
(
" - Should detect self-intersection: "
+
ring
.
containsAnyError
());
}
// --------------------------------------------------------------
// SolidSelfIntCheck
// --------------------------------------------------------------
private
static
void
testSolidSelfIntersection
()
{
System
.
out
.
println
(
"\n[TEST] SolidSelfIntCheck"
);
// first triangle
ConcretePolygon
p1
=
new
ConcretePolygon
();
LinearRing
r1
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
EXTERIOR
);
r1
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0
,
0
,
0
),
new
Vertex
(
1
,
0
,
0
),
new
Vertex
(
1
,
1
,
0
),
new
Vertex
(
0
,
0
,
0
)
));
p1
.
setExteriorRing
(
r1
);
// second triangle overlapping the first
ConcretePolygon
p2
=
new
ConcretePolygon
();
LinearRing
r2
=
new
LinearRing
(
LinearRing
.
LinearRingType
.
EXTERIOR
);
r2
.
addAllVertices
(
Arrays
.
asList
(
new
Vertex
(
0
,
0
,
0
),
new
Vertex
(
1
,
1
,
0
),
new
Vertex
(
0
,
1
,
0
),
new
Vertex
(
0
,
0
,
0
)
));
p2
.
setExteriorRing
(
r2
);
Geometry
solid
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Geometry
.
Orientation
.
OUTWARD
);
solid
.
getPolygons
().
addAll
(
List
.
of
(
p1
,
p2
));
SolidSelfIntCheckAABB
check
=
new
SolidSelfIntCheckAABB
();
check
.
check
(
solid
);
System
.
out
.
println
(
" - Should detect solid self-intersection: "
+
solid
.
containsAnyError
());
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/correctness/RingSelfIntCheckBvhVariantCityGmlTest.java
View file @
a7ed4b64
...
@@ -44,17 +44,11 @@ public class RingSelfIntCheckBvhVariantCityGmlTest {
...
@@ -44,17 +44,11 @@ public class RingSelfIntCheckBvhVariantCityGmlTest {
throws
CityGmlParseException
,
InvalidGmlFileException
{
throws
CityGmlParseException
,
InvalidGmlFileException
{
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
long
start
=
System
.
nanoTime
();
int
oldCount
=
runCheckAndCountErrors
(
geometryOld
,
RingSelfIntCheck
.
Variant
.
OLD
);
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
())
{
for
(
RingSelfIntCheck
.
Variant
bvhVariant
:
bvhVariants
())
{
Geometry
geometryBvh
=
parseGeometry
(
TEST_GML
);
Geometry
geometryBvh
=
parseGeometry
(
TEST_GML
);
start
=
System
.
nanoTime
();
int
bvhCount
=
runCheckAndCountErrors
(
geometryBvh
,
bvhVariant
);
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
);
assertEquals
(
"OLD vs "
+
bvhVariant
+
" differs"
,
oldCount
,
bvhCount
);
}
}
}
}
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/correctness/SolidSelfIntersectionBuildingTest.java
View file @
a7ed4b64
...
@@ -57,8 +57,6 @@ public class SolidSelfIntersectionBuildingTest {
...
@@ -57,8 +57,6 @@ public class SolidSelfIntersectionBuildingTest {
List
<
Polygon
>
candidates
=
tree
.
getAllIntersectingElements
(
probeAabb
);
List
<
Polygon
>
candidates
=
tree
.
getAllIntersectingElements
(
probeAabb
);
int
nonEmptyQueries
=
0
;
int
nonEmptyQueries
=
0
;
int
totalCandidates
=
0
;
int
maxCandidates
=
0
;
for
(
Polygon
polygon
:
polygons
)
{
for
(
Polygon
polygon
:
polygons
)
{
AABB
query
=
AABB
.
of
(
polygon
.
getOriginal
());
AABB
query
=
AABB
.
of
(
polygon
.
getOriginal
());
assertNotNull
(
"Query AABB must not be null for "
+
lodLabel
,
query
);
assertNotNull
(
"Query AABB must not be null for "
+
lodLabel
,
query
);
...
@@ -66,20 +64,9 @@ public class SolidSelfIntersectionBuildingTest {
...
@@ -66,20 +64,9 @@ public class SolidSelfIntersectionBuildingTest {
if
(!
perPolygonCandidates
.
isEmpty
())
{
if
(!
perPolygonCandidates
.
isEmpty
())
{
nonEmptyQueries
++;
nonEmptyQueries
++;
}
}
int
currentSize
=
perPolygonCandidates
.
size
();
totalCandidates
+=
currentSize
;
if
(
currentSize
>
maxCandidates
)
{
maxCandidates
=
currentSize
;
}
}
}
printBvhStats
(
assertFalse
(
"Expected the probe polygon to return BVH candidates on "
+
lodLabel
,
candidates
.
isEmpty
());
lodLabel
,
polygons
.
size
(),
candidates
.
size
(),
nonEmptyQueries
,
totalCandidates
,
maxCandidates
);
assertTrue
(
assertTrue
(
"Expected at least one non-empty BVH query on "
+
lodLabel
,
"Expected at least one non-empty BVH query on "
+
lodLabel
,
nonEmptyQueries
>
0
);
nonEmptyQueries
>
0
);
...
@@ -102,7 +89,6 @@ public class SolidSelfIntersectionBuildingTest {
...
@@ -102,7 +89,6 @@ public class SolidSelfIntersectionBuildingTest {
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
geometry
,
DELTA
);
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
geometry
,
DELTA
);
assertNotNull
(
"New result list must not be null for "
+
lodLabel
,
newRes
);
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 external-tree differs for "
+
lodLabel
,
oldRes
.
size
(),
oldTreeRes
.
size
());
assertEquals
(
"Old vs new-tree differs for "
+
lodLabel
,
oldRes
.
size
(),
newRes
.
size
());
assertEquals
(
"Old vs new-tree differs for "
+
lodLabel
,
oldRes
.
size
(),
newRes
.
size
());
}
}
...
@@ -118,39 +104,6 @@ public class SolidSelfIntersectionBuildingTest {
...
@@ -118,39 +104,6 @@ public class SolidSelfIntersectionBuildingTest {
return
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
()
{
private
Building
createDenseBuildingWithoutIntersections
()
{
Building
b
=
new
Building
();
Building
b
=
new
Building
();
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD2
,
4
,
4
,
false
));
b
.
addGeometry
(
createGridGeometry
(
Lod
.
LOD2
,
4
,
4
,
false
));
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/correctness/SolidSelfIntersectionOldVsNewTest.java
View file @
a7ed4b64
...
@@ -58,14 +58,9 @@ public class SolidSelfIntersectionOldVsNewTest {
...
@@ -58,14 +58,9 @@ public class SolidSelfIntersectionOldVsNewTest {
assertTrue
(
"Expected at least 2 polygons in: "
+
gmlPath
,
polys
.
size
()
>
1
);
assertTrue
(
"Expected at least 2 polygons in: "
+
gmlPath
,
polys
.
size
()
>
1
);
/// Without Tree
/// Without Tree
long
start
=
System
.
nanoTime
();
List
<
PolygonIntersection
>
oldRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection0
(
g
,
delta
);
List
<
PolygonIntersection
>
oldRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection0
(
g
,
delta
);
long
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Alt: "
+
dif
);
/// With IdentityHashMap
/// With IdentityHashMap
start
=
System
.
nanoTime
();
BoundingVolumeHierarchyTree
<
Polygon
>
polygonTree
=
BoundingVolumeHierarchyTree
<
Polygon
>
polygonTree
=
BoundingVolumeHierarchyTree
.
newBinary
(
BoundingVolumeHierarchyTree
.
newBinary
(
g
.
getPolygons
(),
g
.
getPolygons
(),
...
@@ -75,20 +70,10 @@ public class SolidSelfIntersectionOldVsNewTest {
...
@@ -75,20 +70,10 @@ public class SolidSelfIntersectionOldVsNewTest {
List
<
PolygonIntersection
>
oldTreeRes
=
List
<
PolygonIntersection
>
oldTreeRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
,
polygonTree
);
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
,
polygonTree
);
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Alt + external polygon tree: "
+
dif
);
/// With Polygon Indices
/// With Polygon Indices
start
=
System
.
nanoTime
();
List
<
PolygonIntersection
>
newRes
=
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
);
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
);
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Neu: "
+
dif
);
///
System
.
out
.
println
(
"oldRes.size() = "
+
oldRes
.
size
());
System
.
out
.
println
(
"oldTreeRes.size() = "
+
oldTreeRes
.
size
());
System
.
out
.
println
(
"newRes.size() = "
+
newRes
.
size
());
assertEquals
(
"Old vs oldTree differs for: "
+
gmlPath
,
assertEquals
(
"Old vs oldTree differs for: "
+
gmlPath
,
oldRes
.
size
(),
oldTreeRes
.
size
());
oldRes
.
size
(),
oldTreeRes
.
size
());
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/fixtures/BvhSyntheticScenarioCatalog.java
deleted
100644 → 0
View file @
1d9c7810
package
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures
;
import
java.util.List
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
/**
* Central catalog of synthetic BVH exploration scenarios.
*
* The scenarios intentionally vary count, thinness, relative box volume, center
* spread, and city-like clustering so heuristic experiments can compare metric
* ranges without duplicating fixture lists in several tests.
*
* @author Numanoglu
*/
public
final
class
BvhSyntheticScenarioCatalog
{
private
static
final
double
EPSILON
=
0.001
;
private
BvhSyntheticScenarioCatalog
()
{
}
public
static
List
<
SsiScenario
>
ssiScenarios
()
{
return
List
.
of
(
new
SsiScenario
(
"sep-grid-small"
,
SyntheticSolidGeometryFactory
.
separatedBoxGrid
(
Lod
.
LOD2
,
6
,
6
)),
new
SsiScenario
(
"sep-grid-medium"
,
SyntheticSolidGeometryFactory
.
separatedBoxGrid
(
Lod
.
LOD2
,
12
,
12
)),
new
SsiScenario
(
"overlap-grid"
,
SyntheticSolidGeometryFactory
.
overlappingBoxGrid
(
Lod
.
LOD2
,
12
,
12
)),
new
SsiScenario
(
"dense-clusters-small"
,
SyntheticSolidGeometryFactory
.
denseBoxClusters
(
Lod
.
LOD2
,
4
,
20
)),
new
SsiScenario
(
"dense-clusters-large"
,
SyntheticSolidGeometryFactory
.
denseBoxClusters
(
Lod
.
LOD2
,
10
,
24
)),
new
SsiScenario
(
"long-thin-slabs-small"
,
SyntheticSolidGeometryFactory
.
longThinSlabs
(
Lod
.
LOD2
,
80
)),
new
SsiScenario
(
"long-thin-slabs-large"
,
SyntheticSolidGeometryFactory
.
longThinSlabs
(
Lod
.
LOD2
,
240
)),
new
SsiScenario
(
"flat-box-grid-small"
,
SyntheticSolidGeometryFactory
.
flatBoxGrid
(
Lod
.
LOD2
,
80
)),
new
SsiScenario
(
"flat-box-grid-large"
,
SyntheticSolidGeometryFactory
.
flatBoxGrid
(
Lod
.
LOD2
,
240
)),
new
SsiScenario
(
"thin-ratio-025"
,
SyntheticSolidGeometryFactory
.
thinRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
0.25
)),
new
SsiScenario
(
"thin-ratio-050"
,
SyntheticSolidGeometryFactory
.
thinRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
0.50
)),
new
SsiScenario
(
"thin-ratio-075"
,
SyntheticSolidGeometryFactory
.
thinRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
0.75
)),
new
SsiScenario
(
"aspect-005"
,
SyntheticSolidGeometryFactory
.
aspectRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
5.0
)),
new
SsiScenario
(
"aspect-020"
,
SyntheticSolidGeometryFactory
.
aspectRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
20.0
)),
new
SsiScenario
(
"aspect-080"
,
SyntheticSolidGeometryFactory
.
aspectRatioVariationBoxes
(
Lod
.
LOD2
,
120
,
80.0
)),
new
SsiScenario
(
"spread-tight"
,
SyntheticSolidGeometryFactory
.
centerSpreadVariationBoxes
(
Lod
.
LOD2
,
120
,
3.5
)),
new
SsiScenario
(
"spread-wide"
,
SyntheticSolidGeometryFactory
.
centerSpreadVariationBoxes
(
Lod
.
LOD2
,
120
,
20.0
)),
new
SsiScenario
(
"relvol-compact"
,
SyntheticSolidGeometryFactory
.
relativeVolumeVariationBoxes
(
Lod
.
LOD2
,
80
,
5.0
,
1.1
)),
new
SsiScenario
(
"relvol-sparse"
,
SyntheticSolidGeometryFactory
.
relativeVolumeVariationBoxes
(
Lod
.
LOD2
,
80
,
5.0
,
5.0
)),
new
SsiScenario
(
"citylike-mixed-small"
,
SyntheticCityGmlLikeGeometryFactory
.
mixedUrbanDistrict
(
Lod
.
LOD2
,
3
,
3
)),
new
SsiScenario
(
"citylike-mixed-large"
,
SyntheticCityGmlLikeGeometryFactory
.
mixedUrbanDistrict
(
Lod
.
LOD2
,
6
,
5
)),
new
SsiScenario
(
"citylike-courtyard"
,
SyntheticCityGmlLikeGeometryFactory
.
courtyardDistrict
(
Lod
.
LOD2
,
16
)),
new
SsiScenario
(
"citylike-roofs-small"
,
SyntheticCityGmlLikeGeometryFactory
.
variedRoofDistrict
(
Lod
.
LOD2
,
80
)),
new
SsiScenario
(
"citylike-roofs-large"
,
SyntheticCityGmlLikeGeometryFactory
.
variedRoofDistrict
(
Lod
.
LOD2
,
240
)));
}
public
static
List
<
NestedScenario
>
nestedScenarios
()
{
return
List
.
of
(
new
NestedScenario
(
"disjoint-small"
,
SyntheticNestedRingGeometryFactory
.
manyDisjointInnerRings
(
120
)),
new
NestedScenario
(
"disjoint-large"
,
SyntheticNestedRingGeometryFactory
.
manyDisjointInnerRings
(
1_000
)),
new
NestedScenario
(
"one-pair-small"
,
SyntheticNestedRingGeometryFactory
.
oneNestedPairAmongMany
(
120
)),
new
NestedScenario
(
"one-pair-large"
,
SyntheticNestedRingGeometryFactory
.
oneNestedPairAmongMany
(
1_000
)),
new
NestedScenario
(
"concentric-small"
,
SyntheticNestedRingGeometryFactory
.
concentricNestedRings
(
80
)),
new
NestedScenario
(
"concentric-large"
,
SyntheticNestedRingGeometryFactory
.
concentricNestedRings
(
300
)),
new
NestedScenario
(
"overlap-no-error"
,
SyntheticNestedRingGeometryFactory
.
overlappingAabbsButNotNested
(
1_000
)),
new
NestedScenario
(
"clustered-small"
,
SyntheticNestedRingGeometryFactory
.
clusteredInnerRings
(
8
,
40
)),
new
NestedScenario
(
"clustered-large"
,
SyntheticNestedRingGeometryFactory
.
clusteredInnerRings
(
20
,
80
)),
new
NestedScenario
(
"clustered-pair"
,
SyntheticNestedRingGeometryFactory
.
clusteredInnerRingsWithNestedPair
(
16
,
80
)),
new
NestedScenario
(
"aspect-005"
,
SyntheticNestedRingGeometryFactory
.
aspectRatioVariationInnerRings
(
180
,
5.0
)),
new
NestedScenario
(
"aspect-020"
,
SyntheticNestedRingGeometryFactory
.
aspectRatioVariationInnerRings
(
180
,
20.0
)),
new
NestedScenario
(
"aspect-080"
,
SyntheticNestedRingGeometryFactory
.
aspectRatioVariationInnerRings
(
180
,
80.0
)),
new
NestedScenario
(
"spread-tight"
,
SyntheticNestedRingGeometryFactory
.
centerSpreadVariationInnerRings
(
240
,
2.0
)),
new
NestedScenario
(
"spread-wide"
,
SyntheticNestedRingGeometryFactory
.
centerSpreadVariationInnerRings
(
240
,
20.0
)));
}
public
static
List
<
RsiScenario
>
rsiScenarios
()
{
return
List
.
of
(
new
RsiScenario
(
"rsi-small"
,
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
200
,
100
,
EPSILON
)),
new
RsiScenario
(
"rsi-medium"
,
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
1_000
,
500
,
EPSILON
)),
new
RsiScenario
(
"rsi-large"
,
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
5_000
,
2_000
,
EPSILON
)),
new
RsiScenario
(
"rsi-convex-heavy"
,
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
6_000
,
200
,
EPSILON
)),
new
RsiScenario
(
"rsi-zigzag-heavy"
,
SyntheticRingGeometryFactory
.
performanceRingGeometry
(
400
,
4_000
,
EPSILON
)),
new
RsiScenario
(
"rsi-aspect-005"
,
SyntheticRingGeometryFactory
.
aspectRatioVariationGeometry
(
240
,
5.0
)),
new
RsiScenario
(
"rsi-aspect-020"
,
SyntheticRingGeometryFactory
.
aspectRatioVariationGeometry
(
240
,
20.0
)),
new
RsiScenario
(
"rsi-aspect-080"
,
SyntheticRingGeometryFactory
.
aspectRatioVariationGeometry
(
240
,
80.0
)),
new
RsiScenario
(
"rsi-spread-tight"
,
SyntheticRingGeometryFactory
.
centerSpreadVariationGeometry
(
240
,
4.0
)),
new
RsiScenario
(
"rsi-spread-wide"
,
SyntheticRingGeometryFactory
.
centerSpreadVariationGeometry
(
240
,
30.0
)));
}
public
static
final
class
SsiScenario
{
public
final
String
name
;
public
final
Geometry
geometry
;
SsiScenario
(
String
name
,
Geometry
geometry
)
{
this
.
name
=
name
;
this
.
geometry
=
geometry
;
}
}
public
static
final
class
NestedScenario
{
public
final
String
name
;
public
final
ConcretePolygon
polygon
;
NestedScenario
(
String
name
,
ConcretePolygon
polygon
)
{
this
.
name
=
name
;
this
.
polygon
=
polygon
;
}
}
public
static
final
class
RsiScenario
{
public
final
String
name
;
public
final
Geometry
geometry
;
RsiScenario
(
String
name
,
Geometry
geometry
)
{
this
.
name
=
name
;
this
.
geometry
=
geometry
;
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/fixtures/SyntheticCityGmlLikeGeometryFactory.java
View file @
a7ed4b64
...
@@ -10,8 +10,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod;
...
@@ -10,8 +10,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
/**
/**
* Synthetic but citygml-like geometry fixtures for BVH metric and performance
* Synthetic but citygml-like geometry fixtures for BVH comparison tests.
* experiments.
*
*
* The goal is not to serialize valid CityGML, but to mimic typical model
* The goal is not to serialize valid CityGML, but to mimic typical model
* structure more closely than pure grids: mixed building footprints, varying
* structure more closely than pure grids: mixed building footprints, varying
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/fixtures/SyntheticRingGeometryFactory.java
View file @
a7ed4b64
package
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures
;
package
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures
;
import
java.io.File
;
import
org.citygml4j.core.model.CityGMLVersion
;
import
org.citygml4j.core.model.core.CityModel
;
import
org.junit.jupiter.api.Disabled
;
import
org.junit.jupiter.api.Test
;
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.ConcretePolygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType
;
import
de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
...
@@ -35,10 +23,10 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
...
@@ -35,10 +23,10 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
* vertex close to a non-adjacent edge, useful for epsilon-sensitive checks.
* vertex close to a non-adjacent edge, useful for epsilon-sensitive checks.
* - largeConvexRing:
* - largeConvexRing:
* many edges without self-intersection, useful for
candidate-pruning cost
.
* many edges without self-intersection, useful for
larger ring inputs
.
* - zigZagCorridor:
* - zigZagCorridor:
* many nearby but non-crossing segments, useful
as
AABB
broad-phase stress
.
* many nearby but non-crossing segments, useful
for
AABB
candidate filtering
.
*
*
* @author Numanoglu
* @author Numanoglu
*/
*/
...
@@ -58,7 +46,7 @@ public final class SyntheticRingGeometryFactory {
...
@@ -58,7 +46,7 @@ public final class SyntheticRingGeometryFactory {
return
geometry
;
return
geometry
;
}
}
public
static
Geometry
performanc
eRingGeometry
(
int
convexVertexCount
,
int
zigZagSegments
,
double
epsilon
)
{
public
static
Geometry
larg
eRingGeometry
(
int
convexVertexCount
,
int
zigZagSegments
,
double
epsilon
)
{
Geometry
geometry
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Orientation
.
OUTWARD
);
Geometry
geometry
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Orientation
.
OUTWARD
);
addRingPolygon
(
geometry
,
largeConvexRing
(
convexVertexCount
,
0.0
,
0.0
,
0.0
));
addRingPolygon
(
geometry
,
largeConvexRing
(
convexVertexCount
,
0.0
,
0.0
,
0.0
));
addRingPolygon
(
geometry
,
zigZagCorridor
(
zigZagSegments
));
addRingPolygon
(
geometry
,
zigZagCorridor
(
zigZagSegments
));
...
@@ -68,29 +56,6 @@ public final class SyntheticRingGeometryFactory {
...
@@ -68,29 +56,6 @@ public final class SyntheticRingGeometryFactory {
return
geometry
;
return
geometry
;
}
}
public
static
Geometry
aspectRatioVariationGeometry
(
int
ringCount
,
double
aspectRatio
)
{
Geometry
geometry
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Orientation
.
OUTWARD
);
double
width
=
Math
.
max
(
1.0
,
aspectRatio
);
for
(
int
i
=
0
;
i
<
ringCount
;
i
++)
{
double
x
=
(
i
%
20
)
*
(
width
+
4.0
);
double
y
=
(
i
/
20
)
*
5.0
;
addRingPolygon
(
geometry
,
rectangle
(
x
,
y
,
width
,
1.0
));
}
geometry
.
updateEdgesAndVertices
();
return
geometry
;
}
public
static
Geometry
centerSpreadVariationGeometry
(
int
ringCount
,
double
spacing
)
{
Geometry
geometry
=
new
Geometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
,
Orientation
.
OUTWARD
);
for
(
int
i
=
0
;
i
<
ringCount
;
i
++)
{
double
x
=
(
i
%
20
)
*
spacing
;
double
y
=
(
i
/
20
)
*
spacing
;
addRingPolygon
(
geometry
,
rectangle
(
x
,
y
,
3.0
,
3.0
));
}
geometry
.
updateEdgesAndVertices
();
return
geometry
;
}
private
static
void
addRingPolygon
(
Geometry
geometry
,
double
[][]
coordinates
)
{
private
static
void
addRingPolygon
(
Geometry
geometry
,
double
[][]
coordinates
)
{
ConcretePolygon
polygon
=
new
ConcretePolygon
();
ConcretePolygon
polygon
=
new
ConcretePolygon
();
LinearRing
ring
=
new
LinearRing
(
LinearRingType
.
EXTERIOR
);
LinearRing
ring
=
new
LinearRing
(
LinearRingType
.
EXTERIOR
);
...
@@ -185,21 +150,4 @@ public final class SyntheticRingGeometryFactory {
...
@@ -185,21 +150,4 @@ public final class SyntheticRingGeometryFactory {
coordinates
[
index
]
=
new
double
[]
{
60.0
,
0.0
,
0.0
};
coordinates
[
index
]
=
new
double
[]
{
60.0
,
0.0
,
0.0
};
return
coordinates
;
return
coordinates
;
}
}
@Test
@Disabled
public
void
createCityGML
()
throws
CityDoctorWriteException
{
ParserConfiguration
config
=
new
ParserConfiguration
(
8
,
false
);
CityDoctorModel
model
=
new
CityDoctorModel
(
config
,
new
File
(
""
),
new
UnconnectedCache
());
model
.
setCityModel
(
new
CityModel
());
model
.
setParsedCityGMLVersion
(
CityGMLVersion
.
v2_0
);
Building
b
=
new
Building
();
b
.
setGmlObject
(
new
org
.
citygml4j
.
core
.
model
.
building
.
Building
());
b
.
getGeometries
().
add
(
performanceRingGeometry
(
1_000
,
500
,
0.0001
));
model
.
addBuilding
(
b
);
model
.
saveAs
(
"output.gml"
,
false
);
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/heuristics/BvhStrategyHeuristicExplorationTest.java
deleted
100644 → 0
View file @
1d9c7810
package
de.hft.stuttgart.citydoctor2.checks.aabb.heuristics
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.BvhSyntheticScenarioCatalog
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhExplorationCsvWriter
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhHeuristicTimingSupport
;
import
de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhInputMetricsCollector
;
import
java.io.IOException
;
import
java.nio.file.Path
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
org.junit.jupiter.api.Disabled
;
import
org.junit.jupiter.api.Tag
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.RingSelfIntCheck
;
import
de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
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
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy
;
/**
* Collects compact timing observations and broad-phase metrics for BVH strategy
* selection from synthetic fixtures.
*
* The synthetic scenarios are the calibration side of the heuristic workflow:
* they deliberately vary shape and distribution properties so candidate rules
* can be proposed before they are checked against real CityGML data.
*
* @author Numanoglu
*/
@Tag
(
"performance"
)
public
class
BvhStrategyHeuristicExplorationTest
{
private
static
final
double
DELTA
=
0.001
;
private
static
final
double
EPSILON
=
0.001
;
private
static
final
Path
OUTPUT_DIRECTORY
=
Path
.
of
(
"target"
,
"bvh-exploration"
);
@Test
@Disabled
public
void
exploreSsiNestedAndRsiStrategyCandidates
()
throws
IOException
{
// Keep all three checks in one table so metric trends can be compared side by side.
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
=
new
ArrayList
<>();
addSsiObservations
(
observations
);
addNestedObservations
(
observations
);
addRsiObservations
(
observations
);
printObservationSummary
(
observations
);
List
<
BvhExplorationCsvWriter
.
BucketSummaryRecord
>
bucketSummary
=
printBucketSummary
(
observations
);
BvhExplorationCsvWriter
.
writeObservations
(
OUTPUT_DIRECTORY
.
resolve
(
"synthetic_observations.csv"
),
toSyntheticRecords
(
observations
));
BvhExplorationCsvWriter
.
writeBucketSummary
(
OUTPUT_DIRECTORY
.
resolve
(
"synthetic_bucket_summary.csv"
),
bucketSummary
);
}
private
static
List
<
BvhExplorationCsvWriter
.
ObservationRecord
>
toSyntheticRecords
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
// Synthetic observations are labelled as calibration data, not as final evaluation data.
List
<
BvhExplorationCsvWriter
.
ObservationRecord
>
records
=
new
ArrayList
<>(
observations
.
size
());
for
(
BvhHeuristicTimingSupport
.
Observation
observation
:
observations
)
{
records
.
add
(
new
BvhExplorationCsvWriter
.
ObservationRecord
(
"synthetic"
,
"calibration"
,
"synthetic-scenario-catalog"
,
"controlled-synthetic-scenarios"
,
BvhExplorationCsvWriter
.
CANDIDATE_POLICY_VERSION
,
observation
));
}
return
records
;
}
private
static
void
addSsiObservations
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
// SSI uses polygons as BVH elements.
for
(
BvhSyntheticScenarioCatalog
.
SsiScenario
scenario
:
BvhSyntheticScenarioCatalog
.
ssiScenarios
())
{
List
<
Polygon
>
polygons
=
scenario
.
geometry
.
getPolygons
();
BvhInputMetricsCollector
.
Metrics
metrics
=
BvhInputMetricsCollector
.
forPolygonsCheap
(
scenario
.
name
,
polygons
);
observations
.
add
(
BvhHeuristicTimingSupport
.
measureVariants
(
"SSI"
,
scenario
.
name
,
metrics
,
polygons
.
size
(),
strategy
->
calculateSsiWithTree
(
scenario
.
geometry
,
strategy
)));
}
}
private
static
void
addNestedObservations
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
// Nested rings use inner rings as searchable elements; the exact containment check stays unchanged.
for
(
BvhSyntheticScenarioCatalog
.
NestedScenario
scenario
:
BvhSyntheticScenarioCatalog
.
nestedScenarios
())
{
List
<
LinearRing
>
rings
=
scenario
.
polygon
.
getInnerRings
();
BvhInputMetricsCollector
.
Metrics
metrics
=
BvhInputMetricsCollector
.
forRingsCheap
(
scenario
.
name
,
rings
);
observations
.
add
(
BvhHeuristicTimingSupport
.
measureVariants
(
"NESTED"
,
scenario
.
name
,
metrics
,
rings
.
size
(),
strategy
->
runNestedCheck
(
scenario
.
polygon
,
BvhHeuristicTimingSupport
.
nestedVariantFor
(
strategy
))));
}
}
private
static
void
addRsiObservations
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
// RSI broad-phase behavior is driven by edge boxes rather than whole polygon boxes.
for
(
BvhSyntheticScenarioCatalog
.
RsiScenario
scenario
:
BvhSyntheticScenarioCatalog
.
rsiScenarios
())
{
BvhInputMetricsCollector
.
Metrics
metrics
=
BvhInputMetricsCollector
.
collectBoxesCheap
(
scenario
.
name
,
collectEdgeBoxes
(
scenario
.
geometry
));
observations
.
add
(
BvhHeuristicTimingSupport
.
measureVariants
(
"RSI"
,
scenario
.
name
,
metrics
,
countEdges
(
scenario
.
geometry
),
strategy
->
runRsiCheck
(
scenario
.
geometry
,
BvhHeuristicTimingSupport
.
rsiVariantFor
(
strategy
))));
}
}
private
static
int
calculateSsiWithTree
(
Geometry
geometry
,
SplitStrategy
strategy
)
{
// The tree indexes original polygons; SelfIntersectionUtil still performs the exact intersection checks.
BoundingVolumeHierarchyTree
<
Polygon
>
tree
=
BoundingVolumeHierarchyTree
.
newWithStrategy
(
geometry
.
getPolygons
(),
polygon
->
AABB
.
of
(
polygon
.
getOriginal
()),
strategy
);
return
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
geometry
,
DELTA
,
tree
).
size
();
}
private
static
int
runNestedCheck
(
ConcretePolygon
polygon
,
NestedRingsCheck
.
Variant
variant
)
{
NestedRingsCheck
check
=
new
NestedRingsCheck
(
variant
);
check
.
check
(
polygon
);
CheckResult
result
=
polygon
.
getCheckResult
(
check
);
return
result
.
getResultStatus
()
==
ResultStatus
.
ERROR
?
1
:
0
;
}
private
static
int
runRsiCheck
(
Geometry
geometry
,
RingSelfIntCheck
.
Variant
variant
)
{
int
errorCount
=
0
;
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
RingSelfIntCheck
check
=
new
RingSelfIntCheck
(
variant
);
check
.
init
(
Collections
.
singletonMap
(
"minVertexDistance"
,
String
.
valueOf
(
EPSILON
)),
null
);
check
.
check
(
polygon
.
getExteriorRing
());
CheckResult
result
=
polygon
.
getExteriorRing
().
getCheckResult
(
check
);
if
(
result
.
getResultStatus
()
==
ResultStatus
.
ERROR
)
{
errorCount
++;
}
}
return
errorCount
;
}
private
static
int
countEdges
(
Geometry
geometry
)
{
int
edgeCount
=
0
;
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
LinearRing
ring
=
polygon
.
getExteriorRing
();
edgeCount
+=
Math
.
max
(
0
,
ring
.
getVertices
().
size
()
-
1
);
}
return
edgeCount
;
}
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
List
<
AABB
>
collectEdgeBoxes
(
Geometry
geometry
)
{
List
<
AABB
>
boxes
=
new
ArrayList
<>();
for
(
LinearRing
ring
:
collectExteriorRings
(
geometry
))
{
List
<
Vertex
>
vertices
=
ring
.
getVertices
();
for
(
int
i
=
0
;
i
<
vertices
.
size
()
-
1
;
i
++)
{
boxes
.
add
(
AABB
.
of
(
vertices
.
get
(
i
),
vertices
.
get
(
i
+
1
),
EPSILON
));
}
}
return
boxes
;
}
private
static
void
printObservationSummary
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
System
.
out
.
println
();
System
.
out
.
println
(
"[BVH-HEURISTIC-EXPLORATION]"
);
System
.
out
.
println
(
"-------------------------------------------------------------------------------------------------------------------------------"
);
System
.
out
.
println
(
String
.
format
(
"%-7s %-24s %7s %7s %8s %7s %8s %24s %24s %24s %10s"
,
"check"
,
"scenario"
,
"n"
,
"thin"
,
"relVol"
,
"spread"
,
"aspect"
,
"policyPred"
,
"candidatePred"
,
"fastest BVH"
,
"bvh ms"
));
System
.
out
.
println
(
"-------------------------------------------------------------------------------------------------------------------------------"
);
for
(
BvhHeuristicTimingSupport
.
Observation
observation
:
observations
)
{
BvhInputMetricsCollector
.
Metrics
metrics
=
observation
.
metrics
;
System
.
out
.
println
(
String
.
format
(
"%-7s %-24s %7d %7.3f %8.5f %7.3f %8.1f %24s %24s %24s %10.3f"
,
observation
.
checkName
,
metrics
.
scenario
,
metrics
.
elementCount
,
metrics
.
thinBoxRate
,
metrics
.
averageRelativeBoxVolume
,
metrics
.
centerSpreadRatio
,
metrics
.
averageAspectRatio
,
observation
.
currentPolicyPrediction
,
observation
.
candidateRulePrediction
,
observation
.
fastestBvh
.
variant
,
observation
.
fastestBvh
.
averageMillis
()));
}
System
.
out
.
println
(
"-------------------------------------------------------------------------------------------------------------------------------"
);
System
.
out
.
println
(
"Fastest BVH is the target for candidate strategy rules."
);
System
.
out
.
println
();
}
private
static
List
<
BvhExplorationCsvWriter
.
BucketSummaryRecord
>
printBucketSummary
(
List
<
BvhHeuristicTimingSupport
.
Observation
>
observations
)
{
// Buckets reduce continuous metrics to readable intervals for first-pass rule discovery.
Map
<
String
,
BucketStats
>
buckets
=
new
LinkedHashMap
<>();
for
(
BvhHeuristicTimingSupport
.
Observation
observation
:
observations
)
{
addBucket
(
buckets
,
observation
,
"thin"
,
thinBucket
(
observation
.
metrics
.
thinBoxRate
));
addBucket
(
buckets
,
observation
,
"relVol"
,
relVolBucket
(
observation
.
metrics
.
averageRelativeBoxVolume
));
addBucket
(
buckets
,
observation
,
"spread"
,
spreadBucket
(
observation
.
metrics
.
centerSpreadRatio
));
addBucket
(
buckets
,
observation
,
"aspect"
,
aspectBucket
(
observation
.
metrics
.
averageAspectRatio
));
}
System
.
out
.
println
(
"[BVH-WINNER-BY-METRIC-BUCKET]"
);
System
.
out
.
println
(
"-------------------------------------------------------------------------------"
);
System
.
out
.
println
(
String
.
format
(
"%-7s %-7s %-14s %7s %24s %8s"
,
"check"
,
"metric"
,
"bucket"
,
"cases"
,
"mostCommonBvh"
,
"share"
));
System
.
out
.
println
(
"-------------------------------------------------------------------------------"
);
for
(
BucketStats
bucket
:
buckets
.
values
())
{
System
.
out
.
println
(
String
.
format
(
"%-7s %-7s %-14s %7d %24s %7.1f%%"
,
bucket
.
checkName
,
bucket
.
metricName
,
bucket
.
bucketName
,
bucket
.
total
,
bucket
.
mostCommonWinner
(),
bucket
.
mostCommonShare
()
*
100.0
));
}
System
.
out
.
println
();
System
.
out
.
println
(
"[BVH-CANDIDATE-RULES]"
);
System
.
out
.
println
(
"Minimum support: 3 cases; minimum winner share: 65%."
);
for
(
BucketStats
bucket
:
buckets
.
values
())
{
if
(
bucket
.
total
>=
3
&&
bucket
.
mostCommonShare
()
>=
0.65
)
{
System
.
out
.
println
(
String
.
format
(
"if check=%s and %s in %s -> %s (%d cases, %.1f%%)"
,
bucket
.
checkName
,
bucket
.
metricName
,
bucket
.
bucketName
,
bucket
.
mostCommonWinner
(),
bucket
.
total
,
bucket
.
mostCommonShare
()
*
100.0
));
}
}
System
.
out
.
println
();
List
<
BvhExplorationCsvWriter
.
BucketSummaryRecord
>
summary
=
new
ArrayList
<>(
buckets
.
size
());
for
(
BucketStats
bucket
:
buckets
.
values
())
{
summary
.
add
(
bucket
.
toRecord
());
}
return
summary
;
}
private
static
void
addBucket
(
Map
<
String
,
BucketStats
>
buckets
,
BvhHeuristicTimingSupport
.
Observation
observation
,
String
metricName
,
String
bucketName
)
{
String
key
=
observation
.
checkName
+
"|"
+
metricName
+
"|"
+
bucketName
;
BucketStats
bucket
=
buckets
.
computeIfAbsent
(
key
,
ignored
->
new
BucketStats
(
observation
.
checkName
,
metricName
,
bucketName
));
bucket
.
add
(
observation
.
fastestBvh
.
variant
);
}
private
static
String
thinBucket
(
double
value
)
{
if
(
value
==
0.0
)
{
return
"0"
;
}
if
(
value
<=
0.25
)
{
return
"(0,0.25]"
;
}
if
(
value
<=
0.50
)
{
return
"(0.25,0.50]"
;
}
if
(
value
<=
0.75
)
{
return
"(0.50,0.75]"
;
}
return
"(0.75,1]"
;
}
private
static
String
relVolBucket
(
double
value
)
{
if
(
value
<=
0.00001
)
{
return
"<=1e-5"
;
}
if
(
value
<=
0.0001
)
{
return
"<=1e-4"
;
}
if
(
value
<=
0.001
)
{
return
"<=1e-3"
;
}
if
(
value
<=
0.01
)
{
return
"<=1e-2"
;
}
return
">1e-2"
;
}
private
static
String
spreadBucket
(
double
value
)
{
if
(
value
<
0.15
)
{
return
"<0.15"
;
}
if
(
value
<
0.25
)
{
return
"0.15..0.25"
;
}
if
(
value
<
0.35
)
{
return
"0.25..0.35"
;
}
return
">=0.35"
;
}
private
static
String
aspectBucket
(
double
value
)
{
if
(
value
<
5.0
)
{
return
"<5"
;
}
if
(
value
<
20.0
)
{
return
"5..20"
;
}
if
(
value
<
80.0
)
{
return
"20..80"
;
}
return
">=80"
;
}
private
static
final
class
BucketStats
{
final
String
checkName
;
final
String
metricName
;
final
String
bucketName
;
final
Map
<
String
,
Integer
>
winners
=
new
LinkedHashMap
<>();
int
total
;
/**
* Counts which split strategy wins inside one metric bucket.
*/
BucketStats
(
String
checkName
,
String
metricName
,
String
bucketName
)
{
this
.
checkName
=
checkName
;
this
.
metricName
=
metricName
;
this
.
bucketName
=
bucketName
;
}
void
add
(
String
winner
)
{
total
++;
winners
.
merge
(
winner
,
1
,
Integer:
:
sum
);
}
String
mostCommonWinner
()
{
String
bestWinner
=
""
;
int
bestCount
=
-
1
;
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
winners
.
entrySet
())
{
if
(
entry
.
getValue
()
>
bestCount
)
{
bestWinner
=
entry
.
getKey
();
bestCount
=
entry
.
getValue
();
}
}
return
bestWinner
;
}
double
mostCommonShare
()
{
if
(
total
==
0
)
{
return
0.0
;
}
return
(
double
)
winners
.
get
(
mostCommonWinner
())
/
total
;
}
int
winnerCount
()
{
return
winners
.
getOrDefault
(
mostCommonWinner
(),
0
);
}
int
secondBestCount
()
{
int
best
=
0
;
int
second
=
0
;
for
(
int
count
:
winners
.
values
())
{
if
(
count
>
best
)
{
second
=
best
;
best
=
count
;
}
else
if
(
count
>
second
)
{
second
=
count
;
}
}
return
second
;
}
double
winnerMargin
()
{
if
(
total
==
0
)
{
return
0.0
;
}
return
(
double
)
(
winnerCount
()
-
secondBestCount
())
/
total
;
}
BvhExplorationCsvWriter
.
BucketSummaryRecord
toRecord
()
{
return
new
BvhExplorationCsvWriter
.
BucketSummaryRecord
(
checkName
,
metricName
,
bucketName
,
total
,
winners
,
mostCommonWinner
(),
winnerCount
(),
mostCommonShare
(),
secondBestCount
(),
winnerMargin
(),
total
>=
3
&&
mostCommonShare
()
>=
0.65
);
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/aabb/heuristics/RealCityGmlBvhHeuristicExplorationTest.java
deleted
100644 → 0
View file @
1d9c7810
This diff is collapsed.
Click to expand it.
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