Commit 7d61c01b authored by Numanoglu's avatar Numanoglu
Browse files

WIP multi target plane infrastructure

parent a7866e92
Pipeline #12437 failed with stage
in 2 minutes and 21 seconds
...@@ -8,11 +8,12 @@ import org.apache.logging.log4j.LogManager; ...@@ -8,11 +8,12 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError; import de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
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;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; 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.Plane;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class SweepUtilities { public class SweepUtilities {
...@@ -115,6 +116,7 @@ public class SweepUtilities { ...@@ -115,6 +116,7 @@ public class SweepUtilities {
if (!v.hasNewPoint()) if (!v.hasNewPoint())
return false; return false;
break; break;
case MULTI:
case NONE: case NONE:
case ERROR: case ERROR:
return false; return false;
...@@ -150,37 +152,80 @@ public class SweepUtilities { ...@@ -150,37 +152,80 @@ public class SweepUtilities {
public void calcNewTargetPlane() { public void calcNewTargetPlane() {
//todo:
List<SweepVertex> multiVerts = new LinkedList<>(); List<SweepVertex> multiVerts = new LinkedList<>();
// SweepVertices mit mehr als einer outgoing Edge in multiVerts-Liste sammeln, for (SweepVertex sweepVertex : sweepVertices) {
// im Unterschied zu initStatus unabhaengig davon, ob sie auf der alten Zielebene liegen if (sweepVertex.countOutgoingEdges() > 1) {
multiVerts.add(sweepVertex);
}
}
Plane newTargetPlane = null; Plane newTargetPlane = null;
if ( multiVerts.size() == 0 ) { if ( multiVerts.size() == 0 ) {
return; //kann eigentlich nicht passieren return; //kann eigentlich nicht passieren
} }
else if ( multiVerts.size() == 1 ) { else if ( multiVerts.size() == 1 ) {
//newTargetPlane = new Plane(Normale der alten Zielebene, multiVerts[0].getVertex()) newTargetPlane = new Plane(targetPlane.getNormal(), multiVerts.get(0).getSweepVertex());
} }
else if ( multiVerts.size() == 2 ) { else if ( multiVerts.size() == 2 ) {
// Normale entspr. Diss. von Raul berechnen newTargetPlane = calcNewTargetPlaneFromTwoMultiVerts(multiVerts);
//newTargetPlane = new Plane(neue Normale, multiVerts[0].getVertex())
} }
else if ( multiVerts.size() == 3 ) { else if ( multiVerts.size() == 3 ) {
// Wenn die drei Multi-Knoten auf einer Geraden liegen, siehe Fall ==2 newTargetPlane = calcNewTargetPlaneFromThreeMultiVerts(multiVerts);
// Ansonsten
// Normale einer Ebene durch die drei Punkte berechnen
//newTargetPlane = new Plane(neue Normale, multiVerts[0].getVertex())
} }
else { else {
// Ausgleichsebene zur Liste der Multi-Knoten berechnen newTargetPlane = calcRegressionPlaneForMultiVerts(multiVerts);
// mit Methode von Matthias
} }
if ( newTargetPlane != null ) if ( newTargetPlane != null )
targetPlane = newTargetPlane; targetPlane = newTargetPlane;
} }
private Plane calcNewTargetPlaneFromTwoMultiVerts(List<SweepVertex> multiVerts) {
Vector3d firstPoint = multiVerts.get(0).getSweepVertex();
Vector3d secondPoint = multiVerts.get(1).getSweepVertex();
Vector3d fixedPointDirection = secondPoint.minus(firstPoint);
// Name der Toleranz hier irreführend : eigentlich ob zwei MultiKnoten unterscheidbar sind
if (fixedPointDirection.getLength() < pointOnPlaneTolerance) {
return new Plane(targetPlane.getNormal(), firstPoint);
}
Vector3d sideDirection = targetPlane.getNormal().cross(fixedPointDirection);
if (sideDirection.getLength() < pointOnPlaneTolerance) {
return new Plane(targetPlane.getNormal(), firstPoint);
}
Vector3d newNormal = fixedPointDirection.cross(sideDirection);
return new Plane(newNormal, firstPoint);
}
private Plane calcNewTargetPlaneFromThreeMultiVerts(List<SweepVertex> multiVerts) {
Vector3d firstPoint = multiVerts.get(0).getSweepVertex();
Vector3d secondPoint = multiVerts.get(1).getSweepVertex();
Vector3d thirdPoint = multiVerts.get(2).getSweepVertex();
Vector3d firstDirection = secondPoint.minus(firstPoint);
Vector3d secondDirection = thirdPoint.minus(firstPoint);
// Normalieren?!
Vector3d newNormal = firstDirection.cross(secondDirection);
if (newNormal.getLength() < pointOnPlaneTolerance) {
return calcNewTargetPlaneFromTwoMultiVerts(multiVerts);
}
return new Plane(newNormal, firstPoint);
}
private Plane calcRegressionPlaneForMultiVerts(List<SweepVertex> multiVerts) {
List<Vector3d> points = new ArrayList<>(multiVerts.size());
for (SweepVertex multiVert : multiVerts) {
points.add(multiVert.getSweepVertex());
}
return OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(points);
}
private int countSweepVertices(Polygon p) { private int countSweepVertices(Polygon p) {
int nVerts = countSweepVertices(p.getExteriorRing()); int nVerts = countSweepVertices(p.getExteriorRing());
......
...@@ -149,6 +149,7 @@ public class SweepVertex { ...@@ -149,6 +149,7 @@ public class SweepVertex {
newPoint = intersectionPoint; newPoint = intersectionPoint;
return; return;
case MULTI:
case NONE: case NONE:
case ERROR: case ERROR:
default: default:
......
package de.hft.stuttgart.citydoctor2.healing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError;
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.Plane;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class SweepUtilitiesMultiVertexTest {
/**
* Ein Multi-Knoten entsteht hier durch ein kleines Zusatzdreieck, das mit der
* Sweep-Fläche nur den Außenringpunkt an Index 0 teilt. Dadurch besitzt dieser
* Punkt zwei abführende Kanten. calcNewTargetPlane() soll die Zielebene parallel
* zur alten Ebene durch diesen Multi-Knoten legen, so dass er danach ON_PLANE
* ist.
*/
@Test
public void initializeMovesTargetPlaneThroughOneMultiVertex() {
Building building = SyntheticSweepPlanarityModelFactory.createSweepCase3OneMultiVertex();
Polygon sweepPolygon = getPolygonAtIndex(building, 0);
Vertex multiVertex = sweepPolygon.getExteriorRing().getVertices().get(0);
SweepUtilities utilities = createSweepUtilitiesWithDistantHorizontalTargetPlane(sweepPolygon);
System.out.println();
System.out.println("=== Multi-Knoten: Zielebene durch einen komplexen Punkt legen ===");
System.out.println("Modell: Außenring Index 0 teilt sich einen Punkt mit einem Zusatzdreieck.");
System.out.println("Erwartung: Dieser Punkt hat 2 abführende Kanten und wird nach der neuen Zielebene ON_PLANE.");
printPoint("Testknoten Außenring[0]", multiVertex);
utilities.initialize();
SweepVertex sweepVertex = utilities.getSweepVertices().get(0);
System.out.println("Berechnet: Status " + sweepVertex.getStatus() + ", abführende Kanten "
+ sweepVertex.countOutgoingEdges() + ".");
System.out.println();
assertEquals(multiVertex, sweepVertex.getSweepVertex());
assertEquals(2, sweepVertex.countOutgoingEdges());
assertEquals(SweepVertexStatus.ON_PLANE, sweepVertex.getStatus());
}
/**
* Zwei Multi-Knoten liegen an den Außenringpunkten 0 und 1. Die neue Zielebene
* soll durch beide Punkte laufen. Damit bleiben genau diese problematischen
* Fall-3-Knoten nach der zweiten Statusermittlung unbewegt auf der Zielebene.
*/
@Test
public void initializeMovesTargetPlaneThroughTwoMultiVertices() {
Building building = SyntheticSweepPlanarityModelFactory.createSweepCase3TwoMultiVertices();
Polygon sweepPolygon = getPolygonAtIndex(building, 0);
Vertex firstMultiVertex = sweepPolygon.getExteriorRing().getVertices().get(0);
Vertex secondMultiVertex = sweepPolygon.getExteriorRing().getVertices().get(1);
SweepUtilities utilities = createSweepUtilitiesWithDistantHorizontalTargetPlane(sweepPolygon);
System.out.println();
System.out.println("=== Multi-Knoten: Zielebene durch zwei komplexe Punkte legen ===");
System.out.println("Modell: Außenring Indizes 0 und 1 haben je ein Zusatzdreieck.");
System.out.println("Erwartung: Beide Punkte haben 2 abführende Kanten und werden nach der neuen Zielebene ON_PLANE.");
printPoint("Testknoten Außenring[0]", firstMultiVertex);
printPoint("Testknoten Außenring[1]", secondMultiVertex);
utilities.initialize();
SweepVertex firstSweepVertex = utilities.getSweepVertices().get(0);
SweepVertex secondSweepVertex = utilities.getSweepVertices().get(1);
System.out.println("Berechnet Außenring[0]: Status " + firstSweepVertex.getStatus() + ", abführende Kanten "
+ firstSweepVertex.countOutgoingEdges() + ".");
System.out.println("Berechnet Außenring[1]: Status " + secondSweepVertex.getStatus() + ", abführende Kanten "
+ secondSweepVertex.countOutgoingEdges() + ".");
System.out.println();
assertMultiVertexOnPlane(firstSweepVertex, firstMultiVertex);
assertMultiVertexOnPlane(secondSweepVertex, secondMultiVertex);
}
/**
* Drei Multi-Knoten liegen an den Außenringpunkten 0, 1 und 2. Die neue
* Zielebene kann dadurch direkt aus drei nicht-kollinearen Punkten konstruiert
* werden. Diese drei Knoten sollen anschließend ON_PLANE sein.
*/
@Test
public void initializeMovesTargetPlaneThroughThreeMultiVertices() {
Building building = SyntheticSweepPlanarityModelFactory.createSweepCase3ThreeMultiVertices();
Polygon sweepPolygon = getPolygonAtIndex(building, 0);
SweepUtilities utilities = createSweepUtilitiesWithDistantHorizontalTargetPlane(sweepPolygon);
System.out.println();
System.out.println("=== Multi-Knoten: Zielebene durch drei komplexe Punkte legen ===");
System.out.println("Modell: Außenring Indizes 0, 1 und 2 haben je ein Zusatzdreieck.");
System.out.println("Erwartung: Die drei Punkte bestimmen die neue Zielebene und werden ON_PLANE.");
printPoint("Testknoten Außenring[0]", sweepPolygon.getExteriorRing().getVertices().get(0));
printPoint("Testknoten Außenring[1]", sweepPolygon.getExteriorRing().getVertices().get(1));
printPoint("Testknoten Außenring[2]", sweepPolygon.getExteriorRing().getVertices().get(2));
utilities.initialize();
for (int i = 0; i < 3; i++) {
SweepVertex sweepVertex = utilities.getSweepVertices().get(i);
Vertex expectedVertex = sweepPolygon.getExteriorRing().getVertices().get(i);
System.out.println("Berechnet Außenring[" + i + "]: Status " + sweepVertex.getStatus()
+ ", abführende Kanten " + sweepVertex.countOutgoingEdges() + ".");
assertMultiVertexOnPlane(sweepVertex, expectedVertex);
}
System.out.println();
}
/**
* Vier Multi-Knoten treiben den Regressionsebenen-Zweig an. In diesem
* synthetischen Modell sind die vier Eckpunkte absichtlich nicht exakt koplanar.
* Die Regressionsebene kann daher nicht alle Multi-Knoten exakt fixieren; nach
* der zweiten Statusermittlung muss mindestens ein Multi-Knoten ERROR werden,
* solange die echte Fall-3-Topologie noch fehlt.
*/
@Test
public void initializeUsesRegressionPlaneForFourMultiVerticesAndLeavesUnresolvedOnesAsError() {
Building building = SyntheticSweepPlanarityModelFactory.createSweepCase3FourMultiVertices();
Polygon sweepPolygon = getPolygonAtIndex(building, 0);
SweepUtilities utilities = createSweepUtilitiesWithDistantHorizontalTargetPlane(sweepPolygon);
System.out.println();
System.out.println("=== Multi-Knoten: vier komplexe Punkte erzwingen Regressionsebene ===");
System.out.println("Modell: Alle vier Außenringpunkte haben Zusatzdreiecke und sind nicht exakt koplanar.");
System.out.println("Erwartung: Alle vier haben 2 abführende Kanten; mindestens einer bleibt nach der Regression ERROR.");
utilities.initialize();
int errorCount = 0;
for (int i = 0; i < 4; i++) {
SweepVertex sweepVertex = utilities.getSweepVertices().get(i);
printPoint("Testknoten Außenring[" + i + "]", sweepVertex.getSweepVertex());
System.out.println("Berechnet Außenring[" + i + "]: Status " + sweepVertex.getStatus()
+ ", abführende Kanten " + sweepVertex.countOutgoingEdges() + ".");
assertEquals(2, sweepVertex.countOutgoingEdges());
if (sweepVertex.getStatus() == SweepVertexStatus.ERROR) {
errorCount++;
}
}
System.out.println("Berechnet: " + errorCount + " Multi-Knoten bleiben ERROR.");
System.out.println();
assertTrue(errorCount > 0);
}
private void assertMultiVertexOnPlane(SweepVertex sweepVertex, Vertex expectedVertex) {
assertEquals(expectedVertex, sweepVertex.getSweepVertex());
assertEquals(2, sweepVertex.countOutgoingEdges());
assertEquals(SweepVertexStatus.ON_PLANE, sweepVertex.getStatus());
}
private SweepUtilities createSweepUtilitiesWithDistantHorizontalTargetPlane(Polygon sweepPolygon) {
Plane initialTargetPlane = new Plane(
new Vector3d(0, 0, 1),
new Vector3d(0, 0, 10));
NonPlanarPolygonDistancePlaneError error = new NonPlanarPolygonDistancePlaneError(sweepPolygon, 0, null,
initialTargetPlane);
return new SweepUtilities(error, 0.001, 0.001);
}
private Polygon getPolygonAtIndex(Building building, int index) {
Geometry geometry = building.getGeometries().get(0);
return geometry.getPolygons().get(index);
}
private void printPoint(String label, Vector3d point) {
System.out.println(label + ": x=" + point.getX() + ", y=" + point.getY() + ", z=" + point.getZ());
}
}
...@@ -57,6 +57,26 @@ public class SyntheticSweepPlanarityModelFactory { ...@@ -57,6 +57,26 @@ public class SyntheticSweepPlanarityModelFactory {
createSweepCase2ExtendingEdge(), createSweepCase2ExtendingEdge(),
new File("sweep-case2-extending-edge.gml"), new File("sweep-case2-extending-edge.gml"),
"SweepCase2ExtendingEdge"); "SweepCase2ExtendingEdge");
exportForGui(
createSweepCase3OneMultiVertex(),
new File("sweep-case3-one-multi-vertex.gml"),
"SweepCase3OneMultiVertex");
exportForGui(
createSweepCase3TwoMultiVertices(),
new File("sweep-case3-two-multi-vertices.gml"),
"SweepCase3TwoMultiVertices");
exportForGui(
createSweepCase3ThreeMultiVertices(),
new File("sweep-case3-three-multi-vertices.gml"),
"SweepCase3ThreeMultiVertices");
exportForGui(
createSweepCase3FourMultiVertices(),
new File("sweep-case3-four-multi-vertices.gml"),
"SweepCase3FourMultiVertices");
} }
static Building createNonPlanarCube(double boxsize, double bottomerr, double topLift) { static Building createNonPlanarCube(double boxsize, double bottomerr, double topLift) {
...@@ -243,6 +263,44 @@ public class SyntheticSweepPlanarityModelFactory { ...@@ -243,6 +263,44 @@ public class SyntheticSweepPlanarityModelFactory {
return createSweepCase2SingleOutgoingEdge(new Vertex(1, 2, 0)); return createSweepCase2SingleOutgoingEdge(new Vertex(1, 2, 0));
} }
/**
* Fall-3-Modell: Genau eine Ecke des nicht-planaren Sweep-Polygons besitzt
* zwei abführende Kanten. Damit wird der erste calcNewTargetPlane-Zweig
* sichtbar: Die neue Zielebene bleibt parallel zur alten und läuft durch diesen
* einen Multi-Knoten.
*/
static Building createSweepCase3OneMultiVertex() {
return createSweepCase3MultiVertexPolygon(true, false, false, false);
}
/**
* Fall-3-Modell: Zwei Ecken des nicht-planaren Sweep-Polygons besitzen jeweils
* zwei abführende Kanten. Damit wird die Zielebene durch ein festes Punktepaar
* gelegt und möglichst nah an der alten Ebenenrichtung gehalten.
*/
static Building createSweepCase3TwoMultiVertices() {
return createSweepCase3MultiVertexPolygon(true, true, false, false);
}
/**
* Fall-3-Modell: Drei Ecken des nicht-planaren Sweep-Polygons besitzen jeweils
* zwei abführende Kanten. Das ist der direkte Drei-Punkte-Ebenenfall; bei
* Kollinearität fällt die Implementierung auf die Zwei-Punkte-Konstruktion
* zurück.
*/
static Building createSweepCase3ThreeMultiVertices() {
return createSweepCase3MultiVertexPolygon(true, true, true, false);
}
/**
* Fall-3-Modell: Alle vier Ecken des nicht-planaren Sweep-Polygons sind
* Multi-Knoten. Damit wird der Zweig angestoßen, der nur aus den Multi-Knoten
* eine Regressionsebene berechnet.
*/
static Building createSweepCase3FourMultiVertices() {
return createSweepCase3MultiVertexPolygon(true, true, true, true);
}
private static Building createSweepCase2SingleOutgoingEdge(Vertex outgoingTarget) { private static Building createSweepCase2SingleOutgoingEdge(Vertex outgoingTarget) {
Building building = new Building(); Building building = new Building();
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2, Orientation.OUTWARD); Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2, Orientation.OUTWARD);
...@@ -259,6 +317,43 @@ public class SyntheticSweepPlanarityModelFactory { ...@@ -259,6 +317,43 @@ public class SyntheticSweepPlanarityModelFactory {
geom.updateEdgesAndVertices(); geom.updateEdgesAndVertices();
return building; return building;
} }
private static Building createSweepCase3MultiVertexPolygon(boolean firstIsMulti, boolean secondIsMulti,
boolean thirdIsMulti, boolean fourthIsMulti) {
Building building = new Building();
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2, Orientation.OUTWARD);
building.addGeometry(geom);
Vertex lowerLeft = new Vertex(0, 0, 0);
Vertex lowerRight = new Vertex(4, 0, 0.4);
Vertex upperRight = new Vertex(4, 4, -0.3);
Vertex upperLeft = new Vertex(0, 4, 0.2);
addPolygon(geom, lowerLeft, lowerRight, upperRight, upperLeft);
if (firstIsMulti) {
addMultiVertexWing(geom, lowerLeft, -1.2, -0.8);
}
if (secondIsMulti) {
addMultiVertexWing(geom, lowerRight, 1.2, -0.8);
}
if (thirdIsMulti) {
addMultiVertexWing(geom, upperRight, 1.2, 0.8);
}
if (fourthIsMulti) {
addMultiVertexWing(geom, upperLeft, -1.2, 0.8);
}
geom.updateEdgesAndVertices();
return building;
}
private static void addMultiVertexWing(Geometry geom, Vertex anchor, double dx, double dy) {
addPolygon(geom,
anchor,
new Vertex(anchor.getX() + dx, anchor.getY(), anchor.getZ() + 1.2),
new Vertex(anchor.getX(), anchor.getY() + dy, anchor.getZ() - 1.0));
}
private static void exportForGui(Building building, File output, String gmlId) throws CityDoctorWriteException { private static void exportForGui(Building building, File output, String gmlId) throws CityDoctorWriteException {
CityDoctorModel model = createCityDoctorModel(building, output, gmlId); CityDoctorModel model = createCityDoctorModel(building, output, gmlId);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment