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
73b1c745
Commit
73b1c745
authored
Apr 10, 2026
by
Numanoglu
Browse files
Redesign BVH and Further Applications and Tests for BVH-Tree
parent
a4c9094e
Pipeline
#12381
failed with stage
in 2 minutes and 15 seconds
Changes
8
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/AABB.java
View file @
73b1c745
...
...
@@ -103,18 +103,57 @@ public class AABB {
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
/*** ---------------Epsilontic---------------------------------------- ***/
/**
* Returns a new AABB expanded by pad in all directions.
* Negative padding is treated as 0.
*/
public
AABB
padded
(
double
pad
)
{
double
p
=
Math
.
max
(
0.0
,
pad
);
return
new
AABB
(
minX
-
p
,
minY
-
p
,
minZ
-
p
,
maxX
+
p
,
maxY
+
p
,
maxZ
+
p
);
}
/** Builds a padded AABB around a segment (two vertices). */
public
static
AABB
of
(
Vertex
a
,
Vertex
b
,
double
pad
)
{
double
minX
=
Math
.
min
(
a
.
getX
(),
b
.
getX
())
-
pad
;
double
minY
=
Math
.
min
(
a
.
getY
(),
b
.
getY
())
-
pad
;
double
minZ
=
Math
.
min
(
a
.
getZ
(),
b
.
getZ
())
-
pad
;
double
maxX
=
Math
.
max
(
a
.
getX
(),
b
.
getX
())
+
pad
;
double
maxY
=
Math
.
max
(
a
.
getY
(),
b
.
getY
())
+
pad
;
double
maxZ
=
Math
.
max
(
a
.
getZ
(),
b
.
getZ
())
+
pad
;
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
/**
* Computes an AABB from a LinearRing and expands it by pad in all directions.
*/
public
static
AABB
of
(
LinearRing
ring
,
double
pad
)
{
return
of
(
ring
).
padded
(
pad
);
}
/**
* Computes an AABB from a Polygon and expands it by pad in all directions.
*/
public
static
AABB
of
(
Polygon
poly
,
double
pad
)
{
return
of
(
poly
).
padded
(
pad
);
}
/**
* Computes a padded AABB for a single point.
* pad == 0 returns the degenerate point AABB.
*/
public
static
AABB
of
(
Vector3d
vertex
,
double
pad
)
{
return
of
(
vertex
).
padded
(
pad
);
}
/**
* Builds a padded AABB around a segment (two vertices).
*/
public
static
AABB
of
(
Vertex
a
,
Vertex
b
,
double
pad
)
{
double
p
=
Math
.
max
(
0.0
,
pad
);
double
minX
=
Math
.
min
(
a
.
getX
(),
b
.
getX
())
-
p
;
double
minY
=
Math
.
min
(
a
.
getY
(),
b
.
getY
())
-
p
;
double
minZ
=
Math
.
min
(
a
.
getZ
(),
b
.
getZ
())
-
p
;
double
maxX
=
Math
.
max
(
a
.
getX
(),
b
.
getX
())
+
p
;
double
maxY
=
Math
.
max
(
a
.
getY
(),
b
.
getY
())
+
p
;
double
maxZ
=
Math
.
max
(
a
.
getZ
(),
b
.
getZ
())
+
p
;
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
//----------------------------------------------------------------------------------------
/**
* Constructs an AABB from explicit min and max coordinates.
*/
...
...
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/bht/BoundingVolumeHierarchyTree.java
View file @
73b1c745
...
...
@@ -4,141 +4,718 @@ import java.util.Comparator;
import
java.util.List
;
import
java.util.ArrayList
;
import
java.util.function.Function
;
import
java.util.Objects
;
import
de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon
;
/**
* Generic AABB Tree implementation using a binary tree structure.
*
* @param <E> The type of elements stored in the tree, which must be able to provide an AABB.
* Generic Bounding Volume Hierarchy (BVH) for AABBs.
*
* Supports:
* - binary BVH (degree 2)
* - octonary / 8-ary BVH (degree 8)
*
* Split strategies:
* - binary: object median, object mean, spatial median
* - octonary: object median, object mean, spatial median
*
* Notes:
* - This implementation caches AABBs and centers during build time via BuildItem.
* - The input list is NOT reordered.
* - The octonary variant is implemented as an 8-ary BVH with tight child AABBs
* built from bucket contents, not as a strict spatial octree with fixed cell boxes.
*
* @param <E> element type
*/
public
class
BoundingVolumeHierarchyTree
<
E
>
{
private
Node
<
E
>
root
;
private
final
Function
<
E
,
AABB
>
aabbFunction
;
private
static
final
int
DEFAULT_MIN_DEPTH
=
4
;
private
static
final
int
DEFAULT_MAX_DEPTH
=
32
;
private
static
final
int
DEFAULT_BINARY_LEAF_SIZE
=
1
;
private
static
final
int
DEFAULT_OCTONARY_LEAF_SIZE
=
8
;
private
static
final
double
DEFAULT_DEGENERATE_TOL
=
1
e
-
12
;
public
enum
SplitStrategy
{
AUTO
,
BINARY_OBJECT_MEDIAN
,
BINARY_OBJECT_MEAN
,
BINARY_SPATIAL_MEDIAN
,
OCTONARY_OBJECT_MEDIAN
,
OCTONARY_OBJECT_MEAN
,
OCTONARY_SPATIAL_MEDIAN
}
public
static
final
class
BuildConfig
{
private
final
int
degree
;
private
final
SplitStrategy
splitStrategy
;
private
final
int
maxLeafSize
;
private
final
int
maxDepth
;
private
final
double
degenerateTolerance
;
private
BuildConfig
(
Builder
b
)
{
this
.
degree
=
b
.
degree
;
this
.
splitStrategy
=
Objects
.
requireNonNull
(
b
.
splitStrategy
,
"splitStrategy"
);
this
.
maxLeafSize
=
b
.
maxLeafSize
;
this
.
maxDepth
=
b
.
maxDepth
;
this
.
degenerateTolerance
=
b
.
degenerateTolerance
;
}
public
int
getDegree
()
{
return
degree
;
}
public
SplitStrategy
getSplitStrategy
()
{
return
splitStrategy
;
}
public
int
getMaxLeafSize
()
{
return
maxLeafSize
;
}
public
int
getMaxDepth
()
{
return
maxDepth
;
}
public
double
getDegenerateTolerance
()
{
return
degenerateTolerance
;
}
public
static
Builder
builder
()
{
return
new
Builder
();
}
public
static
BuildConfig
binaryDefault
()
{
return
builder
()
.
degree
(
2
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
DEFAULT_BINARY_LEAF_SIZE
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
.
build
();
}
public
static
BuildConfig
octonaryDefault
()
{
return
builder
()
.
degree
(
8
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
DEFAULT_OCTONARY_LEAF_SIZE
)
.
maxDepth
(
DEFAULT_MAX_DEPTH
)
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
.
build
();
}
public
static
final
class
Builder
{
private
int
degree
=
2
;
private
SplitStrategy
splitStrategy
=
SplitStrategy
.
AUTO
;
private
int
maxLeafSize
=
DEFAULT_BINARY_LEAF_SIZE
;
private
int
maxDepth
=
DEFAULT_MAX_DEPTH
;
private
double
degenerateTolerance
=
DEFAULT_DEGENERATE_TOL
;
public
Builder
degree
(
int
degree
)
{
this
.
degree
=
degree
;
return
this
;
}
public
Builder
splitStrategy
(
SplitStrategy
splitStrategy
)
{
this
.
splitStrategy
=
splitStrategy
;
return
this
;
}
public
Builder
maxLeafSize
(
int
maxLeafSize
)
{
this
.
maxLeafSize
=
maxLeafSize
;
return
this
;
}
public
Builder
maxDepth
(
int
maxDepth
)
{
this
.
maxDepth
=
maxDepth
;
return
this
;
}
public
Builder
degenerateTolerance
(
double
degenerateTolerance
)
{
this
.
degenerateTolerance
=
degenerateTolerance
;
return
this
;
}
public
BuildConfig
build
()
{
if
(
degree
!=
2
&&
degree
!=
8
)
{
throw
new
IllegalArgumentException
(
"Only degree 2 and 8 are supported."
);
}
if
(
maxLeafSize
<
1
)
{
throw
new
IllegalArgumentException
(
"maxLeafSize must be >= 1."
);
}
if
(
maxDepth
<
1
)
{
throw
new
IllegalArgumentException
(
"maxDepth must be >= 1."
);
}
if
(
degenerateTolerance
<
0.0
)
{
throw
new
IllegalArgumentException
(
"degenerateTolerance must be >= 0."
);
}
return
new
BuildConfig
(
this
);
}
}
}
/**
* Internal build-time cache.
*/
private
static
final
class
BuildItem
<
E
>
{
final
E
element
;
final
AABB
aabb
;
final
double
centerX
;
final
double
centerY
;
final
double
centerZ
;
BuildItem
(
E
element
,
AABB
aabb
)
{
this
.
element
=
element
;
this
.
aabb
=
aabb
;
this
.
centerX
=
0.5
*
(
aabb
.
getMinX
()
+
aabb
.
getMaxX
());
this
.
centerY
=
0.5
*
(
aabb
.
getMinY
()
+
aabb
.
getMaxY
());
this
.
centerZ
=
0.5
*
(
aabb
.
getMinZ
()
+
aabb
.
getMaxZ
());
}
double
center
(
int
axis
)
{
switch
(
axis
)
{
case
0
:
return
centerX
;
case
1
:
return
centerY
;
case
2
:
return
centerZ
;
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
}
}
}
/**
* Creates an empty AABB tree.
*
* @param aabbFunction Function to extract an AABB from an element of type E
* Creates an empty BVH.
*/
public
BoundingVolumeHierarchyTree
(
Function
<
E
,
AABB
>
aabbFunction
)
{
this
.
root
=
null
;
this
.
aabbFunction
=
aabbFunction
;
this
.
aabbFunction
=
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
)
;
}
/**
* Builds the AABB tree from a list of elements.
*
* @param elements The list of elements to insert into the tree
* @param aabbFunction Function to extract an AABB from an element of type E
* Creates a binary BVH with default config (AUTO -> binary spatial median).
*/
public
BoundingVolumeHierarchyTree
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
this
.
aabbFunction
=
aabbFunction
;
this
(
elements
,
aabbFunction
,
BuildConfig
.
binaryDefault
());
}
/**
* Creates a BVH with a full config.
*/
public
BoundingVolumeHierarchyTree
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
,
BuildConfig
config
)
{
this
.
aabbFunction
=
Objects
.
requireNonNull
(
aabbFunction
,
"aabbFunction"
);
Objects
.
requireNonNull
(
config
,
"config"
);
if
(
elements
==
null
||
elements
.
isEmpty
())
{
this
.
root
=
null
;
return
;
}
List
<
BuildItem
<
E
>>
items
=
toBuildItems
(
elements
);
SplitStrategy
resolvedStrategy
=
resolveSplitStrategy
(
config
);
if
(
config
.
getDegree
()
==
2
)
{
this
.
root
=
buildBinaryRecursive
(
items
,
0
,
resolvedStrategy
,
config
);
}
else
{
this
.
root
=
buildRecursive
(
elements
);
}
}
/**
* NOTE: Generic Version all ready exists Calculates the smallest AABB that
* encloses all given polygons
*/
public
static
AABB
getAggregateAABB
(
List
<
ConcretePolygon
>
polygons
)
{
if
(
polygons
.
size
()
==
1
)
{
return
AABB
.
of
(
polygons
.
get
(
0
));
}
return
getAggregateAABB
(
polygons
,
AABB:
:
of
);
}
public
static
<
E
>
AABB
getAggregateAABB
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunc
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
,
minY
=
Double
.
POSITIVE_INFINITY
,
minZ
=
Double
.
POSITIVE_INFINITY
;
double
maxX
=
Double
.
NEGATIVE_INFINITY
,
maxY
=
Double
.
NEGATIVE_INFINITY
,
maxZ
=
Double
.
NEGATIVE_INFINITY
;
for
(
E
e
:
elements
)
{
AABB
aabb
=
aabbFunc
.
apply
(
e
);
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
}
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
this
.
root
=
buildOctonaryRecursive
(
items
,
0
,
resolvedStrategy
,
config
);
}
}
/**
*
Returns the root node of
th
e
A
ABB tree
.
*
Convenience factory: binary BVH wi
th A
UTO/defaults
.
*/
public
Node
<
E
>
getRoot
()
{
return
root
;
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newBinary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
BuildConfig
.
binaryDefault
());
}
/**
*
Sets the root node manually
.
*
Convenience factory: octonary BVH with AUTO/defaults
.
*/
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
)
{
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
BuildConfig
.
octonaryDefault
());
}
/**
* Convenience factory: octonary BVH with a custom maxLeafSize.
*/
public
static
<
E
>
BoundingVolumeHierarchyTree
<
E
>
newOctonary
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunction
,
int
maxLeafSize
)
{
BuildConfig
cfg
=
BuildConfig
.
builder
()
.
degree
(
8
)
.
splitStrategy
(
SplitStrategy
.
AUTO
)
.
maxLeafSize
(
maxLeafSize
)
.
maxDepth
(
computeDefaultMaxDepth
(
elements
!=
null
?
elements
.
size
()
:
0
))
.
degenerateTolerance
(
DEFAULT_DEGENERATE_TOL
)
.
build
();
return
new
BoundingVolumeHierarchyTree
<>(
elements
,
aabbFunction
,
cfg
);
}
public
Node
<
E
>
getRoot
()
{
return
root
;
}
public
void
setRoot
(
Node
<
E
>
root
)
{
this
.
root
=
root
;
}
/**
*
Recursively builds a balanced AABB tree from the list of elemen
ts.
*
NOTE: generic version already exis
ts.
*/
private
Node
<
E
>
buildRecursive
(
List
<
E
>
elements
)
{
if
(
elements
.
size
()
==
1
)
{
E
elem
=
elements
.
get
(
0
);
return
new
Node
<>(
elem
,
aabbFunction
.
apply
(
elem
));
public
static
AABB
getAggregateAABB
(
List
<
ConcretePolygon
>
polygons
)
{
if
(
polygons
.
size
()
==
1
)
{
return
AABB
.
of
(
polygons
.
get
(
0
));
}
return
getAggregateAABB
(
polygons
,
AABB:
:
of
);
}
public
static
<
E
>
AABB
getAggregateAABB
(
List
<
E
>
elements
,
Function
<
E
,
AABB
>
aabbFunc
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
;
double
minY
=
Double
.
POSITIVE_INFINITY
;
double
minZ
=
Double
.
POSITIVE_INFINITY
;
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
for
(
E
e
:
elements
)
{
AABB
aabb
=
aabbFunc
.
apply
(
e
);
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
}
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
public
List
<
E
>
getAllIntersectingElements
(
AABB
query
)
{
List
<
E
>
result
=
new
ArrayList
<>();
getAllIntersectingElementsRecursive
(
root
,
query
,
result
);
return
result
;
}
private
void
getAllIntersectingElementsRecursive
(
Node
<
E
>
node
,
AABB
query
,
List
<
E
>
result
)
{
if
(
node
==
null
)
{
return
;
}
if
(!
node
.
getAabb
().
overlaps
(
query
))
{
return
;
}
if
(
node
.
isLeaf
())
{
result
.
add
(
node
.
getElement
());
return
;
}
for
(
Node
<
E
>
child
:
node
.
getChildren
())
{
getAllIntersectingElementsRecursive
(
child
,
query
,
result
);
}
}
// ---------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------
private
List
<
BuildItem
<
E
>>
toBuildItems
(
List
<
E
>
elements
)
{
List
<
BuildItem
<
E
>>
items
=
new
ArrayList
<>(
elements
.
size
());
for
(
E
e
:
elements
)
{
AABB
aabb
=
Objects
.
requireNonNull
(
aabbFunction
.
apply
(
e
),
"aabbFunction returned null"
);
items
.
add
(
new
BuildItem
<>(
e
,
aabb
));
}
return
items
;
}
private
SplitStrategy
resolveSplitStrategy
(
BuildConfig
config
)
{
if
(
config
.
getSplitStrategy
()
!=
SplitStrategy
.
AUTO
)
{
validateStrategyMatchesDegree
(
config
.
getDegree
(),
config
.
getSplitStrategy
());
return
config
.
getSplitStrategy
();
}
return
(
config
.
getDegree
()
==
2
)
?
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
:
SplitStrategy
.
OCTONARY_OBJECT_MEAN
;
}
private
void
validateStrategyMatchesDegree
(
int
degree
,
SplitStrategy
strategy
)
{
boolean
binary
=
strategy
==
SplitStrategy
.
BINARY_OBJECT_MEDIAN
||
strategy
==
SplitStrategy
.
BINARY_OBJECT_MEAN
||
strategy
==
SplitStrategy
.
BINARY_SPATIAL_MEDIAN
;
boolean
octonary
=
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEDIAN
||
strategy
==
SplitStrategy
.
OCTONARY_OBJECT_MEAN
||
strategy
==
SplitStrategy
.
OCTONARY_SPATIAL_MEDIAN
;
if
(
degree
==
2
&&
!
binary
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 2."
);
}
if
(
degree
==
8
&&
!
octonary
)
{
throw
new
IllegalArgumentException
(
"Strategy "
+
strategy
+
" does not match degree 8."
);
}
}
private
Node
<
E
>
buildBinaryRecursive
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
SplitStrategy
strategy
,
BuildConfig
config
)
{
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
return
packLeafFromItems
(
items
,
totalAabb
);
}
// Compute total AABB
AABB
totalAabb
=
getAggregateAABB
(
elements
,
aabbFunction
);
int
axis
=
totalAabb
.
findLongestAxis
();
SplitResult
<
E
>
split
;
// Sort elements along axis
elements
.
sort
(
Comparator
.
comparingDouble
(
e
->
aabbFunction
.
apply
(
e
).
getCenter
()[
axis
]));
int
mid
=
elements
.
size
()
/
2
;
List
<
E
>
leftList
=
elements
.
subList
(
0
,
mid
);
List
<
E
>
rightList
=
elements
.
subList
(
mid
,
elements
.
size
());
switch
(
strategy
)
{
case
BINARY_OBJECT_MEDIAN:
split
=
splitBinaryObjectMedian
(
items
,
axis
);
break
;
case
BINARY_OBJECT_MEAN:
split
=
splitBinaryObjectMean
(
items
,
axis
);
break
;
case
BINARY_SPATIAL_MEDIAN:
split
=
splitBinarySpatialMedian
(
items
,
axis
,
totalAabb
);
break
;
default
:
throw
new
IllegalStateException
(
"Unexpected binary strategy: "
+
strategy
);
}
if
(!
split
.
valid
())
{
return
packLeafFromItems
(
items
,
totalAabb
);
}
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
node
.
getChildren
().
add
(
buildRecursive
(
leftList
));
node
.
getChildren
().
add
(
buildRecursive
(
rightList
));
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
left
,
depth
+
1
,
strategy
,
config
));
node
.
getChildren
().
add
(
buildBinaryRecursive
(
split
.
right
,
depth
+
1
,
strategy
,
config
));
return
node
;
}
private
Node
<
E
>
buildOctonaryRecursive
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
SplitStrategy
strategy
,
BuildConfig
config
)
{
AABB
totalAabb
=
getAggregateAABBFromItems
(
items
);
if
(
shouldStop
(
items
,
depth
,
config
,
totalAabb
))
{
return
packLeafFromItems
(
items
,
totalAabb
);
}
OctSplit
<
E
>
split
;
switch
(
strategy
)
{
case
OCTONARY_OBJECT_MEDIAN:
split
=
splitOctonaryObjectMedian
(
items
);
break
;
case
OCTONARY_OBJECT_MEAN:
split
=
splitOctonaryObjectMean
(
items
);
break
;
case
OCTONARY_SPATIAL_MEDIAN:
split
=
splitOctonarySpatialMedian
(
items
,
totalAabb
);
break
;
default
:
throw
new
IllegalStateException
(
"Unexpected octonary strategy: "
+
strategy
);
}
if
(!
split
.
valid
())
{
return
packLeafFromItems
(
items
,
totalAabb
);
}
Node
<
E
>
node
=
new
Node
<>(
null
,
totalAabb
);
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
List
<
BuildItem
<
E
>>
bucket
=
split
.
buckets
[
i
];
if
(
bucket
.
isEmpty
())
{
continue
;
}
node
.
getChildren
().
add
(
buildOctonaryRecursive
(
bucket
,
depth
+
1
,
strategy
,
config
));
}
return
node
;
}
// Works with overlaps()<--includes touching; not with intersects()
public
List
<
E
>
getAllOverlappingElements
(
AABB
query
)
{
List
<
E
>
result
=
new
ArrayList
<>();
getAllOverlappingElementsRecursive
(
root
,
query
,
result
);
return
result
;
}
private
void
getAllOverlappingElementsRecursive
(
Node
<
E
>
node
,
AABB
query
,
List
<
E
>
result
)
{
if
(
node
==
null
)
{
return
;
}
// Broad-phase pruning (includes touching points)
if
(!
node
.
getAabb
().
overlaps
(
query
))
{
return
;
}
if
(
node
.
isLeaf
())
{
// Leaf-AABB is all ready in heap
if
(
node
.
getAabb
().
overlaps
(
query
))
{
result
.
add
(
node
.
getElement
());
}
return
;
}
for
(
Node
<
E
>
child
:
node
.
getChildren
())
{
getAllOverlappingElementsRecursive
(
child
,
query
,
result
);
}
}
}
private
boolean
shouldStop
(
List
<
BuildItem
<
E
>>
items
,
int
depth
,
BuildConfig
config
,
AABB
totalAabb
)
{
return
items
.
size
()
<=
config
.
getMaxLeafSize
()
||
depth
>=
config
.
getMaxDepth
()
||
totalAabb
.
isDegenerate
(
config
.
getDegenerateTolerance
());
}
private
Node
<
E
>
packLeafFromItems
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
if
(
items
.
size
()
==
1
)
{
BuildItem
<
E
>
item
=
items
.
get
(
0
);
return
new
Node
<>(
item
.
element
,
item
.
aabb
);
}
Node
<
E
>
leaf
=
new
Node
<>(
null
,
totalAabb
);
for
(
BuildItem
<
E
>
item
:
items
)
{
leaf
.
getChildren
().
add
(
new
Node
<>(
item
.
element
,
item
.
aabb
));
}
return
leaf
;
}
private
static
<
E
>
AABB
getAggregateAABBFromItems
(
List
<
BuildItem
<
E
>>
items
)
{
double
minX
=
Double
.
POSITIVE_INFINITY
;
double
minY
=
Double
.
POSITIVE_INFINITY
;
double
minZ
=
Double
.
POSITIVE_INFINITY
;
double
maxX
=
Double
.
NEGATIVE_INFINITY
;
double
maxY
=
Double
.
NEGATIVE_INFINITY
;
double
maxZ
=
Double
.
NEGATIVE_INFINITY
;
for
(
BuildItem
<
E
>
item
:
items
)
{
AABB
aabb
=
item
.
aabb
;
minX
=
Math
.
min
(
minX
,
aabb
.
getMinX
());
minY
=
Math
.
min
(
minY
,
aabb
.
getMinY
());
minZ
=
Math
.
min
(
minZ
,
aabb
.
getMinZ
());
maxX
=
Math
.
max
(
maxX
,
aabb
.
getMaxX
());
maxY
=
Math
.
max
(
maxY
,
aabb
.
getMaxY
());
maxZ
=
Math
.
max
(
maxZ
,
aabb
.
getMaxZ
());
}
return
new
AABB
(
minX
,
minY
,
minZ
,
maxX
,
maxY
,
maxZ
);
}
// ---------------------------------------------------------------------
// Binary splits
// ---------------------------------------------------------------------
private
SplitResult
<
E
>
splitBinaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
center
(
axis
)));
int
mid
=
items
.
size
()
/
2
;
if
(
mid
<=
0
||
mid
>=
items
.
size
())
{
return
SplitResult
.
invalid
();
}
return
SplitResult
.
of
(
items
.
subList
(
0
,
mid
),
items
.
subList
(
mid
,
items
.
size
()));
}
private
SplitResult
<
E
>
splitBinaryObjectMean
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
double
sum
=
0.0
;
for
(
BuildItem
<
E
>
item
:
items
)
{
sum
+=
item
.
center
(
axis
);
}
double
splitValue
=
sum
/
items
.
size
();
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
for
(
BuildItem
<
E
>
item
:
items
)
{
if
(
item
.
center
(
axis
)
<
splitValue
)
{
left
.
add
(
item
);
}
else
{
right
.
add
(
item
);
}
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
splitBinaryObjectMedian
(
items
,
axis
);
}
return
SplitResult
.
of
(
left
,
right
);
}
private
SplitResult
<
E
>
splitBinarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
int
axis
,
AABB
totalAabb
)
{
double
splitValue
;
switch
(
axis
)
{
case
0
:
splitValue
=
totalAabb
.
getCenterX
();
break
;
case
1
:
splitValue
=
totalAabb
.
getCenterY
();
break
;
case
2
:
splitValue
=
totalAabb
.
getCenterZ
();
break
;
default
:
throw
new
IllegalArgumentException
(
"axis must be 0, 1, or 2"
);
}
List
<
BuildItem
<
E
>>
left
=
new
ArrayList
<>();
List
<
BuildItem
<
E
>>
right
=
new
ArrayList
<>();
for
(
BuildItem
<
E
>
item
:
items
)
{
if
(
item
.
center
(
axis
)
<
splitValue
)
{
left
.
add
(
item
);
}
else
{
right
.
add
(
item
);
}
}
if
(
left
.
isEmpty
()
||
right
.
isEmpty
())
{
return
splitBinaryObjectMean
(
items
,
axis
);
}
return
SplitResult
.
of
(
left
,
right
);
}
private
static
final
class
SplitResult
<
E
>
{
final
List
<
BuildItem
<
E
>>
left
;
final
List
<
BuildItem
<
E
>>
right
;
private
SplitResult
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
this
.
left
=
left
;
this
.
right
=
right
;
}
static
<
E
>
SplitResult
<
E
>
of
(
List
<
BuildItem
<
E
>>
left
,
List
<
BuildItem
<
E
>>
right
)
{
return
new
SplitResult
<>(
left
,
right
);
}
static
<
E
>
SplitResult
<
E
>
invalid
()
{
return
new
SplitResult
<>(
null
,
null
);
}
boolean
valid
()
{
return
left
!=
null
&&
right
!=
null
&&
!
left
.
isEmpty
()
&&
!
right
.
isEmpty
();
}
}
// ---------------------------------------------------------------------
// Octonary / 8-ary splits
// ---------------------------------------------------------------------
private
OctSplit
<
E
>
splitOctonaryObjectMean
(
List
<
BuildItem
<
E
>>
items
)
{
double
sumX
=
0.0
;
double
sumY
=
0.0
;
double
sumZ
=
0.0
;
for
(
BuildItem
<
E
>
item
:
items
)
{
sumX
+=
item
.
centerX
;
sumY
+=
item
.
centerY
;
sumZ
+=
item
.
centerZ
;
}
double
sx
=
sumX
/
items
.
size
();
double
sy
=
sumY
/
items
.
size
();
double
sz
=
sumZ
/
items
.
size
();
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
private
OctSplit
<
E
>
splitOctonarySpatialMedian
(
List
<
BuildItem
<
E
>>
items
,
AABB
totalAabb
)
{
double
sx
=
totalAabb
.
getCenterX
();
double
sy
=
totalAabb
.
getCenterY
();
double
sz
=
totalAabb
.
getCenterZ
();
OctSplit
<
E
>
split
=
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
false
);
if
(!
split
.
valid
())
{
return
splitOctonaryObjectMean
(
items
);
}
return
split
;
}
private
OctSplit
<
E
>
splitOctonaryObjectMedian
(
List
<
BuildItem
<
E
>>
items
)
{
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerX
));
double
sx
=
medianValue
(
items
,
0
);
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerY
));
double
sy
=
medianValue
(
items
,
1
);
items
.
sort
(
Comparator
.
comparingDouble
(
it
->
it
.
centerZ
));
double
sz
=
medianValue
(
items
,
2
);
return
bucketizeOctonary
(
items
,
sx
,
sy
,
sz
,
true
);
}
private
double
medianValue
(
List
<
BuildItem
<
E
>>
items
,
int
axis
)
{
int
n
=
items
.
size
();
int
mid
=
n
/
2
;
if
((
n
&
1
)
==
1
)
{
return
items
.
get
(
mid
).
center
(
axis
);
}
return
0.5
*
(
items
.
get
(
mid
-
1
).
center
(
axis
)
+
items
.
get
(
mid
).
center
(
axis
));
}
@SuppressWarnings
(
"unchecked"
)
private
OctSplit
<
E
>
bucketizeOctonary
(
List
<
BuildItem
<
E
>>
items
,
double
sx
,
double
sy
,
double
sz
,
boolean
fallbackToMeanOnFailure
)
{
List
<
BuildItem
<
E
>>[]
buckets
=
new
List
[
8
];
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
buckets
[
i
]
=
new
ArrayList
<>();
}
int
nonEmptyCount
=
0
;
boolean
[]
seen
=
new
boolean
[
8
];
for
(
BuildItem
<
E
>
item
:
items
)
{
int
idx
=
octantIndex
(
item
.
centerX
,
item
.
centerY
,
item
.
centerZ
,
sx
,
sy
,
sz
);
buckets
[
idx
].
add
(
item
);
if
(!
seen
[
idx
])
{
seen
[
idx
]
=
true
;
nonEmptyCount
++;
}
}
if
(
nonEmptyCount
<=
1
)
{
if
(
fallbackToMeanOnFailure
)
{
// caller may already be object mean, so just mark invalid here
return
OctSplit
.
invalid
();
}
return
OctSplit
.
invalid
();
}
return
OctSplit
.
of
(
buckets
);
}
private
int
octantIndex
(
double
x
,
double
y
,
double
z
,
double
sx
,
double
sy
,
double
sz
)
{
int
idx
=
0
;
if
(
x
>=
sx
)
idx
|=
1
;
if
(
y
>=
sy
)
idx
|=
2
;
if
(
z
>=
sz
)
idx
|=
4
;
return
idx
;
}
private
static
final
class
OctSplit
<
E
>
{
final
List
<
BuildItem
<
E
>>[]
buckets
;
private
OctSplit
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
this
.
buckets
=
buckets
;
}
static
<
E
>
OctSplit
<
E
>
of
(
List
<
BuildItem
<
E
>>[]
buckets
)
{
return
new
OctSplit
<>(
buckets
);
}
@SuppressWarnings
(
"unchecked"
)
static
<
E
>
OctSplit
<
E
>
invalid
()
{
return
new
OctSplit
<>(
null
);
}
boolean
valid
()
{
if
(
buckets
==
null
)
{
return
false
;
}
int
nonEmpty
=
0
;
for
(
List
<
BuildItem
<
E
>>
bucket
:
buckets
)
{
if
(
bucket
!=
null
&&
!
bucket
.
isEmpty
())
{
nonEmpty
++;
}
}
return
nonEmpty
>
1
;
}
}
// ---------------------------------------------------------------------
// helpers: to be continued with more heuristics
// ---------------------------------------------------------------------
private
static
int
computeDefaultMaxDepth
(
int
n
)
{
if
(
n
<=
1
)
{
return
DEFAULT_MIN_DEPTH
;
}
double
log2n
=
Math
.
log
(
n
)
/
Math
.
log
(
2.0
);
int
depth
=
(
int
)
Math
.
ceil
(
2.0
*
log2n
);
return
clamp
(
depth
,
DEFAULT_MIN_DEPTH
,
DEFAULT_MAX_DEPTH
);
}
private
static
int
clamp
(
int
value
,
int
min
,
int
max
)
{
return
Math
.
max
(
min
,
Math
.
min
(
max
,
value
));
}
}
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NestedRingsCheck.java
View file @
73b1c745
...
...
@@ -35,6 +35,7 @@ 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.bht.AABB
;
/**
* Checks whether a inner ring is completely contained in another inner ring
...
...
@@ -57,9 +58,40 @@ public class NestedRingsCheck extends Check {
deps
.
add
(
CheckId
.
C_GE_P_ORIENTATION_RINGS_SAME
);
dependencies
=
Collections
.
unmodifiableList
(
deps
);
}
/**
* FilterSwitch @Numanoglu
* true -> use AABB prefilter
* false -> use original double-loop exact version
*/
private
boolean
useAabbFilter
=
true
;
public
NestedRingsCheck
()
{
}
public
NestedRingsCheck
(
boolean
useAabbFilter
)
{
this
.
useAabbFilter
=
useAabbFilter
;
}
public
void
setUseAabbFilter
(
boolean
useAabbFilter
)
{
this
.
useAabbFilter
=
useAabbFilter
;
}
public
boolean
isUseAabbFilter
()
{
return
useAabbFilter
;
}
//
@Override
public
void
check
(
Polygon
p
)
{
if
(
useAabbFilter
)
{
checkWithBoundingBoxFilter
(
p
);
}
else
{
checkOriginal
(
p
);
}
}
private
void
checkOriginal
(
Polygon
p
){
for
(
LinearRing
interiorRing
:
p
.
getInnerRings
())
{
for
(
LinearRing
checkRing
:
p
.
getInnerRings
())
{
if
(
checkRing
==
interiorRing
)
{
...
...
@@ -80,6 +112,71 @@ public class NestedRingsCheck extends Check {
p
.
addCheckResult
(
cr
);
}
/**
* Alternative implementation using cached AABBs as broad-phase filter.
* Exact geometry check is still done via areAllPointsInside(...).
*/
public
void
checkWithBoundingBoxFilter
(
Polygon
p
)
{
List
<
LinearRing
>
innerRings
=
p
.
getInnerRings
();
if
(
innerRings
==
null
||
innerRings
.
size
()
<
2
)
{
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
p
.
addCheckResult
(
cr
);
return
;
}
List
<
RingAabbEntry
>
ringEntries
=
new
ArrayList
<>(
innerRings
.
size
());
for
(
LinearRing
ring
:
innerRings
)
{
ringEntries
.
add
(
new
RingAabbEntry
(
ring
,
AABB
.
of
(
ring
)));
}
for
(
int
i
=
0
;
i
<
ringEntries
.
size
();
i
++)
{
RingAabbEntry
outerEntry
=
ringEntries
.
get
(
i
);
LinearRing
interiorRing
=
outerEntry
.
ring
;
AABB
outerAabb
=
outerEntry
.
aabb
;
for
(
int
j
=
0
;
j
<
ringEntries
.
size
();
j
++)
{
if
(
i
==
j
)
{
// do not compare with itself
continue
;
}
RingAabbEntry
innerEntry
=
ringEntries
.
get
(
j
);
LinearRing
checkRing
=
innerEntry
.
ring
;
AABB
innerAabb
=
innerEntry
.
aabb
;
// Broad phase:
// if outer AABB does not fully contain inner AABB,
// full geometric containment is impossible
if
(!
outerAabb
.
contains
(
innerAabb
))
{
continue
;
}
// Narrow phase:
// exact test
if
(
areAllPointsInside
(
interiorRing
,
checkRing
))
{
CheckError
err
=
new
NestedRingError
(
p
,
interiorRing
,
checkRing
);
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
p
.
addCheckResult
(
cr
);
return
;
}
}
}
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
p
.
addCheckResult
(
cr
);
}
private
static
final
class
RingAabbEntry
{
final
LinearRing
ring
;
final
AABB
aabb
;
RingAabbEntry
(
LinearRing
ring
,
AABB
aabb
)
{
this
.
ring
=
ring
;
this
.
aabb
=
aabb
;
}
}
private
boolean
areAllPointsInside
(
LinearRing
ring
,
LinearRing
checkRing
)
{
boolean
isInside
=
true
;
for
(
Vertex
v
:
checkRing
.
getVertices
())
{
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
View file @
73b1c745
...
...
@@ -37,11 +37,12 @@ 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.datastructure.bht.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.math.CovarianceMatrix
;
import
de.hft.stuttgart.citydoctor2.math.DistanceResult
;
import
de.hft.stuttgart.citydoctor2.math.Matrix3x3d
;
...
...
@@ -50,6 +51,8 @@ import de.hft.stuttgart.citydoctor2.math.Segment3d;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
/**
* Checks whether a ring self intersects. Also checks if a point is too close to
* an edge of the ring.
...
...
@@ -57,15 +60,22 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
* @author Matthias Betz
*
*/
/**
* Variants:
* - OLD: only BoundingBox replaced by AABB, logic otherwise unchanged
* - TREE_1_EDGE_BVH: edge-edge broad phase via BVH
* - TREE_2_EDGE_AND_VERTEX_BVH: edge-edge via edge BVH, point-edge via vertex BVH
*
* @author Baris Numanoglu
*/
public
class
RingSelfIntCheck
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
{
...
...
@@ -76,6 +86,28 @@ public class RingSelfIntCheck extends Check {
dependencies
=
Collections
.
unmodifiableList
(
deps
);
}
public
enum
Variant
{
OLD
,
TREE_1_EDGE_BVH
,
TREE_2_EDGE_AND_VERTEX_BVH
}
private
Variant
variant
=
Variant
.
OLD
;
public
RingSelfIntCheck
()
{
}
public
RingSelfIntCheck
(
Variant
variant
)
{
this
.
variant
=
variant
;
}
public
void
setVariant
(
Variant
variant
)
{
this
.
variant
=
variant
;
}
public
Variant
getVariant
()
{
return
variant
;
}
@Override
public
void
init
(
Map
<
String
,
String
>
parameters
,
ParserConfiguration
config
)
{
...
...
@@ -90,25 +122,38 @@ public class RingSelfIntCheck extends Check {
@Override
public
void
check
(
LinearRing
lr
)
{
checkRingJava
(
lr
);
switch
(
variant
)
{
case
OLD:
checkRingJavaOld
(
lr
);
break
;
case
TREE_1_EDGE_BVH:
checkRingJavaTree1
(
lr
);
break
;
case
TREE_2_EDGE_AND_VERTEX_BVH:
checkRingJavaTree2
(
lr
);
break
;
default
:
checkRingJavaOld
(
lr
);
break
;
}
}
private
void
checkRingJava
(
LinearRing
lr
)
{
// check for tiny edge as well
// store all used points in temporary list
/**
* OLD:
* only BoundingBox -> AABB replaced, otherwise logic unchanged.
*/
private
void
checkRingJavaOld
(
LinearRing
lr
)
{
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
if
(
checkEigenvalues
(
lr
,
vertices
,
ed
))
{
// found tiny edge error, abort further checking
return
;
}
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
for
(
Edge
e
:
edges
)
{
if
(
checkForPointsTouchingEdge
(
lr
,
e
))
{
if
(
checkForPointsTouchingEdge
Old
(
lr
,
e
))
{
return
;
}
}
...
...
@@ -125,7 +170,6 @@ public class RingSelfIntCheck extends Check {
Segment3d
s2
=
new
Segment3d
(
e2
.
getFrom
(),
e2
.
getTo
());
DistanceResult
dr
=
s1
.
getDistanceResult
(
s2
);
if
(
dr
.
distance
()
<
epsilon
)
{
// intersection
CheckError
err
=
new
RingEdgeIntersectionError
(
lr
,
e1
,
e2
,
dr
.
point1
());
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
lr
.
addCheckResult
(
cr
);
...
...
@@ -134,27 +178,167 @@ public class RingSelfIntCheck extends Check {
}
}
// no errors detected
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
lr
.
addCheckResult
(
cr
);
}
/**
* TREE_1:
* - point-edge remains old
* - edge-edge uses Edge-BVH
*/
private
void
checkRingJavaTree1
(
LinearRing
lr
)
{
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
if
(
checkEigenvalues
(
lr
,
vertices
,
ed
))
{
return
;
}
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
for
(
Edge
e
:
edges
)
{
if
(
checkForPointsTouchingEdgeOld
(
lr
,
e
))
{
return
;
}
}
BoundingVolumeHierarchyTree
<
Edge
>
edgeTree
=
new
BoundingVolumeHierarchyTree
<>(
edges
,
e
->
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
),
BoundingVolumeHierarchyTree
.
BuildConfig
.
binaryDefault
());
for
(
int
i
=
0
;
i
<
edges
.
size
();
i
++)
{
Edge
e1
=
edges
.
get
(
i
);
AABB
q
=
AABB
.
of
(
e1
.
getFrom
(),
e1
.
getTo
(),
epsilon
);
List
<
Edge
>
candidates
=
edgeTree
.
getAllIntersectingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
continue
;
}
Segment3d
s1
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
for
(
Edge
e2
:
candidates
)
{
if
(
e1
==
e2
)
{
continue
;
}
if
(
e1
.
getConnectionPoint
(
e2
)
!=
null
)
{
continue
;
}
// avoid double pairwise checks
int
j
=
edges
.
indexOf
(
e2
);
if
(
j
<=
i
)
{
continue
;
}
Segment3d
s2
=
new
Segment3d
(
e2
.
getFrom
(),
e2
.
getTo
());
DistanceResult
dr
=
s1
.
getDistanceResult
(
s2
);
if
(
dr
.
distance
()
<
epsilon
)
{
CheckError
err
=
new
RingEdgeIntersectionError
(
lr
,
e1
,
e2
,
dr
.
point1
());
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
lr
.
addCheckResult
(
cr
);
return
;
}
}
}
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
lr
.
addCheckResult
(
cr
);
}
/**
* TREE_2:
* - point-edge uses Vertex-BVH
* - edge-edge uses Edge-BVH
*/
private
void
checkRingJavaTree2
(
LinearRing
lr
)
{
List
<
Vertex
>
vertices
=
lr
.
getVertices
();
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
if
(
checkEigenvalues
(
lr
,
vertices
,
ed
))
{
return
;
}
List
<
Edge
>
edges
=
getEdgesForRing
(
lr
);
BoundingVolumeHierarchyTree
<
Vertex
>
vertexTree
=
new
BoundingVolumeHierarchyTree
<>(
vertices
,
v
->
AABB
.
of
(
v
,
epsilon
),
BoundingVolumeHierarchyTree
.
BuildConfig
.
binaryDefault
());
for
(
Edge
e
:
edges
)
{
if
(
checkForPointsTouchingEdgeTree
(
lr
,
e
,
vertexTree
))
{
return
;
}
}
BoundingVolumeHierarchyTree
<
Edge
>
edgeTree
=
new
BoundingVolumeHierarchyTree
<>(
edges
,
e
->
AABB
.
of
(
e
.
getFrom
(),
e
.
getTo
(),
epsilon
),
BoundingVolumeHierarchyTree
.
BuildConfig
.
binaryDefault
());
for
(
int
i
=
0
;
i
<
edges
.
size
();
i
++)
{
Edge
e1
=
edges
.
get
(
i
);
AABB
q
=
AABB
.
of
(
e1
.
getFrom
(),
e1
.
getTo
(),
epsilon
);
List
<
Edge
>
candidates
=
edgeTree
.
getAllIntersectingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
continue
;
}
Segment3d
s1
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
for
(
Edge
e2
:
candidates
)
{
if
(
e1
==
e2
)
{
continue
;
}
if
(
e1
.
getConnectionPoint
(
e2
)
!=
null
)
{
continue
;
}
int
j
=
edges
.
indexOf
(
e2
);
if
(
j
<=
i
)
{
continue
;
}
Segment3d
s2
=
new
Segment3d
(
e2
.
getFrom
(),
e2
.
getTo
());
DistanceResult
dr
=
s1
.
getDistanceResult
(
s2
);
if
(
dr
.
distance
()
<
epsilon
)
{
CheckError
err
=
new
RingEdgeIntersectionError
(
lr
,
e1
,
e2
,
dr
.
point1
());
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
ERROR
,
err
);
lr
.
addCheckResult
(
cr
);
return
;
}
}
}
CheckResult
cr
=
new
CheckResult
(
this
,
ResultStatus
.
OK
,
null
);
lr
.
addCheckResult
(
cr
);
}
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
));
}
BoundingBox
bbox
=
BoundingBox
.
ofPoints
(
rotatedVertices
);
AABB
bbox
=
AABB
.
ofPoints
(
rotatedVertices
);
double
width
=
bbox
.
getMaxX
()
-
bbox
.
getMinX
();
double
height
=
bbox
.
getMaxY
()
-
bbox
.
getMinY
();
double
depth
=
bbox
.
getMaxZ
()
-
bbox
.
getMinZ
();
int
nrOfEigenvaluesBelowTolerance
=
0
;
if
(
bbox
.
getW
idth
()
<
degeneratedRingTolerance
)
{
if
(
w
idth
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
if
(
bbox
.
getH
eight
()
<
degeneratedRingTolerance
)
{
if
(
h
eight
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
if
(
bbox
.
getD
epth
()
<
degeneratedRingTolerance
)
{
if
(
d
epth
<
degeneratedRingTolerance
)
{
nrOfEigenvaluesBelowTolerance
++;
}
...
...
@@ -166,7 +350,7 @@ public class RingSelfIntCheck extends Check {
return
false
;
}
private
boolean
checkForPointsTouchingEdge
(
LinearRing
lr
,
Edge
e1
)
{
private
boolean
checkForPointsTouchingEdge
Old
(
LinearRing
lr
,
Edge
e1
)
{
Segment3d
seg
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
for
(
Vertex
v
:
lr
.
getVertices
())
{
if
(
v
!=
e1
.
getFrom
()
&&
v
!=
e1
.
getTo
()
&&
seg
.
getDistance
(
v
)
<
epsilon
)
{
...
...
@@ -179,6 +363,30 @@ public class RingSelfIntCheck extends Check {
return
false
;
}
private
boolean
checkForPointsTouchingEdgeTree
(
LinearRing
lr
,
Edge
e1
,
BoundingVolumeHierarchyTree
<
Vertex
>
vertexTree
)
{
Segment3d
seg
=
new
Segment3d
(
e1
.
getFrom
(),
e1
.
getTo
());
AABB
q
=
AABB
.
of
(
e1
.
getFrom
(),
e1
.
getTo
(),
epsilon
);
List
<
Vertex
>
candidates
=
vertexTree
.
getAllIntersectingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
return
false
;
}
for
(
Vertex
v
:
candidates
)
{
if
(
v
!=
e1
.
getFrom
()
&&
v
!=
e1
.
getTo
()
&&
seg
.
getDistance
(
v
)
<
epsilon
)
{
CheckError
err
=
new
PointTouchesEdgeError
(
lr
,
e1
,
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
();
...
...
@@ -218,4 +426,4 @@ public class RingSelfIntCheck extends Check {
public
CheckId
getCheckId
()
{
return
CheckId
.
C_GE_R_SELF_INTERSECTION
;
}
}
}
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/SelfIntersectionUtil.java
View file @
73b1c745
...
...
@@ -129,27 +129,25 @@ public class SelfIntersectionUtil {
*
* New Version with tree query
* */
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersectionWithTree
(
Geometry
g
,
double
delta
)
{
Geometry
g
,
double
delta
,
BoundingVolumeHierarchyTree
.
BuildConfig
treeConfig
)
{
List
<
TesselatedPolygon
>
tesselatedPolygons
=
tesselateAndFilter
(
g
,
delta
);
List
<
Integer
>
indices
=
new
ArrayList
<>(
tesselatedPolygons
.
size
());
for
(
int
i
=
0
;
i
<
tesselatedPolygons
.
size
();
i
++)
{
indices
.
add
(
i
);
indices
.
add
(
i
);
}
// Build BVH on polygons, but compute AABBs from the *original* polygons
BoundingVolumeHierarchyTree
<
Integer
>
tree
=
new
BoundingVolumeHierarchyTree
<>(
indices
,
index
->
AABB
.
of
(
tesselatedPolygons
.
get
(
index
).
getOriginal
()));
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
// IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>();
// for (int i = 0; i < tesselatedPolygons.size(); i++) {
// Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon
// ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
// indexByOriginal.put(orig, i);
// }
// Build BVH on polygon indices, while computing AABBs from the original polygons
BoundingVolumeHierarchyTree
<
Integer
>
tree
=
new
BoundingVolumeHierarchyTree
<>(
indices
,
index
->
AABB
.
of
(
tesselatedPolygons
.
get
(
index
).
getOriginal
()),
treeConfig
);
List
<
PolygonIntersection
>
intersections
=
new
ArrayList
<>();
...
...
@@ -159,20 +157,20 @@ public class SelfIntersectionUtil {
// Query AABB based on original polygon (consistent with tree construction)
AABB
q
=
AABB
.
of
(
p1
.
getOriginal
());
List
<
Integer
>
candidates
=
tree
.
getAll
Overlapp
ingElements
(
q
);
List
<
Integer
>
candidates
=
tree
.
getAll
Intersect
ingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
continue
;
// no candidate
ergo
no overlap
continue
;
// no candidate
=>
no overlap
}
for
(
Integer
j
:
candidates
)
{
if
(
j
<=
i
)
{
// avoids double
pairwise
checks and self-pair
continue
;
// avoids double checks and self-pair
continue
;
}
TesselatedPolygon
p2
=
tesselatedPolygons
.
get
(
j
);
// TODO may
be later
a fur
ther tree
in
here
TesselatedPolygon
p2
=
tesselatedPolygons
.
get
(
j
);
// TODO maybe later
use ano
ther tree here
GeometrySelfIntersection
inter
=
doPolygonsIntersect
(
p1
,
p2
,
delta
);
if
(
inter
!=
null
)
{
intersections
.
add
(
PolygonIntersection
.
triangles
(
inter
.
t1
(),
inter
.
t2
()));
...
...
@@ -181,12 +179,22 @@ public class SelfIntersectionUtil {
}
return
intersections
;
}
}
// older signature with two arguments provisionally
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersectionWithTree
(
Geometry
g
,
double
delta
)
{
return
calculateSolidSelfIntersectionWithTree
(
g
,
delta
,
BoundingVolumeHierarchyTree
.
BuildConfig
.
binaryDefault
()
);
}
/*
* @ Baris Numanoglu
*
*
New
Version with tree query
* Version with
IdentityHashmap
tree query
* */
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersection
(
Geometry
g
,
double
delta
,
BoundingVolumeHierarchyTree
<
Polygon
>
tree
)
{
...
...
@@ -210,7 +218,7 @@ public class SelfIntersectionUtil {
ConcretePolygon
p1Orig
=
p1
.
getOriginal
().
getOriginal
();
// p1:teselatedPoly->Polygon->Concrete Polygon
AABB
q
=
AABB
.
of
(
p1Orig
);
List
<
Polygon
>
candidates
=
tree
.
getAll
Overlapp
ingElements
(
q
);
List
<
Polygon
>
candidates
=
tree
.
getAll
Intersect
ingElements
(
q
);
if
(
candidates
.
isEmpty
())
{
continue
;
// no candidate ergo no overlap
}
...
...
@@ -258,6 +266,45 @@ public class SelfIntersectionUtil {
return
intersections
;
}
// The very oldest Version Without Tree and internal tesselation
public
static
List
<
PolygonIntersection
>
calculateSolidSelfIntersection0
(
Geometry
g
,
double
delta
)
{
List
<
TesselatedPolygon
>
tesselatedPolygons
=
new
ArrayList
<>();
for
(
Polygon
p
:
g
.
getPolygons
())
{
TesselatedPolygon
tessPolygon
=
EarcutTesselator
.
tesselatePolygon
(
p
);
for
(
Iterator
<
Triangle3d
>
iterator
=
tessPolygon
.
getTriangles
().
iterator
();
iterator
.
hasNext
();)
{
Triangle3d
t
=
iterator
.
next
();
List
<
Vector3d
>
vertices
=
new
ArrayList
<>(
3
);
vertices
.
add
(
t
.
getP1
());
vertices
.
add
(
t
.
getP2
());
vertices
.
add
(
t
.
getP3
());
Vector3d
centroid
=
CovarianceMatrix
.
getCentroid
(
vertices
);
EigenvalueDecomposition
ed
=
OrthogonalRegressionPlane
.
decompose
(
vertices
,
centroid
);
Matrix
eigenValues
=
ed
.
getD
();
double
[]
eigenValuesArray
=
new
double
[
3
];
eigenValuesArray
[
0
]
=
eigenValues
.
get
(
0
,
0
);
eigenValuesArray
[
1
]
=
eigenValues
.
get
(
1
,
1
);
eigenValuesArray
[
2
]
=
eigenValues
.
get
(
2
,
2
);
if
(
eigenValuesArray
[
1
]
<
delta
)
{
iterator
.
remove
();
}
}
tesselatedPolygons
.
add
(
tessPolygon
);
}
List
<
PolygonIntersection
>
intersections
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
tesselatedPolygons
.
size
()
-
1
;
i
++)
{
TesselatedPolygon
p1
=
tesselatedPolygons
.
get
(
i
);
for
(
int
j
=
i
+
1
;
j
<
tesselatedPolygons
.
size
();
j
++)
{
TesselatedPolygon
p2
=
tesselatedPolygons
.
get
(
j
);
GeometrySelfIntersection
intersection
=
doPolygonsIntersect
(
p1
,
p2
,
delta
);
if
(
intersection
!=
null
)
{
intersections
.
add
(
PolygonIntersection
.
triangles
(
intersection
.
t1
(),
intersection
.
t2
()));
}
}
}
return
intersections
;
}
public
static
GeometrySelfIntersection
doesSolidSelfIntersect
(
Geometry
g
,
double
epsilon
)
{
return
selfIntersectionJava
(
g
,
epsilon
);
}
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheckVariantComparisonTest.java
0 → 100644
View file @
73b1c745
package
de.hft.stuttgart.citydoctor2.checks.geometry
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.ValidationConfiguration
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParseException
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
/**
* Compares RingSelfIntCheck variants on the same input model.
*
* Compared variants:
* - OLD
* - TREE_1_EDGE_BVH
* - TREE_2_EDGE_AND_VERTEX_BVH
*/
public
class
RingSelfIntCheckVariantComparisonTest
{
private
static
final
String
TEST_GML
=
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml"
;
private
static
final
double
EPSILON
=
0.001
;
@Test
public
void
oldVsTree1VsTree2_sameErrorCounts
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
Geometry
geometryTree1
=
parseGeometry
(
TEST_GML
);
Geometry
geometryTree2
=
parseGeometry
(
TEST_GML
);
long
start
=
System
.
nanoTime
();
int
oldCount
=
runCheckAndCountErrors
(
geometryOld
,
RingSelfIntCheck
.
Variant
.
OLD
);
long
oldTime
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"RingSelfIntCheck OLD count="
+
oldCount
+
" time(ns)="
+
oldTime
);
start
=
System
.
nanoTime
();
int
tree1Count
=
runCheckAndCountErrors
(
geometryTree1
,
RingSelfIntCheck
.
Variant
.
TREE_1_EDGE_BVH
);
long
tree1Time
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"RingSelfIntCheck TREE_1_EDGE_BVH count="
+
tree1Count
+
" time(ns)="
+
tree1Time
);
start
=
System
.
nanoTime
();
int
tree2Count
=
runCheckAndCountErrors
(
geometryTree2
,
RingSelfIntCheck
.
Variant
.
TREE_2_EDGE_AND_VERTEX_BVH
);
long
tree2Time
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"RingSelfIntCheck TREE_2_EDGE_AND_VERTEX_BVH count="
+
tree2Count
+
" time(ns)="
+
tree2Time
);
assertEquals
(
"OLD vs TREE_1_EDGE_BVH differs"
,
oldCount
,
tree1Count
);
assertEquals
(
"OLD vs TREE_2_EDGE_AND_VERTEX_BVH differs"
,
oldCount
,
tree2Count
);
}
@Test
public
void
perRingResultsMatch_oldVsTree1VsTree2
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
Geometry
geometryOld
=
parseGeometry
(
TEST_GML
);
Geometry
geometryTree1
=
parseGeometry
(
TEST_GML
);
Geometry
geometryTree2
=
parseGeometry
(
TEST_GML
);
List
<
LinearRing
>
oldRings
=
collectRings
(
geometryOld
);
List
<
LinearRing
>
tree1Rings
=
collectRings
(
geometryTree1
);
List
<
LinearRing
>
tree2Rings
=
collectRings
(
geometryTree2
);
assertEquals
(
"Different number of rings in old/tree1 geometry"
,
oldRings
.
size
(),
tree1Rings
.
size
());
assertEquals
(
"Different number of rings in old/tree2 geometry"
,
oldRings
.
size
(),
tree2Rings
.
size
());
for
(
int
i
=
0
;
i
<
oldRings
.
size
();
i
++)
{
LinearRing
oldRing
=
oldRings
.
get
(
i
);
LinearRing
tree1Ring
=
tree1Rings
.
get
(
i
);
LinearRing
tree2Ring
=
tree2Rings
.
get
(
i
);
RingSelfIntCheck
oldCheck
=
createCheck
(
RingSelfIntCheck
.
Variant
.
OLD
);
RingSelfIntCheck
tree1Check
=
createCheck
(
RingSelfIntCheck
.
Variant
.
TREE_1_EDGE_BVH
);
RingSelfIntCheck
tree2Check
=
createCheck
(
RingSelfIntCheck
.
Variant
.
TREE_2_EDGE_AND_VERTEX_BVH
);
oldCheck
.
check
(
oldRing
);
tree1Check
.
check
(
tree1Ring
);
tree2Check
.
check
(
tree2Ring
);
CheckResult
oldResult
=
oldRing
.
getCheckResult
(
oldCheck
);
CheckResult
tree1Result
=
tree1Ring
.
getCheckResult
(
tree1Check
);
CheckResult
tree2Result
=
tree2Ring
.
getCheckResult
(
tree2Check
);
assertNotNull
(
"OLD result is null for ring index "
+
i
,
oldResult
);
assertNotNull
(
"TREE_1 result is null for ring index "
+
i
,
tree1Result
);
assertNotNull
(
"TREE_2 result is null for ring index "
+
i
,
tree2Result
);
assertEquals
(
"OLD vs TREE_1 status differs for ring index "
+
i
,
oldResult
.
getResultStatus
(),
tree1Result
.
getResultStatus
());
assertEquals
(
"OLD vs TREE_2 status differs for ring index "
+
i
,
oldResult
.
getResultStatus
(),
tree2Result
.
getResultStatus
());
}
}
private
int
runCheckAndCountErrors
(
Geometry
geometry
,
RingSelfIntCheck
.
Variant
variant
)
{
int
count
=
0
;
for
(
LinearRing
ring
:
collectRings
(
geometry
))
{
RingSelfIntCheck
check
=
createCheck
(
variant
);
check
.
check
(
ring
);
CheckResult
result
=
ring
.
getCheckResult
(
check
);
assertNotNull
(
"CheckResult must not be null"
,
result
);
if
(
result
.
getResultStatus
()
==
ResultStatus
.
ERROR
)
{
count
++;
}
}
return
count
;
}
private
RingSelfIntCheck
createCheck
(
RingSelfIntCheck
.
Variant
variant
)
{
RingSelfIntCheck
check
=
new
RingSelfIntCheck
(
variant
);
check
.
init
(
Collections
.
singletonMap
(
"minVertexDistance"
,
String
.
valueOf
(
EPSILON
)),
null
);
return
check
;
}
private
List
<
LinearRing
>
collectRings
(
Geometry
geometry
)
{
assertNotNull
(
"geometry must not be null"
,
geometry
);
List
<
LinearRing
>
rings
=
new
ArrayList
<>();
for
(
Polygon
polygon
:
geometry
.
getPolygons
())
{
if
(
polygon
.
getExteriorRing
()
!=
null
)
{
rings
.
add
(
polygon
.
getExteriorRing
());
}
rings
.
addAll
(
polygon
.
getInnerRings
());
}
return
rings
;
}
private
Geometry
parseGeometry
(
String
gmlPath
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
CityDoctorModel
model
=
CityGmlParser
.
parseCityGmlFile
(
gmlPath
,
config
.
getParserConfiguration
());
Building
building
=
model
.
getBuildings
().
findFirst
().
orElseThrow
();
Geometry
geometry
=
building
.
getGeometry
(
GeometryType
.
SOLID
,
Lod
.
LOD2
);
assertNotNull
(
"Expected SOLID LOD2 geometry in test model: "
+
gmlPath
,
geometry
);
return
geometry
;
}
}
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/geometry/SolidSelfIntCheckTest.java
View file @
73b1c745
...
...
@@ -133,8 +133,6 @@ public class SolidSelfIntCheckTest {
public
void
testKnownFalsePositiveExample2
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
testFalsePositiveExample
(
"src/test/resources/SolidSelfIntTest-known_false_positive2.gml"
);
}
}
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/util/SolidSelfIntersectionOldVsNewTest.java
View file @
73b1c745
...
...
@@ -13,12 +13,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import
de.hft.stuttgart.citydoctor2.datastructure.GeometryType
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParseException
;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
import
de.hft.stuttgart.citydoctor2.utils.PolygonIntersection
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.AABB
;
import
de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree
;
/*
* First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version
...
...
@@ -27,20 +27,20 @@ import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
* */
public
class
SolidSelfIntersectionOldVsNewTest
{
@Test
public
void
testOldVsNewSameResultCount
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
compareOnFile
(
"src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh2.gml"
,
0.001
);
}
//
@Test
//
@Test
//
public void testOldVsNewSameResultCount() throws CityGmlParseException, InvalidGmlFileException {
//
compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh2.gml", 0.001);
// }// dubious Test data: may be TP and not FP?
//
//
@Test
// public void testOldVsNewSameResultCountFalsePositiveExample1() throws CityGmlParseException, InvalidGmlFileException {
// compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive1.gml", 0.001);
// }
//
//
@Test
//
public void testOldVsNewSameResultCountFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException {
//
compareOnFile("src/test/resources/SolidSelfIntTest-known_false_positive2.gml", 0.001);
//
}
@Test
public
void
testOldVsNewSameResultCountFalsePositiveExample2
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
compareOnFile
(
"src/test/resources/SolidSelfIntTest-known_false_positive2.gml"
,
0.001
);
}
private
void
compareOnFile
(
String
gmlPath
,
double
delta
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
...
...
@@ -56,18 +56,46 @@ public class SolidSelfIntersectionOldVsNewTest {
assertNotNull
(
polys
);
assertTrue
(
"Expected at least 2 polygons in: "
+
gmlPath
,
polys
.
size
()
>
1
);
/// Without Tree
long
start
=
System
.
nanoTime
();
List
<
PolygonIntersection
>
oldRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
);
List
<
PolygonIntersection
>
oldRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
0
(
g
,
delta
);
long
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Alt: "
+
dif
);
/// With IdentityHashMap
start
=
System
.
nanoTime
();
BoundingVolumeHierarchyTree
<
Polygon
>
polygonTree
=
new
BoundingVolumeHierarchyTree
<>(
g
.
getPolygons
(),
AABB:
:
of
,
BoundingVolumeHierarchyTree
.
BuildConfig
.
binaryDefault
()
);
List
<
PolygonIntersection
>
oldTreeRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersection
(
g
,
delta
,
polygonTree
);
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Alt + external polygon tree: "
+
dif
);
/// With Polygon Indices
start
=
System
.
nanoTime
();
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
);
List
<
PolygonIntersection
>
newRes
=
SelfIntersectionUtil
.
calculateSolidSelfIntersectionWithTree
(
g
,
delta
);
dif
=
System
.
nanoTime
()
-
start
;
System
.
out
.
println
(
"Neu: "
+
dif
);
assertFalse
(
oldRes
.
isEmpty
());
assertFalse
(
newRes
.
isEmpty
());
///
System
.
out
.
println
(
"oldRes.size() = "
+
oldRes
.
size
());
System
.
out
.
println
(
"oldTreeRes.size() = "
+
oldTreeRes
.
size
());
System
.
out
.
println
(
"newRes.size() = "
+
newRes
.
size
());
assertFalse
(
"oldRes is empty for: "
+
gmlPath
,
oldRes
.
isEmpty
());
assertFalse
(
"oldTreeRes is empty for: "
+
gmlPath
,
oldTreeRes
.
isEmpty
());
assertFalse
(
"newRes is empty for: "
+
gmlPath
,
newRes
.
isEmpty
());
assertEquals
(
"Old vs oldTree differs for: "
+
gmlPath
,
oldRes
.
size
(),
oldTreeRes
.
size
());
assertEquals
(
"Old vs new self-intersection result count differs for: "
+
gmlPath
,
oldRes
.
size
(),
newRes
.
size
());
}
...
...
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