Commit 5c34fcf8 authored by Matthias Betz's avatar Matthias Betz
Browse files

Add SceneBuilder turning polygons into SceneData



Implements SceneBuilder with fromPolygons() and build() entry points,
plus private helpers for center computation and base color selection.
Test loads real GML fixture (building with BoundarySurfaces) to verify
buffer length consistency and that all picking ids resolve to Polygons.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent de0fb85d
package de.hft.stuttgart.citydoctor2.gui.gl;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javafx.scene.paint.Color;
/**
* Builds {@link SceneData} from model polygons: tesselate, bake shade colour, assign picking ids,
* accumulate vertices and per-triangle metadata, recentre by the bounding-box centre.
* Intended to run on a worker thread.
*/
public final class SceneBuilder {
private SceneBuilder() {}
public static SceneData fromPolygons(Collection<? extends Polygon> polygons, Color baseColor) {
return build(polygons, baseColor, Lod.LOD2.ordinal(), false);
}
/** General entry: caller supplies polygons already chosen, plus their lod/roof attributes. */
public static SceneData build(Collection<? extends Polygon> polygons, Color baseColor,
int lod, boolean roof) {
Vector3d center = findCenter(polygons);
MeshAccumulator acc = new MeshAccumulator(center);
PickRegistry registry = new PickRegistry();
List<TriangleMeta> metas = new ArrayList<>();
int triangleIndex = 0;
for (Polygon p : polygons) {
Color base = chooseBase(p, baseColor);
Color shaded = ShadeColor.shade(p.calculateNormalNormalized(), base);
float r = (float) shaded.getRed();
float g = (float) shaded.getGreen();
float b = (float) shaded.getBlue();
int id = registry.register(p);
TesselatedPolygon tp = p.tesselate();
for (Triangle3d t : tp.getTriangles()) {
acc.addTriangle(t, id, r, g, b);
metas.add(new TriangleMeta(triangleIndex++, p, lod, roof));
}
}
double[] centerArr = {center.getX(), center.getY(), center.getZ()};
return new SceneData(acc.positions(), acc.colors(), acc.ids(), metas, registry, centerArr);
}
private static Color chooseBase(Polygon p, Color fallback) {
Color rc = p.getRenderColor();
if (rc != null && !rc.equals(Color.WHITE)) {
return rc;
}
return fallback;
}
private static Vector3d findCenter(Collection<? extends Polygon> polygons) {
double xMin = Double.MAX_VALUE, yMin = Double.MAX_VALUE, zMin = Double.MAX_VALUE;
double xMax = -Double.MAX_VALUE, yMax = -Double.MAX_VALUE, zMax = -Double.MAX_VALUE;
for (Polygon p : polygons) {
for (Vector3d v : p.getExteriorRing().getVertices()) {
xMin = Math.min(xMin, v.getX()); xMax = Math.max(xMax, v.getX());
yMin = Math.min(yMin, v.getY()); yMax = Math.max(yMax, v.getY());
zMin = Math.min(zMin, v.getZ()); zMax = Math.max(zMax, v.getZ());
}
}
if (xMin > xMax) {
return new Vector3d(0, 0, 0);
}
return new Vector3d((xMax - xMin) / 2 + xMin, (yMax - yMin) / 2 + yMin, (zMax - zMin) / 2 + zMin);
}
}
package de.hft.stuttgart.citydoctor2.gui.gl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.paint.Color;
import org.junit.BeforeClass;
import org.junit.Test;
public class SceneBuilderTest {
private static List<Polygon> polygons;
@BeforeClass
public static void loadFixture() throws Exception {
ParserConfiguration config = new ParserConfiguration(8, false);
CityDoctorModel model = CityGmlParser.parseCityGmlFile(
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0001-T0001.gml", config);
polygons = new ArrayList<>();
model.getBuildings().forEach(b -> {
for (Geometry g : b.getGeometries()) {
for (Polygon p : g.getPolygons()) {
if (!p.isLink()) {
polygons.add(p);
}
}
}
for (de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface bs : b.getBoundarySurfaces()) {
for (Geometry g : bs.getGeometries()) {
for (Polygon p : g.getPolygons()) {
if (!p.isLink()) {
polygons.add(p);
}
}
}
}
});
assertTrue("fixture should contain polygons", !polygons.isEmpty());
}
@Test
public void buildsConsistentBufferLengths() {
SceneData data = SceneBuilder.fromPolygons(polygons, Color.WHITE);
int vertices = data.positions().length / 3;
assertEquals(vertices * 3, data.colors().length);
assertEquals(vertices, data.ids().length);
assertEquals(vertices, data.triangleCount() * 3);
}
@Test
public void everyPolygonGetsAPickingIdResolvingToAPolygon() {
SceneData data = SceneBuilder.fromPolygons(polygons, Color.WHITE);
for (int id : data.ids()) {
assertTrue(data.pickRegistry().resolve(id) instanceof Polygon);
}
}
}
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