Commit 2877e8f0 authored by Matthias Betz's avatar Matthias Betz
Browse files

Add SceneBuilder.fromModel, FloatList.clear and OrbitCamera angle getters



Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 26d3eda9
package de.hft.stuttgart.citydoctor2.gui;
import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.gui.filter.ViewFilter;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.UnitVector3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.*;
import java.util.*;
import java.util.stream.Stream;
public class TriangulatedGeometry {
private static final PhongMaterial GRID_MAT = new PhongMaterial(Color.BLACK);
// random vector for calculating normal angles, for color determination
private static final UnitVector3d AXIS = new Vector3d(19, 0.8, 1.5).normalize();
private Vector3d movedBy;
private List<MeshView> meshes;
private List<PhongMaterial> materials;
public static TriangulatedGeometry of(Geometry geom) {
return of(geom.getPolygons());
}
public static TriangulatedGeometry of(Collection<? extends Polygon> polygons, Color basePolygonColor) {
TriangulatedGeometry triGeom = new TriangulatedGeometry();
triGeom.materials = new ArrayList<>();
triGeom.meshes = new ArrayList<>();
List<Vector3d> points = new ArrayList<>();
for (Polygon p : polygons) {
points.addAll(p.getExteriorRing().getVertices());
}
triGeom.movedBy = triGeom.findCenter(points);
addPolygonDataToTriGeom(polygons, basePolygonColor, triGeom);
return triGeom;
}
private static void addPolygonDataToTriGeom(Collection<? extends Polygon> polygons, Color basePolygonColor,
TriangulatedGeometry triGeom) {
for (Polygon p : polygons) {
TesselatedPolygon tp = p.tesselate();
TriangleMesh triMesh = new TriangleMesh(VertexFormat.POINT_TEXCOORD);
Map<Vector3d, Integer> indexMap = new HashMap<>();
List<Vector3d> vertices = new ArrayList<>();
int index = 0;
for (Triangle3d t : tp.getTriangles()) {
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP1());
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP2());
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP3());
}
for (Vector3d point : vertices) {
float x = (float) (point.getX() - triGeom.movedBy.getX());
float y = (float) (point.getY() - triGeom.movedBy.getY());
float z = (float) (point.getZ() - triGeom.movedBy.getZ());
triMesh.getPoints().addAll(x, y, z);
}
triMesh.getTexCoords().addAll(0, 0);
MeshView view = new MeshView(triMesh);
view.setUserData(new PolygonClickDispatcher(p));
PhongMaterial mat = triGeom.calculateMaterial(p, basePolygonColor);
triGeom.materials.add(mat);
triGeom.meshes.add(view);
}
}
public static TriangulatedGeometry of(CityDoctorModel model, List<ViewFilter> filters) {
List<Vector3d> points = new ArrayList<>();
addPointsFromCityObject(model.createFeatureStream().toList(), points);
TriangulatedGeometry triGeom = new TriangulatedGeometry();
triGeom.materials = new ArrayList<>();
triGeom.meshes = new ArrayList<>();
triGeom.movedBy = triGeom.findCenter(points);
addPolygonDataFromCityObjects(model.getBuildings(), triGeom, Color.WHITE, filters);
addPolygonDataFromCityObjects(model.getBridges(), triGeom, Color.LIGHTSTEELBLUE, filters);
addPolygonDataFromCityObjects(model.getTunnels(), triGeom, Color.SLATEGRAY, filters);
addPolygonDataFromCityObjects(model.getLand(), triGeom, Color.TAN, filters);
addPolygonDataFromCityObjects(model.getTransportation(), triGeom, Color.DIMGRAY, filters);
addPolygonDataFromCityObjects(model.getVegetation(), triGeom, Color.LIGHTGREEN, filters);
addPolygonDataFromCityObjects(model.getWater(), triGeom, Color.LIGHTSKYBLUE, filters);
addPolygonDataFromCityObjects(model.getCityFurniture(), triGeom, Color.BLUEVIOLET, filters);
addPolygonDataFromCityObjects(model.getOtherCityObjects(), triGeom, Color.BLACK, filters);
return triGeom;
}
private static void addPolygonDataFromCityObjects(Stream<? extends CityObject> cos,
TriangulatedGeometry triGeom, Color color, List<ViewFilter> filters) {
CityObjectCollector collector = new CityObjectCollector();
cos.forEach(cityObject -> cityObject.accept(collector));
collector.getCityObjects().forEach(cityObject -> addPolygonData(cityObject, triGeom, color, filters));
}
private static void addPolygonData(CityObject co, TriangulatedGeometry triGeom, Color color, List<ViewFilter> filters) {
for (Geometry geom : co.getGeometries()) {
if (isGeometryFiltered(co, geom, filters)) {
continue;
}
List<Polygon> polygons = new ArrayList<>();
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
polygons.add(p);
}
addPolygonDataToTriGeom(polygons, color, triGeom);
}
}
private static boolean isGeometryFiltered(CityObject co, Geometry geom, List<ViewFilter> filters) {
for (ViewFilter filter : filters) {
if (filter.allowedToUse(co, geom)) {
return false;
}
}
return true;
}
private static void addPointsFromCityObject(List<? extends CityObject> cos, List<Vector3d> points) {
CityObjectCollector collector = new CityObjectCollector();
cos.forEach(cityObject -> cityObject.accept(collector));
collector.getCityObjects().forEach(cityObject -> addPoints(cityObject, points));
}
private static void addPoints(CityObject co, List<Vector3d> points) {
for (Geometry geom : co.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
points.addAll(p.getExteriorRing().getVertices());
}
}
}
public static TriangulatedGeometry of(Collection<? extends Polygon> polygons) {
return of(polygons, Color.WHITE);
}
private int filterDuplicates(TriangleMesh triMesh, Map<Vector3d, Integer> indexMap, List<Vector3d> vertices,
int index, Vector3d v) {
Integer vertexIndex = indexMap.get(v);
if (vertexIndex == null) {
indexMap.put(v, index);
vertices.add(v);
vertexIndex = index;
index++;
}
triMesh.getFaces().addAll(vertexIndex, 0);
return index;
}
private PhongMaterial calculateMaterial(Polygon p, Color baseColor) {
Vector3d normal = p.calculateNormalNormalized();
if (p.getRenderColor() != null && p.getRenderColor() != Color.WHITE) {
baseColor = p.getRenderColor();
}
double cos = normal.dot(AXIS);
double acos = Math.acos(cos);
// normalize to range [0.3, 0.9]
acos = acos / Math.PI;
acos = acos * 0.6 + 0.3;
Color derivedColor = baseColor.deriveColor(0, 1.0, acos, 1.0);
return new PhongMaterial(derivedColor);
}
private Vector3d findCenter(List<Vector3d> points) {
double xMin = Double.MAX_VALUE;
double yMin = Double.MAX_VALUE;
double zMin = Double.MAX_VALUE;
double xMax = Double.NEGATIVE_INFINITY;
double yMax = Double.NEGATIVE_INFINITY;
double zMax = Double.NEGATIVE_INFINITY;
for (Vector3d point : points) {
if (point.getX() < xMin) {
xMin = point.getX();
}
if (point.getX() > xMax) {
xMax = point.getX();
}
if (point.getY() < yMin) {
yMin = point.getY();
}
if (point.getY() > yMax) {
yMax = point.getY();
}
if (point.getZ() < zMin) {
zMin = point.getZ();
}
if (point.getZ() > zMax) {
zMax = point.getZ();
}
}
// center
double x = (xMax - xMin) / 2 + xMin;
double y = (yMax - yMin) / 2 + yMin;
double z = (zMax - zMin) / 2 + zMin;
return new Vector3d(x, y, z);
}
public Vector3d getMovedBy() {
return movedBy;
}
public void setCullFace(CullFace currentCulling) {
for (MeshView mesh : meshes) {
mesh.setCullFace(currentCulling);
}
}
public void setDrawMode(DrawMode currentDrawMode) {
if (currentDrawMode == DrawMode.LINE) {
for (MeshView mesh : meshes) {
mesh.setDrawMode(currentDrawMode);
mesh.setMaterial(GRID_MAT);
}
} else if (currentDrawMode == DrawMode.FILL) {
for (int i = 0; i < meshes.size(); i++) {
MeshView mesh = meshes.get(i);
mesh.setDrawMode(currentDrawMode);
mesh.setMaterial(materials.get(i));
}
}
}
public List<MeshView> getMeshes() {
return meshes;
}
}
......@@ -34,5 +34,8 @@ public class FloatList {
public int size() { return size; }
/** Resets the list to empty without releasing the backing array. */
public void clear() { size = 0; }
public float[] toArray() { return Arrays.copyOf(data, size); }
}
......@@ -59,6 +59,12 @@ public class OrbitCamera {
public double distance() { return distance; }
/** Current azimuth (rotation about Z) in degrees. */
public double azimuthDeg() { return azimuthDeg; }
/** Current elevation (rotation about X) in degrees. */
public double elevationDeg() { return elevationDeg; }
/** Combined projection * view * model-rotation, column-major, ready for a GL uniform. */
public float[] viewProjection() {
float[] proj = Mat4.perspective((float) FOVY_RAD, aspect, NEAR, FAR);
......
package de.hft.stuttgart.citydoctor2.gui.gl;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
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 de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import javafx.scene.paint.Color;
/**
......@@ -51,6 +57,100 @@ public final class SceneBuilder {
return new SceneData(acc.positions(), acc.colors(), acc.ids(), metas, registry, centerArr);
}
/**
* Builds ALL features of the model into a single {@link SceneData} with one shared center,
* one {@link PickRegistry} and continuous triangle indices. Filtering by LOD/roof happens later
* via the index set, not at build time. Replicates the per-feature base colours of the legacy
* {@code TriangulatedGeometry.of(model, filters)}.
*/
public static SceneData fromModel(CityDoctorModel model) {
List<Polygon> allPolygons = new ArrayList<>();
Vector3d center = findCenterOfModel(model);
MeshAccumulator acc = new MeshAccumulator(center);
PickRegistry registry = new PickRegistry();
List<TriangleMeta> metas = new ArrayList<>();
int[] triangleIndex = {0};
appendFeatures(model.getBuildings(), Color.WHITE, acc, registry, metas, triangleIndex);
appendFeatures(model.getBridges(), Color.LIGHTSTEELBLUE, acc, registry, metas, triangleIndex);
appendFeatures(model.getTunnels(), Color.SLATEGRAY, acc, registry, metas, triangleIndex);
appendFeatures(model.getLand(), Color.TAN, acc, registry, metas, triangleIndex);
appendFeatures(model.getTransportation(), Color.DIMGRAY, acc, registry, metas, triangleIndex);
appendFeatures(model.getVegetation(), Color.LIGHTGREEN, acc, registry, metas, triangleIndex);
appendFeatures(model.getWater(), Color.LIGHTSKYBLUE, acc, registry, metas, triangleIndex);
appendFeatures(model.getCityFurniture(), Color.BLUEVIOLET, acc, registry, metas, triangleIndex);
appendFeatures(model.getOtherCityObjects(), Color.BLACK, acc, registry, metas, triangleIndex);
double[] centerArr = {center.getX(), center.getY(), center.getZ()};
return new SceneData(acc.positions(), acc.colors(), acc.ids(), metas, registry, centerArr);
}
private static void appendFeatures(Stream<? extends CityObject> cos, Color color,
MeshAccumulator acc, PickRegistry registry,
List<TriangleMeta> metas, int[] triangleIndex) {
CityObjectCollector collector = new CityObjectCollector();
cos.forEach(cityObject -> cityObject.accept(collector));
for (CityObject co : collector.getCityObjects()) {
for (Geometry geom : co.getGeometries()) {
int lod = geom.getLod() == null ? Lod.LOD2.ordinal() : geom.getLod().ordinal();
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
boolean roof = p.getPartOfSurface() != null
&& p.getPartOfSurface().getType() == BoundarySurfaceType.ROOF;
appendPolygon(p, color, lod, roof, acc, registry, metas, triangleIndex);
}
}
}
}
private static void appendPolygon(Polygon p, Color baseColor, int lod, boolean roof,
MeshAccumulator acc, PickRegistry registry,
List<TriangleMeta> metas, int[] triangleIndex) {
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[0]++, p, lod, roof));
}
}
private static Vector3d findCenterOfModel(CityDoctorModel model) {
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;
boolean any = false;
for (CityObject co : model.createFeatureStream().toList()) {
CityObjectCollector collector = new CityObjectCollector();
co.accept(collector);
for (CityObject inner : collector.getCityObjects()) {
for (Geometry geom : inner.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
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());
any = true;
}
}
}
}
}
if (!any) {
return new Vector3d(0, 0, 0);
}
return new Vector3d((xMax - xMin) / 2 + xMin, (yMax - yMin) / 2 + yMin, (zMax - zMin) / 2 + zMin);
}
private static Color chooseBase(Polygon p, Color fallback) {
Color rc = p.getRenderColor();
if (rc != null && !rc.equals(Color.WHITE)) {
......
......@@ -23,4 +23,14 @@ public class FloatListTest {
list.add(1f, 2f, 3f);
assertArrayEquals(new float[]{1,2,3}, list.toArray(), 0.0f);
}
@Test
public void clearResetsSizeAndArray() {
FloatList list = new FloatList(4);
list.add(1f, 2f, 3f);
list.add(4f);
list.clear();
assertEquals(0, list.size());
assertEquals(0, list.toArray().length);
}
}
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