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
caf5d65b
Commit
caf5d65b
authored
Sep 01, 2026
by
Numanoglu
Browse files
Add regression plane sweep tests
parent
2799ce51
Pipeline
#12433
failed with stage
in 2 minutes and 20 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/SweepUtilitiesRegressionPlaneTest.java
0 → 100644
View file @
caf5d65b
package
de.hft.stuttgart.citydoctor2.healing
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.util.List
;
import
org.junit.Test
;
import
de.hft.stuttgart.citydoctor2.check.CheckResult
;
import
de.hft.stuttgart.citydoctor2.check.ResultStatus
;
import
de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError
;
import
de.hft.stuttgart.citydoctor2.checks.geometry.PlanarCheck
;
import
de.hft.stuttgart.citydoctor2.datastructure.Building
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane
;
import
de.hft.stuttgart.citydoctor2.math.Plane
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
public
class
SweepUtilitiesRegressionPlaneTest
{
private
static
final
double
ASSERTION_EPS
=
0.000001
;
private
static
final
double
POINT_ON_PLANE_TOLERANCE
=
0.001
;
private
static
final
double
SWEEP_EDGE_AND_ANGLE_TOLERANCE
=
0.001
;
/**
* Würfelmodell : Ein Deckelpunkt steht nach oben ab.
* Die ZielEbene wird hier als Regressionsebene aus allen
* eindeutigen Punkten der fehlerhaften Sweep-Fazette berechnet.
* Dadurch wird
* keine perfekte Würfeldeckelebene erzwungen, sondern die beste Ausgleichsebene
* der gesamten nicht-planaren Fazette verwendet.
*/
@Test
public
void
sweepsRaisedCubePointToRegressionPlaneOfSweepFacet
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createRaisedTopCornerCube
();
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
5
);
Vertex
raisedPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
);
Plane
targetPlane
=
regressionPlaneFromVertices
(
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
0
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
1
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
3
));
SweepUtilities
utilities
=
createSweepUtilities
(
sweepPolygon
,
targetPlane
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Regressionsebene aus ganzer Sweep-Fazette: abstehender Würfel-Deckelpunkt ==="
);
System
.
out
.
println
(
"Modell: Deckelfläche hat drei Punkte bei z = 4 und einen abstehenden Punkt bei z = 5."
);
System
.
out
.
println
(
"ZielEbene: Orthogonale Regression aus allen vier eindeutigen Deckelpunkten."
);
System
.
out
.
println
(
"Erwartung: Der abstehende Punkt wird auf diese berechnete Ausgleichsebene gesweept."
);
printPoint
(
"Abstehender Deckelpunkt vor doHeal()"
,
raisedPoint
);
System
.
out
.
println
(
"Abstand des Punktes zur Regressionsebene vor doHeal(): "
+
targetPlane
.
getDistance
(
raisedPoint
));
utilities
.
initialize
();
assertTrue
(
utilities
.
calculate
());
utilities
.
doHeal
();
printPoint
(
"Abstehender Deckelpunkt nach doHeal()"
,
raisedPoint
);
System
.
out
.
println
(
"Abstand des Punktes zur Regressionsebene nach doHeal(): "
+
targetPlane
.
getDistance
(
raisedPoint
));
System
.
out
.
println
();
assertEquals
(
0
,
targetPlane
.
getDistance
(
raisedPoint
),
ASSERTION_EPS
);
}
/**
* Produktivpfad für die ZielEbene: Nicht der Test baut die Ebene selbst,
* sondern PlanarCheck berechnet die Regressionsebene und steckt sie in den
* NonPlanarPolygonDistancePlaneError. SweepUtilities muss dann genau diese
* Error-Ebene verwenden.
*/
@Test
public
void
sweepsRaisedCubePointUsingRegressionPlaneFromPlanarCheck
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createRaisedTopCornerCube
();
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
5
);
Vertex
raisedPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
);
NonPlanarPolygonDistancePlaneError
error
=
errorFromPlanarCheck
(
sweepPolygon
);
SweepUtilities
utilities
=
createSweepUtilities
(
error
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Produktivpfad: PlanarCheck liefert die Regressionsebene für den Sweep ==="
);
System
.
out
.
println
(
"Modell: Würfel-Deckelfläche mit einem abstehenden Punkt bei z = 5."
);
System
.
out
.
println
(
"ZielEbene: kommt aus NonPlanarPolygonDistancePlaneError.getPlane(), erzeugt durch PlanarCheck."
);
System
.
out
.
println
(
"Erwartung: Der Punkt wird auf genau diese vom Check berechnete Ebene gesweept."
);
printPoint
(
"Abstehender Deckelpunkt vor doHeal()"
,
raisedPoint
);
System
.
out
.
println
(
"Abstand zur PlanarCheck-Ebene vor doHeal(): "
+
error
.
getPlane
().
getDistance
(
raisedPoint
));
utilities
.
initialize
();
assertTrue
(
utilities
.
calculate
());
utilities
.
doHeal
();
printPoint
(
"Abstehender Deckelpunkt nach doHeal()"
,
raisedPoint
);
System
.
out
.
println
(
"Abstand zur PlanarCheck-Ebene nach doHeal(): "
+
error
.
getPlane
().
getDistance
(
raisedPoint
));
System
.
out
.
println
();
assertEquals
(
0
,
error
.
getPlane
().
getDistance
(
raisedPoint
),
ASSERTION_EPS
);
}
/**
* Gleicher Würfel, aber andere ZielEbenen-Strategie: Der abstehende Punkt ist
* im Test bewusst als fehlerhafter Sweep-Punkt bekannt.
* Die Regressionsebene wird deshalb
* nur aus den drei stabilen Deckelpunkten berechnet.
* Bei diesem einfachen Würfel ergibt das die naheliegende perfekte Deckelebene z = 4.
*/
@Test
public
void
sweepsRaisedCubePointToRegressionPlaneOfStableFacetVertices
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createRaisedTopCornerCube
();
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
5
);
Vertex
raisedPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
);
Plane
targetPlane
=
regressionPlaneFromVertices
(
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
0
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
1
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
3
));
SweepUtilities
utilities
=
createSweepUtilities
(
sweepPolygon
,
targetPlane
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Regressionsebene aus stabilen Deckelpunkten: abstehender Würfel-Deckelpunkt ==="
);
System
.
out
.
println
(
"Modell: Der Punkt bei (4,4,5) wird als fehlerhafter Sweep-Punkt verstanden."
);
System
.
out
.
println
(
"ZielEbene: Orthogonale Regression aus den drei übrigen Deckelpunkten bei z = 4."
);
System
.
out
.
println
(
"Erwartung: Der Punkt landet auf der perfekten Deckelebene z = 4."
);
printPoint
(
"Abstehender Deckelpunkt vor doHeal()"
,
raisedPoint
);
utilities
.
initialize
();
assertTrue
(
utilities
.
calculate
());
utilities
.
doHeal
();
printPoint
(
"Abstehender Deckelpunkt nach doHeal()"
,
raisedPoint
);
System
.
out
.
println
(
"Abstand des Punktes zur stabilen Regressionsebene nach doHeal(): "
+
targetPlane
.
getDistance
(
raisedPoint
));
System
.
out
.
println
();
assertEquals
(
4
,
raisedPoint
.
getZ
(),
ASSERTION_EPS
);
assertEquals
(
0
,
targetPlane
.
getDistance
(
raisedPoint
),
ASSERTION_EPS
);
}
/**
* Entarteter Würfel: Der fehlerhafte Deckelpunkt liegt unter seinem
* angrenzenden Vertikalpunkt. Die Regressionsebene aus den stabilen
* Deckelpunkten ist weiterhin z = 4. Der Schnitt der abführenden Kante mit
* dieser Ebene liegt jedoch vor dem referencePoint; die Kante würde also kippen
* und der aktuelle Fall-2-Sweep muss ablehnen.
*/
@Test
public
void
rejectsDegeneratedCubeWhenStableRegressionPlaneWouldFlipOutgoingEdge
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createNonPlanarCube
(
4
,
0
,
-
5
);
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
2
);
Vertex
degeneratedPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
1
);
Plane
targetPlane
=
regressionPlaneFromVertices
(
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
0
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
3
));
SweepUtilities
utilities
=
createSweepUtilities
(
sweepPolygon
,
targetPlane
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Regressionsebene aus stabilen Deckelpunkten: entarteter Würfel-Kippfall ==="
);
System
.
out
.
println
(
"Modell: Der bekannte Deckelpunkt liegt bei z = -1 unter seinem angrenzenden Vertikalpunkt."
);
System
.
out
.
println
(
"ZielEbene: Regression aus den übrigen Deckelpunkten bei z = 4."
);
System
.
out
.
println
(
"Erwartung: calculate() lehnt ab, weil der Schnitt vor dem referencePoint liegt."
);
printPoint
(
"Entarteter Deckelpunkt vor calculate()"
,
degeneratedPoint
);
utilities
.
initialize
();
assertFalse
(
utilities
.
calculate
());
printPoint
(
"Entarteter Deckelpunkt nach calculate()"
,
degeneratedPoint
);
System
.
out
.
println
(
"Berechnet: calculate() == false, Punkt bleibt unverändert."
);
System
.
out
.
println
();
assertEquals
(-
1
,
degeneratedPoint
.
getZ
(),
ASSERTION_EPS
);
}
/**
* Kleiner Fall-2-Kippfall ohne Würfel: Die stabile Regressionsebene wird aus
* drei Punkten der Sweep-Fazette berechnet und entspricht x = 0. Die
* abführende Kante des bekannten Sweep-Punktes startet aber bei x = 0.5 und
* läuft zu x = 1. Der Schnitt mit x = 0 liegt deshalb vor dem referencePoint.
*/
@Test
public
void
rejectsCase2WhenStableRegressionPlaneWouldFlipOutgoingEdge
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createSweepCase2IntersectionBeforeReferencePoint
();
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
0
);
Vertex
sweepPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
1
);
Plane
targetPlane
=
regressionPlaneFromVertices
(
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
0
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
2
),
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
3
));
SweepUtilities
utilities
=
createSweepUtilities
(
sweepPolygon
,
targetPlane
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Regressionsebene aus stabilen Fazettenpunkten: kleiner Fall-2-Kippfall ==="
);
System
.
out
.
println
(
"Modell: Sweep-Punkt bei x = 1, referencePoint der abführenden Kante bei x = 0.5."
);
System
.
out
.
println
(
"ZielEbene: Regression aus drei stabilen Punkten der Sweep-Fazette; geometrisch x = 0."
);
System
.
out
.
println
(
"Erwartung: calculate() lehnt ab, weil der Schnittpunkt bei x = 0 vor dem referencePoint liegt."
);
printPoint
(
"Sweep-Punkt vor calculate()"
,
sweepPoint
);
utilities
.
initialize
();
assertFalse
(
utilities
.
calculate
());
printPoint
(
"Sweep-Punkt nach calculate()"
,
sweepPoint
);
System
.
out
.
println
(
"Berechnet: calculate() == false, Punkt bleibt unverändert."
);
System
.
out
.
println
();
assertEquals
(
1
,
sweepPoint
.
getX
(),
ASSERTION_EPS
);
}
/**
* Produktivpfad für einen Kippfall: PlanarCheck berechnet die Regressionsebene
* aus dem nicht-planaren Polygon und liefert sie über den Error. Der Sweep
* bekommt also keine manuell gesetzte ZielEbene. Die Kante darf nicht geheilt
* werden, wenn der daraus berechnete Schnitt vor dem referencePoint liegt.
*/
@Test
public
void
rejectsCase2WhenRegressionPlaneFromPlanarCheckWouldFlipOutgoingEdge
()
{
Building
building
=
SyntheticSweepPlanarityModelFactory
.
createSweepCase2IntersectionBeforeReferencePoint
();
Polygon
sweepPolygon
=
getPolygonAtIndex
(
building
,
0
);
Vertex
sweepPoint
=
sweepPolygon
.
getExteriorRing
().
getVertices
().
get
(
1
);
NonPlanarPolygonDistancePlaneError
error
=
errorFromPlanarCheck
(
sweepPolygon
);
SweepUtilities
utilities
=
createSweepUtilities
(
error
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Produktivpfad: PlanarCheck-Ebene im kleinen Fall-2-Kippfall ==="
);
System
.
out
.
println
(
"Modell: Sweep-Punkt bei x = 1, referencePoint der abführenden Kante bei x = 0.5."
);
System
.
out
.
println
(
"ZielEbene: kommt aus NonPlanarPolygonDistancePlaneError.getPlane(), erzeugt durch PlanarCheck."
);
System
.
out
.
println
(
"Erwartung: calculate() lehnt ab, wenn der Schnitt vor dem referencePoint liegt."
);
printPoint
(
"Sweep-Punkt vor calculate()"
,
sweepPoint
);
System
.
out
.
println
(
"Abstand zur PlanarCheck-Ebene vor calculate(): "
+
error
.
getPlane
().
getDistance
(
sweepPoint
));
utilities
.
initialize
();
assertFalse
(
utilities
.
calculate
());
printPoint
(
"Sweep-Punkt nach calculate()"
,
sweepPoint
);
System
.
out
.
println
(
"Berechnet: calculate() == false, Punkt bleibt unverändert."
);
System
.
out
.
println
();
assertEquals
(
1
,
sweepPoint
.
getX
(),
ASSERTION_EPS
);
}
private
SweepUtilities
createSweepUtilities
(
Polygon
sweepPolygon
,
Plane
targetPlane
)
{
NonPlanarPolygonDistancePlaneError
error
=
new
NonPlanarPolygonDistancePlaneError
(
sweepPolygon
,
0
,
null
,
targetPlane
);
return
createSweepUtilities
(
error
);
}
private
SweepUtilities
createSweepUtilities
(
NonPlanarPolygonDistancePlaneError
error
)
{
return
new
SweepUtilities
(
error
,
POINT_ON_PLANE_TOLERANCE
,
SWEEP_EDGE_AND_ANGLE_TOLERANCE
);
}
private
Polygon
getPolygonAtIndex
(
Building
building
,
int
index
)
{
Geometry
geometry
=
building
.
getGeometries
().
get
(
0
);
return
geometry
.
getPolygons
().
get
(
index
);
}
private
NonPlanarPolygonDistancePlaneError
errorFromPlanarCheck
(
Polygon
sweepPolygon
)
{
PlanarCheck
check
=
new
PlanarCheck
();
check
.
check
(
sweepPolygon
);
CheckResult
checkResult
=
sweepPolygon
.
getCheckResult
(
check
);
assertNotNull
(
checkResult
);
assertEquals
(
ResultStatus
.
ERROR
,
checkResult
.
getResultStatus
());
assertTrue
(
checkResult
.
getError
()
instanceof
NonPlanarPolygonDistancePlaneError
);
return
(
NonPlanarPolygonDistancePlaneError
)
checkResult
.
getError
();
}
private
Plane
regressionPlaneFromVertices
(
Vertex
...
vertices
)
{
List
<
Vertex
>
regressionVertices
=
List
.
of
(
vertices
);
return
OrthogonalRegressionPlane
.
calculateOrthogonalRegressionPlane
(
regressionVertices
);
}
private
void
printPoint
(
String
label
,
Vector3d
point
)
{
System
.
out
.
println
(
label
+
": x="
+
point
.
getX
()
+
", y="
+
point
.
getY
()
+
", z="
+
point
.
getZ
());
}
}
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