From b02fab9e1484b3f74f5e1b309f49333d2dddaf1d Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Sat, 19 Oct 2024 14:32:55 +0200 Subject: [PATCH 01/85] Require VectorXZ and VectorXYZ to be finite so errors surface early --- .../org/osm2world/core/math/TriangleXYZ.java | 4 +--- .../java/org/osm2world/core/math/VectorXYZ.java | 16 ++++++++-------- .../java/org/osm2world/core/math/VectorXZ.java | 4 +++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/TriangleXYZ.java b/src/main/java/org/osm2world/core/math/TriangleXYZ.java index 60dd02ff6..e39434832 100644 --- a/src/main/java/org/osm2world/core/math/TriangleXYZ.java +++ b/src/main/java/org/osm2world/core/math/TriangleXYZ.java @@ -15,9 +15,7 @@ public TriangleXYZ(VectorXYZ v1, VectorXYZ v2, VectorXYZ v3) { this.v2 = v2; this.v3 = v3; - if (!v1.isFinite() || !v2.isFinite() || !v3.isFinite()) { - throw new InvalidGeometryException("Triangle vertex is not finite: " + v1 + ", " + v2 + ", " + v3); - } else if (getArea() < 1e-6) { + if (getArea() < 1e-6) { // degenerate triangle: all three points are (almost, to account for floating point arithmetic) in a line throw new InvalidGeometryException("Degenerate triangle: " + v1 + ", " + v2 + ", " + v3); } diff --git a/src/main/java/org/osm2world/core/math/VectorXYZ.java b/src/main/java/org/osm2world/core/math/VectorXYZ.java index 25f22ea6b..8d405d158 100644 --- a/src/main/java/org/osm2world/core/math/VectorXYZ.java +++ b/src/main/java/org/osm2world/core/math/VectorXYZ.java @@ -1,5 +1,7 @@ package org.osm2world.core.math; +import static java.lang.Double.doubleToLongBits; +import static java.lang.Double.isFinite; import static java.lang.Math.*; import java.util.ArrayList; @@ -13,7 +15,9 @@ public VectorXYZ(double x, double y, double z) { this.x = x; this.y = y; this.z = z; - assert isFinite(); + if (!(isFinite(x) && isFinite(y) && isFinite(z))) { + throw new InvalidGeometryException("Vector not finite: (" + x + ", " + y + ", " + z + ")"); + } } @Override @@ -31,10 +35,6 @@ public double getZ() { return z; } - public boolean isFinite() { - return Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z); - } - public double length() { return Math.sqrt(x*x + y*y + z*z); } @@ -253,11 +253,11 @@ public int hashCode() { final int prime = 31; int result = 1; long temp; - temp = Double.doubleToLongBits(x); + temp = doubleToLongBits(x); result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(y); + temp = doubleToLongBits(y); result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(z); + temp = doubleToLongBits(z); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } diff --git a/src/main/java/org/osm2world/core/math/VectorXZ.java b/src/main/java/org/osm2world/core/math/VectorXZ.java index 403e6ddfb..f0bb67117 100644 --- a/src/main/java/org/osm2world/core/math/VectorXZ.java +++ b/src/main/java/org/osm2world/core/math/VectorXZ.java @@ -34,7 +34,9 @@ public double getZ() { public VectorXZ(double x, double z) { this.x = x; this.z = z; - assert isFinite(x) && isFinite(z); + if (!(isFinite(x) && isFinite(z))) { + throw new InvalidGeometryException("Vector not finite: (" + x + ", " + z + ")"); + } } @Override From d4d54e4e4a21a77a071111842d4379b477f7eba7 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 22 Oct 2024 15:19:04 +0200 Subject: [PATCH 02/85] Include element ID in errors originating from LevelAndHeightData --- .../core/world/modules/building/BuildingPart.java | 2 +- .../world/modules/building/LevelAndHeightData.java | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index eb8f02a50..f1720eeaf 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -109,7 +109,7 @@ public BuildingPart(Building building, MapArea area, Configuration config) { /* determine the level structure */ levelStructure = new LevelAndHeightData(building.getPrimaryMapElement().getTags(), - area.getTags(), levelTagSets, roofShape, this.area.getPolygon()); + area.getTags(), levelTagSets, roofShape, this.area.getPolygon(), this.area); /* build the roof */ diff --git a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java index 30dc84cb5..31b5c1de2 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java +++ b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; import org.osm2world.core.conversion.ConversionLog; +import org.osm2world.core.map_data.data.MapRelation; import org.osm2world.core.map_data.data.TagSet; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level.LevelType; @@ -98,7 +99,7 @@ public String toString() { * If available, explicitly tagged data is used, with tags on indoor=level elements having the highest priority. */ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map levelTagSets, - String roofShape, PolygonShapeXZ outline) { + String roofShape, PolygonShapeXZ outline, MapRelation.Element element) { BuildingDefaults defaults = BuildingDefaults.getDefaultsFor(inheritTags(buildingPartTags, buildingTags)); @@ -179,7 +180,7 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map heightWithoutRoof) { - ConversionLog.warn("min_height exceeds building (part) height without roof"); + ConversionLog.warn("min_height exceeds building (part) height without roof", element); minHeight = heightWithoutRoof - 0.1; } @@ -213,10 +214,10 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map maxLevel) { - ConversionLog.warn("min_level is larger than max_level"); + ConversionLog.warn("min_level is larger than max_level", element); ignoreIndoorLevelNumbers = true; } else if ((maxLevel - minLevel) + 1 - nonExistentLevels.size() != totalLevels) { - ConversionLog.warn("min_level, max_level and non_existent_levels do not match S3DB levels"); + ConversionLog.warn("min_level, max_level and non_existent_levels do not match S3DB levels", element); ignoreIndoorLevelNumbers = true; } } else if (minLevel != null && maxLevel == null) { @@ -270,7 +271,7 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map it < 0)) { - ConversionLog.warn("Sum of explicit level heights exceeds total available height, ignoring them."); + ConversionLog.warn("Sum of explicit level heights exceeds total available height, ignoring them.", element); explicitLevelHeights.clear(); defaultLevelHeights.clear(); } From 8ea2a2cce1b95f88b952dd7cd56e43c150c1a354 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 22 Oct 2024 15:29:54 +0200 Subject: [PATCH 03/85] Handle buildings with min_level exceeding the number of levels --- .../modules/building/LevelAndHeightData.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java index 31b5c1de2..63cb8cc2f 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java +++ b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java @@ -110,23 +110,30 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map 0) + ? buildingMinLevel + : min(buildingMinLevel, -1 * buildingUndergroundLevels); + Double parsedLevels = parseOsmDecimal(tags.getValue("building:levels"), false); int buildingLevels; if (parsedLevels != null) { buildingLevels = max(0, (int)(double)parsedLevels); } else if (parseHeight(tags, -1) > 0) { - buildingLevels = max(1, (int) (parseHeight(tags, -1) / defaults.heightPerLevel)); + buildingLevels = max(buildingMinLevelWithUnderground + 1, + max(1, (int) (parseHeight(tags, -1) / defaults.heightPerLevel))); + } else if (buildingMinLevelWithUnderground > 0) { + buildingLevels = buildingMinLevelWithUnderground + 1; } else { buildingLevels = defaults.levels; } - final int buildingMinLevel = parseInt(tags.getValue("building:min_level"), 0); - final int buildingUndergroundLevels = parseUInt(tags.getValue("building:levels:underground"), 0); - - final int buildingMinLevelWithUnderground = (buildingMinLevel > 0) - ? buildingMinLevel - : min(buildingMinLevel, -1 * buildingUndergroundLevels); + if (buildingLevels < buildingMinLevelWithUnderground + 1) { + throw new IllegalArgumentException("Min level exceeds total building levels for " + element); + } /* determine roof height and roof levels */ From 628771cc719d226b0904ea9654e4580ad90c556c Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 25 Sep 2024 13:36:54 +0200 Subject: [PATCH 04/85] TextureCam: Tweak normal maps at displacement edges --- .../target/common/material/TextureCam.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java index c6d83149e..dbe893acc 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java @@ -255,6 +255,36 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection } + /* tweak normals by adding very steep slopes at displacement jumps (currently only vertical+horizontal edges) */ + + double jumpThreshold = (maxHeight - minHeight) / 4; //10; + + for (int y = 0; y < res.height; y++) { + for (int x = 0; x < res.width; x++) { + + if (x > 0 && displacementHeights[x][y] != null && displacementHeights[x - 1][y] != null) { + if (displacementHeights[x][y] - displacementHeights[x - 1][y] > jumpThreshold) { + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(-1, 0, 0)).toAWT().getRGB()); + normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(-1, 0, 0)).toAWT().getRGB()); + } else if (displacementHeights[x][y] - displacementHeights[x - 1][y] < -jumpThreshold) { + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(+1, 0, 0)).toAWT().getRGB()); + normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(+1, 0, 0)).toAWT().getRGB()); + } + } + + if (y > 0 && displacementHeights[x][y] != null && displacementHeights[x][y - 1] != null) { + if (displacementHeights[x][y] - displacementHeights[x][y - 1] > jumpThreshold) { + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, 0, -1)).toAWT().getRGB()); + normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 0, -1)).toAWT().getRGB()); + } else if (displacementHeights[x][y] - displacementHeights[x][y - 1] < -jumpThreshold) { + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, 0, 1)).toAWT().getRGB()); + normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 0, 1)).toAWT().getRGB()); + } + } + + } + } + /* build and return the result */ return new TextureLayer( From 3becb8c1830afaa4c6001486d0d6b0d51d5aceae Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 24 Oct 2024 12:14:23 +0200 Subject: [PATCH 05/85] TextureCam: Increase occlusion at displacement edges --- .../target/common/material/TextureCam.java | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java index dbe893acc..e99b3d749 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java @@ -1,7 +1,7 @@ package org.osm2world.core.target.common.material; import static java.lang.Double.isFinite; -import static java.lang.Math.PI; +import static java.lang.Math.*; import static java.util.Arrays.asList; import static java.util.Arrays.stream; import static java.util.Collections.nCopies; @@ -255,29 +255,41 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection } - /* tweak normals by adding very steep slopes at displacement jumps (currently only vertical+horizontal edges) */ + /* increase occlusion and tweak normals at displacement jumps (currently only vertical+horizontal edges) */ - double jumpThreshold = (maxHeight - minHeight) / 4; //10; + double jumpThreshold = (maxHeight - minHeight) / 4; for (int y = 0; y < res.height; y++) { for (int x = 0; x < res.width; x++) { if (x > 0 && displacementHeights[x][y] != null && displacementHeights[x - 1][y] != null) { - if (displacementHeights[x][y] - displacementHeights[x - 1][y] > jumpThreshold) { + double jumpHeight = displacementHeights[x][y] - displacementHeights[x - 1][y]; + double baseStrength = 0.5 + abs(jumpHeight) * 0.5; + if (jumpHeight > jumpThreshold) { + for (int i = 1; i <= 4; i++) { + increaseOcclusion(ormImage, x - i, y, baseStrength - 0.1 * i); + } normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(-1, 0, 0)).toAWT().getRGB()); - normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(-1, 0, 0)).toAWT().getRGB()); - } else if (displacementHeights[x][y] - displacementHeights[x - 1][y] < -jumpThreshold) { - normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(+1, 0, 0)).toAWT().getRGB()); + } else if (jumpHeight < -jumpThreshold) { + for (int i = 1; i <= 4; i++) { + increaseOcclusion(ormImage, x - 1 + i, y, baseStrength - 0.1 * i); + } normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(+1, 0, 0)).toAWT().getRGB()); } } if (y > 0 && displacementHeights[x][y] != null && displacementHeights[x][y - 1] != null) { - if (displacementHeights[x][y] - displacementHeights[x][y - 1] > jumpThreshold) { + double jumpHeight = displacementHeights[x][y] - displacementHeights[x][y - 1]; + double baseStrength = 0.5 + abs(jumpHeight) * 0.5; + if (jumpHeight > jumpThreshold) { + for (int i = 1; i <= 4; i++) { + increaseOcclusion(ormImage, x, y - i, baseStrength - 0.1 * i); + } normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, 0, -1)).toAWT().getRGB()); - normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 0, -1)).toAWT().getRGB()); - } else if (displacementHeights[x][y] - displacementHeights[x][y - 1] < -jumpThreshold) { - normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, 0, 1)).toAWT().getRGB()); + } else if (jumpHeight < -jumpThreshold) { + for (int i = 1; i <= 4; i++) { + increaseOcclusion(ormImage, x, y - 1 + i, baseStrength - 0.1 * i); + } normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 0, 1)).toAWT().getRGB()); } } @@ -350,6 +362,14 @@ static LColor colorFromNormal(VectorXYZ normal) { (float)(normal.z + 1.0) / 2); } + /** increases the occlusion (red) value in an ORM map unless it's already high */ + private static void increaseOcclusion(BufferedImage ormImage, int x, int y, double strength) { + if (x < 0 || x >= ormImage.getWidth() || y < 0 || y >= ormImage.getHeight()) return; + Color c = new Color(ormImage.getRGB(x, y)); + c = new Color(min((int)(255 * (1 - strength)), c.getRed()), c.getGreen(), c.getBlue()); + ormImage.setRGB(x, y, c.getRGB()); + } + private static final class RenderedTexture extends RuntimeTexture { private final BufferedImage image; From 17419a27e31ca74fdda7028af62a0a594f4e52fb Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 29 Oct 2024 13:19:38 +0100 Subject: [PATCH 06/85] Fix overzealous check for roof-only buildings --- .../core/world/modules/building/LevelAndHeightData.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java index 63cb8cc2f..5e634fd84 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java +++ b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java @@ -131,10 +131,6 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map Date: Tue, 29 Oct 2024 13:26:38 +0100 Subject: [PATCH 07/85] TextureCam: Avoid extreme strength values for occlusion tweaks --- .../osm2world/core/target/common/material/TextureCam.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java index e99b3d749..883305b9f 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java @@ -264,7 +264,7 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection if (x > 0 && displacementHeights[x][y] != null && displacementHeights[x - 1][y] != null) { double jumpHeight = displacementHeights[x][y] - displacementHeights[x - 1][y]; - double baseStrength = 0.5 + abs(jumpHeight) * 0.5; + double baseStrength = min(0.5 + abs(jumpHeight) * 0.5, 1.0); if (jumpHeight > jumpThreshold) { for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x - i, y, baseStrength - 0.1 * i); @@ -280,7 +280,7 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection if (y > 0 && displacementHeights[x][y] != null && displacementHeights[x][y - 1] != null) { double jumpHeight = displacementHeights[x][y] - displacementHeights[x][y - 1]; - double baseStrength = 0.5 + abs(jumpHeight) * 0.5; + double baseStrength = min(0.5 + abs(jumpHeight) * 0.5, 1.0); if (jumpHeight > jumpThreshold) { for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x, y - i, baseStrength - 0.1 * i); @@ -364,6 +364,7 @@ static LColor colorFromNormal(VectorXYZ normal) { /** increases the occlusion (red) value in an ORM map unless it's already high */ private static void increaseOcclusion(BufferedImage ormImage, int x, int y, double strength) { + if (strength < 0 || strength > 1) throw new IllegalArgumentException("invalid strength: " + strength); if (x < 0 || x >= ormImage.getWidth() || y < 0 || y >= ormImage.getHeight()) return; Color c = new Color(ormImage.getRGB(x, y)); c = new Color(min((int)(255 * (1 - strength)), c.getRed()), c.getGreen(), c.getBlue()); From cc8e6626dabd7d2b7ed489af17d00041da1e886b Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 29 Oct 2024 13:49:40 +0100 Subject: [PATCH 08/85] TextureCam: Fix y dimension in normal maps --- .../core/target/common/material/TextureCam.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java index 883305b9f..514b6d7af 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureCam.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureCam.java @@ -196,9 +196,11 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection } VectorXYZ geometryNormal = interpolateOnTriangle(point, t.triangle, t.normals.get(0), t.normals.get(1), t.normals.get(2)); - geometryNormal = new VectorXYZ(geometryNormal.x, geometryNormal.y, -geometryNormal.z); if (viewDirection == ViewDirection.FROM_TOP) { - geometryNormal = geometryNormal.rotateVec(PI / 2, NULL_VECTOR, X_UNIT); + geometryNormal = geometryNormal.rotateVec(-PI / 2, NULL_VECTOR, X_UNIT); + geometryNormal = new VectorXYZ(geometryNormal.x, -geometryNormal.y, -geometryNormal.z); + } else { + geometryNormal = new VectorXYZ(geometryNormal.x, geometryNormal.y, -geometryNormal.z); } if (geometryNormal.distanceTo(Z_UNIT) > 0.1) { // overwrite texture normal with geometry normal unless it's almost directly facing the camera @@ -269,12 +271,12 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x - i, y, baseStrength - 0.1 * i); } - normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(-1, 0, 0)).toAWT().getRGB()); + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(-1, 0, 0.01).normalize()).toAWT().getRGB()); } else if (jumpHeight < -jumpThreshold) { for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x - 1 + i, y, baseStrength - 0.1 * i); } - normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(+1, 0, 0)).toAWT().getRGB()); + normalImage.setRGB(x - 1, y, colorFromNormal(new VectorXYZ(+1, 0, 0.01).normalize()).toAWT().getRGB()); } } @@ -285,12 +287,12 @@ public static final TextureLayer renderTextures(List meshes, ViewDirection for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x, y - i, baseStrength - 0.1 * i); } - normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, 0, -1)).toAWT().getRGB()); + normalImage.setRGB(x, y, colorFromNormal(new VectorXYZ(0, -1, 0.01).normalize()).toAWT().getRGB()); } else if (jumpHeight < -jumpThreshold) { for (int i = 1; i <= 4; i++) { increaseOcclusion(ormImage, x, y - 1 + i, baseStrength - 0.1 * i); } - normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 0, 1)).toAWT().getRGB()); + normalImage.setRGB(x, y - 1, colorFromNormal(new VectorXYZ(0, 1, 0.01).normalize()).toAWT().getRGB()); } } From ed584e021a36a0cc1140b5b425fe4a57cd6d789a Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 30 Oct 2024 11:49:22 +0100 Subject: [PATCH 09/85] Migrate most modules from LegacyWorldObject to ProceduralWO --- .../core/world/modules/AerowayModule.java | 23 ++- .../core/world/modules/BridgeModule.java | 3 +- .../core/world/modules/CliffModule.java | 7 +- .../world/modules/ExternalModelModule.java | 17 +- .../core/world/modules/GolfModule.java | 11 +- .../core/world/modules/MastModule.java | 10 +- .../core/world/modules/ParkingModule.java | 12 +- .../core/world/modules/PoolModule.java | 11 +- .../core/world/modules/PowerModule.java | 36 ++-- .../core/world/modules/RailwayModule.java | 7 +- .../core/world/modules/RoadModule.java | 6 +- .../world/modules/StreetFurnitureModule.java | 155 ++++++------------ .../core/world/modules/SurfaceAreaModule.java | 7 +- .../core/world/modules/TunnelModule.java | 9 +- .../core/world/modules/WaterModule.java | 19 +-- .../world/modules/common/BridgeOrTunnel.java | 10 +- .../osm2world/core/test/TestWorldModule.java | 7 +- 17 files changed, 144 insertions(+), 206 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/AerowayModule.java b/src/main/java/org/osm2world/core/world/modules/AerowayModule.java index 5ffc6ab75..f3b828296 100644 --- a/src/main/java/org/osm2world/core/world/modules/AerowayModule.java +++ b/src/main/java/org/osm2world/core/world/modules/AerowayModule.java @@ -22,13 +22,12 @@ import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.TextureDataDimensions; import org.osm2world.core.target.common.texcoord.GlobalXZTexCoordFunction; import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleGeometryUtil; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; @@ -96,7 +95,7 @@ private static Material getSurfaceForNode(MapNode node) { } public static class Helipad extends AbstractAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { protected Helipad(MapArea area) { super(area); @@ -108,7 +107,7 @@ public Collection getRawGroundFootprint() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { Function localXZTexCoordFunction = (TextureDataDimensions textureDimensions) -> { return (List vs) -> { @@ -136,14 +135,14 @@ public void renderTo(Target target) { } public static class Apron extends NetworkAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public Apron(MapArea area) { super(area); } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { Material material = getSurfaceMaterial(area.getTags().getValue("surface"), ASPHALT); @@ -157,7 +156,7 @@ public void renderTo(Target target) { /** some linear "road" on an airport, e.g. a runway or taxiway */ public static abstract class AerowaySegment extends AbstractNetworkWaySegmentWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { final float centerlineWidthMeters; @@ -167,7 +166,7 @@ protected AerowaySegment(MapWaySegment segment, float centerlineWidthMeters) { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { List leftOuter = getOutline(false); List rightOuter = getOutline(true); @@ -246,14 +245,14 @@ Material getCenterlineSurface() { } public static class AerowayJunction extends JunctionNodeWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public AerowayJunction(MapNode node) { super(node, AerowaySegment.class); } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { Material material = getSurfaceForNode(node); List triangles = super.getTriangulation(); @@ -266,7 +265,7 @@ public void renderTo(Target target) { } public static class AerowayConnector extends VisibleConnectorNodeWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public AerowayConnector(MapNode node) { super(node, AerowaySegment.class); @@ -281,7 +280,7 @@ public double getLength() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { Material material = getSurfaceForNode(node); diff --git a/src/main/java/org/osm2world/core/world/modules/BridgeModule.java b/src/main/java/org/osm2world/core/world/modules/BridgeModule.java index 73484b8f2..96f81a0a0 100644 --- a/src/main/java/org/osm2world/core/world/modules/BridgeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BridgeModule.java @@ -29,7 +29,6 @@ import org.osm2world.core.math.shapes.ClosedShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.ExtrudeOption; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; @@ -97,7 +96,7 @@ public Iterable getEleConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { drawBridgeUnderside(target); diff --git a/src/main/java/org/osm2world/core/world/modules/CliffModule.java b/src/main/java/org/osm2world/core/world/modules/CliffModule.java index e97e8265f..c0b83c11e 100644 --- a/src/main/java/org/osm2world/core/world/modules/CliffModule.java +++ b/src/main/java/org/osm2world/core/world/modules/CliffModule.java @@ -16,10 +16,9 @@ import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.VectorXYZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; @@ -59,7 +58,7 @@ private static int getConnectedCliffs(MapNode node) { } private abstract static class AbstractCliff extends AbstractNetworkWaySegmentWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { protected AbstractCliff(MapWaySegment segment) { super(segment); @@ -108,7 +107,7 @@ public void defineEleConstraints(EleConstraintEnforcer enforcer) { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { List groundVs = createTriangleStripBetween( getOutline(false), getOutline(true)); diff --git a/src/main/java/org/osm2world/core/world/modules/ExternalModelModule.java b/src/main/java/org/osm2world/core/world/modules/ExternalModelModule.java index e76535d27..8edef976a 100644 --- a/src/main/java/org/osm2world/core/world/modules/ExternalModelModule.java +++ b/src/main/java/org/osm2world/core/world/modules/ExternalModelModule.java @@ -2,21 +2,19 @@ import static java.util.Collections.emptyList; +import java.util.List; + import org.apache.commons.configuration.Configuration; -import org.osm2world.core.map_data.data.MapArea; -import org.osm2world.core.map_data.data.MapData; -import org.osm2world.core.map_data.data.MapElement; -import org.osm2world.core.map_data.data.MapNode; -import org.osm2world.core.map_data.data.MapWaySegment; +import org.osm2world.core.map_data.data.*; import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.GroundState; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.world.creation.WorldModule; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.NodeWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; +import org.osm2world.core.world.data.WorldObject; public class ExternalModelModule implements WorldModule { @@ -44,7 +42,7 @@ public void applyTo(MapData mapData) { public void setConfiguration(Configuration config) {} /** temporary placeholder, to be replaced with an actual 3dmr model by the Target */ - private abstract static class ExternalModelPlaceholder implements LegacyWorldObject { + private abstract static class ExternalModelPlaceholder implements WorldObject { protected final T primaryMapElement; @@ -71,8 +69,9 @@ public T getPrimaryMapElement() { } @Override - public void renderTo(Target target) { + public List buildMeshes() { // no rendering + return List.of(); } } diff --git a/src/main/java/org/osm2world/core/world/modules/GolfModule.java b/src/main/java/org/osm2world/core/world/modules/GolfModule.java index 669def01d..df4c5c14f 100644 --- a/src/main/java/org/osm2world/core/world/modules/GolfModule.java +++ b/src/main/java/org/osm2world/core/world/modules/GolfModule.java @@ -34,11 +34,10 @@ import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.StreetFurnitureModule.Flagpole.StripedFlag; import org.osm2world.core.world.modules.SurfaceAreaModule.SurfaceArea; import org.osm2world.core.world.modules.common.AbstractModule; @@ -95,7 +94,7 @@ private Fairway(MapArea area) { } - private static class Bunker extends AbstractAreaWorldObject implements LegacyWorldObject { + private static class Bunker extends AbstractAreaWorldObject implements ProceduralWorldObject { public Bunker(MapArea area) { super(area); @@ -112,7 +111,7 @@ public Collection getRawGroundFootprint() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { /* draw the bunker as a depression by shrinking the outline polygon and lowering it at each step. * @@ -184,7 +183,7 @@ public void renderTo(Target target) { } - private static class Green extends AbstractAreaWorldObject implements LegacyWorldObject { + private static class Green extends AbstractAreaWorldObject implements ProceduralWorldObject { private final VectorXZ pinPosition; private final SimplePolygonXZ pinHoleLoop; @@ -260,7 +259,7 @@ public EleConnectorGroup getEleConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { /* render green surface */ diff --git a/src/main/java/org/osm2world/core/world/modules/MastModule.java b/src/main/java/org/osm2world/core/world/modules/MastModule.java index 5520c8e8c..349a5ba38 100644 --- a/src/main/java/org/osm2world/core/world/modules/MastModule.java +++ b/src/main/java/org/osm2world/core/world/modules/MastModule.java @@ -18,12 +18,12 @@ import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.LegacyModel; import org.osm2world.core.target.common.model.Model; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** places various towers and masts (currently only freestanding mobile phone communication masts) */ @@ -40,7 +40,7 @@ protected void applyToNode(MapNode node) { } - public static class MobilePhoneMast extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static class MobilePhoneMast extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public MobilePhoneMast(MapNode node) { super(node); @@ -52,7 +52,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { double height = parseHeight(node.getTags(), 7); double radiusBottom = height / 40; @@ -66,7 +66,7 @@ public void renderTo(Target target) { for (double angleRad : asList(PI, 0.3 * PI, 1.5 * PI)) { VectorXYZ pos = getBase().addY(height - 0.6); pos = pos.add(VectorXZ.fromAngle(angleRad).mult(radiusTop)); - MOBILE_PHONE_ANTENNA_MODEL.render(target, new InstanceParameters(pos, angleRad)); + target.addSubModel(new ModelInstance(MOBILE_PHONE_ANTENNA_MODEL, new InstanceParameters(pos, angleRad))); } } diff --git a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java index 7d9aa2bb2..a2d369e62 100644 --- a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java @@ -21,14 +21,14 @@ import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.mesh.LODRange; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.target.common.model.Models; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -57,7 +57,7 @@ protected void applyToArea(MapArea area) { } private class SurfaceParking extends AbstractAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { private final List parkingSpaces = new ArrayList<>(); @@ -91,7 +91,7 @@ public Collection getRawGroundFootprint() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { String surface = area.getTags().getValue("surface"); Material material = getSurfaceMaterial(surface, ASPHALT); @@ -132,8 +132,8 @@ public void renderTo(Target target) { } direction = direction.normalize(); - target.drawModel(carModel, new InstanceParameters( - bbox.getCenter().xyz(ele), direction.angle(), carColor, new LODRange(LOD3, LOD4))); + target.addSubModel(new ModelInstance(carModel, new InstanceParameters( + bbox.getCenter().xyz(ele), direction.angle(), carColor, new LODRange(LOD3, LOD4)))); } } diff --git a/src/main/java/org/osm2world/core/world/modules/PoolModule.java b/src/main/java/org/osm2world/core/world/modules/PoolModule.java index 32b704995..5a9578c1f 100644 --- a/src/main/java/org/osm2world/core/world/modules/PoolModule.java +++ b/src/main/java/org/osm2world/core/world/modules/PoolModule.java @@ -29,12 +29,11 @@ import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.ImmutableMaterial; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; @@ -69,7 +68,7 @@ protected void applyToWaySegment(MapWaySegment segment) { } public static class Pool extends AbstractAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public Pool(MapArea area) { super(area); @@ -81,7 +80,7 @@ public Collection getRawGroundFootprint() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { /* render water */ @@ -110,7 +109,7 @@ public void renderTo(Target target) { } } - private static class WaterSlide implements WaySegmentWorldObject, LegacyWorldObject { + private static class WaterSlide implements WaySegmentWorldObject, ProceduralWorldObject { private static final Color DEFAULT_COLOR = ORANGE; @@ -183,7 +182,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { //TODO parse material (e.g. for steel slides) and apply color to it diff --git a/src/main/java/org/osm2world/core/world/modules/PowerModule.java b/src/main/java/org/osm2world/core/world/modules/PowerModule.java index 1439d90d2..7eaed3d24 100644 --- a/src/main/java/org/osm2world/core/world/modules/PowerModule.java +++ b/src/main/java/org/osm2world/core/world/modules/PowerModule.java @@ -38,7 +38,6 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.*; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.material.TextureDataDimensions; @@ -49,14 +48,15 @@ import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.LegacyModel; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.util.ValueParseUtil; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; import org.osm2world.core.world.data.NoOutlineWaySegmentWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -148,7 +148,7 @@ protected void applyToArea(MapArea area) { } } - private static final class PowerCabinet extends NoOutlineNodeWorldObject implements LegacyWorldObject { + private static final class PowerCabinet extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public PowerCabinet(MapNode node) { super(node); @@ -160,7 +160,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); @@ -191,7 +191,7 @@ public boolean isHighVoltagePowerTower() { } } - private static final class Powerpole extends NoOutlineNodeWorldObject implements LegacyWorldObject { + private static final class Powerpole extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public Powerpole(MapNode node) { super(node); @@ -203,7 +203,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { /* determine material */ @@ -228,7 +228,7 @@ public void renderTo(Target target) { } - public static final class WindTurbine extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class WindTurbine extends NoOutlineNodeWorldObject implements ProceduralWorldObject { /** model of a rotor with 1 m rotor diameter */ public static final Model ROTOR = new LegacyModel() { @@ -297,7 +297,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { double poleHeight = parseHeight(node.getTags(), parseMeasure(node.getTags().getValue("height:hub"), 100.0)); @@ -344,9 +344,9 @@ public void renderTo(Target target) { nacelleVector, nacelleHeight, nacelleHeight, nacelleDepth); /* draw rotor blades */ - target.drawModel(ROTOR, new InstanceParameters( + target.addSubModel(new ModelInstance(ROTOR, new InstanceParameters( position.addY(poleHeight).add(-poleRadiusTop*2.5, nacelleHeight/2, 0), - 0, rotorDiameter, rotorDiameter, rotorDiameter)); + 0, rotorDiameter, rotorDiameter, rotorDiameter))); } @@ -581,7 +581,7 @@ public List buildMeshes() { } - private static final class PowerTower extends NoOutlineNodeWorldObject implements LegacyWorldObject { + private static final class PowerTower extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private TowerConfig config; @@ -598,7 +598,7 @@ public GroundState getGroundState() { // TODO we're missing the ceramics to hold the power lines @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { VectorXYZ base = getBase().addY(-0.5); double height = parseHeight(node.getTags(), 14); @@ -629,7 +629,7 @@ public void renderTo(Target target) { } - private static final class HighVoltagePowerTower extends NoOutlineNodeWorldObject implements LegacyWorldObject { + private static final class HighVoltagePowerTower extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private TowerConfig config; private VectorXZ direction; @@ -795,7 +795,7 @@ private void drawHorizontalPole(Target target, double elevation, // TODO we're missing the ceramics to hold the power lines @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { float pole_width = config.voltage > 150000 ? 16 : 13; float[] tower_width = config.voltage > 150000 ? new float[]{11,6,4f,0} : new float[]{8,5,3,0}; @@ -820,7 +820,7 @@ public void renderTo(Target target) { } } - private static final class PhotovoltaicPlant extends AbstractAreaWorldObject implements LegacyWorldObject { + private static final class PhotovoltaicPlant extends AbstractAreaWorldObject implements ProceduralWorldObject { /** compares vectors by x coordinate */ private static final Comparator X_COMPARATOR = comparingDouble(v -> v.x); @@ -917,7 +917,7 @@ protected PhotovoltaicPlant(MapArea area) { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { for (PolylineXZ panelRow : panelRows) { @@ -985,7 +985,7 @@ private void renderPanelsTo(Target target, VectorXYZ bottomLeft, } - static final class RooftopSolarPanels extends AbstractAreaWorldObject implements LegacyWorldObject { + static final class RooftopSolarPanels extends AbstractAreaWorldObject implements ProceduralWorldObject { private static final double DISTANCE_FROM_ROOF = 0.05; @@ -1014,7 +1014,7 @@ public Iterable getAttachmentConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { if (connector.isAttached()) { diff --git a/src/main/java/org/osm2world/core/world/modules/RailwayModule.java b/src/main/java/org/osm2world/core/world/modules/RailwayModule.java index 93b7a33bb..4d5932a15 100644 --- a/src/main/java/org/osm2world/core/world/modules/RailwayModule.java +++ b/src/main/java/org/osm2world/core/world/modules/RailwayModule.java @@ -33,7 +33,6 @@ import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.ExtrudeOption; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Material.Interpolation; @@ -45,7 +44,7 @@ import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleGeometryUtil; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; @@ -298,14 +297,14 @@ private long countConnectedRailSegments(MapNode node) { } public static class RailJunction extends JunctionNodeWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public RailJunction(MapNode node) { super(node, Rail.class); } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { if (getOutlinePolygon() == null) return; diff --git a/src/main/java/org/osm2world/core/world/modules/RoadModule.java b/src/main/java/org/osm2world/core/world/modules/RoadModule.java index a0fc2c2e5..c5bad8948 100644 --- a/src/main/java/org/osm2world/core/world/modules/RoadModule.java +++ b/src/main/java/org/osm2world/core/world/modules/RoadModule.java @@ -39,7 +39,6 @@ import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.material.TextureDataDimensions; @@ -47,7 +46,6 @@ import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.target.common.texcoord.TexCoordUtil; import org.osm2world.core.util.enums.LeftRight; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; @@ -1569,14 +1567,14 @@ public void buildMeshesAndModels(Target target) { } public static class RoadArea extends NetworkAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public RoadArea(MapArea area) { super(area); } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { String surface = area.getTags().getValue("surface"); Material material = getSurfaceMaterial(surface, ASPHALT); diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index fcd073a1d..6db051174 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -59,10 +59,7 @@ import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.attachment.AttachmentSurface.Builder; -import org.osm2world.core.world.data.LegacyWorldObject; -import org.osm2world.core.world.data.NoOutlineNodeWorldObject; -import org.osm2world.core.world.data.NodeModelInstance; -import org.osm2world.core.world.data.NodeWorldObject; +import org.osm2world.core.world.data.*; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -201,7 +198,7 @@ public Collection getAttachmentSurfaces() { } - public static final class Flagpole extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Flagpole extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public Flagpole(MapNode node) { super(node); @@ -213,12 +210,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD2, LOD4); /* draw the pole */ @@ -548,7 +542,7 @@ public TexturedFlag(Material material) { } - public static final class AdvertisingColumn extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class AdvertisingColumn extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public AdvertisingColumn(MapNode node) { super(node); @@ -560,12 +554,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double height = parseHeight(node.getTags(), 3.0); @@ -590,7 +581,7 @@ public void renderTo(Target target) { } - public static final class Billboard extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Billboard extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private final double width; /** the height of the billboard itself, i.e. height minus minHeight */ @@ -626,11 +617,6 @@ public GroundState getGroundState() { return GroundState.ON; } - @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } - @Override public Iterable getAttachmentConnectors() { if (connector == null) { @@ -641,7 +627,9 @@ public Iterable getAttachmentConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { + + target.setCurrentLodRange(LOD2, LOD4); VectorXZ faceVector; VectorXYZ bottomCenter; @@ -725,7 +713,7 @@ public void renderTo(Target target) { } - public static final class Swing extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Swing extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public Swing(MapNode node) { super(node); @@ -737,12 +725,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); // determine width and height of the swing structure final double swingHeight = parseHeight(node.getTags(), 1.5); @@ -843,7 +828,7 @@ to keep ropes (and seats) within C_B_W's limits */ } } - public static final class Bench extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Bench extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private final AttachmentConnector connector; @@ -865,12 +850,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); /* determine elevation from connector */ @@ -948,7 +930,7 @@ public Iterable getAttachmentConnectors() { } - public static final class Table extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Table extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private final ConfMaterial defaultMaterial; @@ -968,12 +950,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); int seats = parseInt(node.getTags(), 4, "seats"); @@ -1085,7 +1064,7 @@ private void renderSeatSide(Target target, Material material, VectorXZ rowPos, d /** * a summit cross or wayside cross */ - public static final class Cross extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Cross extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public Cross(MapNode node) { super(node); @@ -1097,12 +1076,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD2, LOD4); boolean summit = node.getTags().containsKey("summit:cross") || node.getTags().contains("natural", "peak"); @@ -1146,7 +1122,7 @@ public void renderTo(Target target) { /** * a clock. Currently only clocks attached to walls are supported. */ - public static final class Clock implements NodeWorldObject, LegacyWorldObject { + public static final class Clock implements NodeWorldObject, ProceduralWorldObject { private static final LocalTime TIME = LocalTime.parse("12:25"); @@ -1172,11 +1148,6 @@ public GroundState getGroundState() { return GroundState.ON; } - @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } - @Override public Iterable getEleConnectors() { return emptyList(); @@ -1191,10 +1162,12 @@ public Iterable getAttachmentConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { if (!connector.isAttached()) return; + target.setCurrentLodRange(LOD2, LOD4); + double diameter = parseWidth(node.getTags(), 1f); new ClockFace(TIME).render(target, new InstanceParameters(connector.getAttachedPos(), connector.getAttachedSurfaceNormal().xz().angle(), @@ -1266,7 +1239,7 @@ private static double angleHourHand(LocalTime time) { } - public static final class RecyclingContainer extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class RecyclingContainer extends NoOutlineNodeWorldObject implements ProceduralWorldObject { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); @@ -1281,12 +1254,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD2, LOD4); float distanceX = 3f; float distanceZ = 1.6f; @@ -1366,7 +1336,7 @@ private void drawContainer(Target target, String trash, VectorXYZ pos) { } - public static final class WasteBasket extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class WasteBasket extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private final AttachmentConnector connector; @@ -1393,11 +1363,6 @@ public GroundState getGroundState() { } } - @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } - @Override public Iterable getAttachmentConnectors() { if (connector == null) { @@ -1408,7 +1373,9 @@ public Iterable getAttachmentConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { + + target.setCurrentLodRange(LOD3, LOD4); /* determine material */ @@ -1475,7 +1442,7 @@ private void renderBasket(Target target, Material material, VectorXYZ pos, Vecto } - public static final class GritBin extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class GritBin extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public GritBin(MapNode node) { super(node); @@ -1487,12 +1454,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double height = parseHeight(node.getTags(), 0.5f); double width = parseWidth(node.getTags(), 1); @@ -1534,7 +1498,7 @@ public void renderTo(Target target) { } - public static final class Phone extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Phone extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private static enum Type {WALL, PILLAR, CELL, HALFCELL} @@ -1548,12 +1512,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); @@ -1619,7 +1580,7 @@ public void renderTo(Target target) { } - public static final class VendingMachineVice extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class VendingMachineVice extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private static enum Type {WALL, PILLAR} @@ -1633,12 +1594,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); @@ -1691,7 +1649,7 @@ public void renderTo(Target target) { } - public static final class PostBox extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class PostBox extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private static enum Type {WALL, PILLAR} @@ -1705,12 +1663,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); @@ -1900,7 +1855,7 @@ public List buildMeshes(InstanceParameters params) { } - public static final class FireHydrant extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class FireHydrant extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public FireHydrant(MapNode node) { super(node); @@ -1912,12 +1867,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double height = parseHeight(node.getTags(), 1.0); @@ -2021,7 +1973,7 @@ public Collection getAttachmentSurfaces() { } - public static final class Board extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class Board extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public Board(MapNode node) { super(node); @@ -2033,12 +1985,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); diff --git a/src/main/java/org/osm2world/core/world/modules/SurfaceAreaModule.java b/src/main/java/org/osm2world/core/world/modules/SurfaceAreaModule.java index e2a487883..c73f62318 100644 --- a/src/main/java/org/osm2world/core/world/modules/SurfaceAreaModule.java +++ b/src/main/java/org/osm2world/core/world/modules/SurfaceAreaModule.java @@ -23,12 +23,11 @@ import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -83,7 +82,7 @@ protected void applyToArea(MapArea area) { } public static class SurfaceArea extends AbstractAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { private final String surface; @@ -95,7 +94,7 @@ public SurfaceArea(MapArea area, String surface) { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { Material material; diff --git a/src/main/java/org/osm2world/core/world/modules/TunnelModule.java b/src/main/java/org/osm2world/core/world/modules/TunnelModule.java index 99f7898e1..efefe0d27 100644 --- a/src/main/java/org/osm2world/core/world/modules/TunnelModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TunnelModule.java @@ -24,10 +24,9 @@ import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.mesh.Mesh; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.NodeWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; import org.osm2world.core.world.modules.common.BridgeOrTunnel; @@ -138,7 +137,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { List leftOutline = primaryRep.getOutline(false); List rightOutline = primaryRep.getOutline(true); @@ -311,7 +310,7 @@ public List buildMeshes() { } - public static class TunnelJunction implements NodeWorldObject, LegacyWorldObject { + public static class TunnelJunction implements NodeWorldObject, ProceduralWorldObject { private final MapNode node; private final JunctionNodeWorldObject primaryRep; @@ -341,7 +340,7 @@ public Iterable getEleConnectors() { public void defineEleConstraints(EleConstraintEnforcer enforcer) {} @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { //TODO port to new elevation model diff --git a/src/main/java/org/osm2world/core/world/modules/WaterModule.java b/src/main/java/org/osm2world/core/world/modules/WaterModule.java index 115cb1eb2..856414341 100644 --- a/src/main/java/org/osm2world/core/world/modules/WaterModule.java +++ b/src/main/java/org/osm2world/core/world/modules/WaterModule.java @@ -26,9 +26,8 @@ import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleParseUtil; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; @@ -103,7 +102,7 @@ public void applyTo(MapData mapData) { } public static class Waterway extends AbstractNetworkWaySegmentWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public Waterway(MapWaySegment line) { super(line); @@ -146,7 +145,7 @@ public SimplePolygonXZ getOutlinePolygonXZ() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { //note: simply "extending" a river cannot work - unlike streets - // because there can be islands within the riverbank polygon. @@ -235,14 +234,14 @@ private static void modifyLineHeight(List leftWaterBorder, float yMod } public static class RiverJunction extends JunctionNodeWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public RiverJunction(MapNode node) { super(node, Waterway.class); } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { //TODO: check whether it's within a riverbank (as with Waterway) @@ -258,7 +257,7 @@ public void renderTo(Target target) { } public static class Water extends NetworkAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { //TODO: only cover with water to 0.95 * distance to center; add land below. // possible algorithm: for each node of the outer polygon, check whether it @@ -280,7 +279,7 @@ public void defineEleConstraints(EleConstraintEnforcer enforcer) { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { List triangles = getTriangulation(); target.drawTriangles(WATER, triangles, triangleTexCoordLists(triangles, WATER, GLOBAL_X_Z)); @@ -289,7 +288,7 @@ public void renderTo(Target target) { } public static class AreaFountain extends AbstractAreaWorldObject - implements LegacyWorldObject { + implements ProceduralWorldObject { public AreaFountain(MapArea area) { super(area); @@ -306,7 +305,7 @@ public Collection getRawGroundFootprint() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { /* render water */ diff --git a/src/main/java/org/osm2world/core/world/modules/common/BridgeOrTunnel.java b/src/main/java/org/osm2world/core/world/modules/common/BridgeOrTunnel.java index 3c59fd032..5f80ef612 100644 --- a/src/main/java/org/osm2world/core/world/modules/common/BridgeOrTunnel.java +++ b/src/main/java/org/osm2world/core/world/modules/common/BridgeOrTunnel.java @@ -2,8 +2,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.*; -import static org.osm2world.core.map_elevation.data.GroundState.*; +import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.MAX; +import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.MIN; +import static org.osm2world.core.map_elevation.data.GroundState.ABOVE; +import static org.osm2world.core.map_elevation.data.GroundState.ON; import static org.osm2world.core.math.GeometryUtil.isBetween; import java.util.List; @@ -20,7 +22,7 @@ import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.TreeModule.Forest; @@ -29,7 +31,7 @@ /** * common superclass for bridges and tunnels */ -public abstract class BridgeOrTunnel implements WaySegmentWorldObject, LegacyWorldObject { +public abstract class BridgeOrTunnel implements WaySegmentWorldObject, ProceduralWorldObject { protected final MapWaySegment segment; protected final AbstractNetworkWaySegmentWorldObject primaryRep; diff --git a/src/test/java/org/osm2world/core/test/TestWorldModule.java b/src/test/java/org/osm2world/core/test/TestWorldModule.java index 248b33403..468b2d5fe 100644 --- a/src/test/java/org/osm2world/core/test/TestWorldModule.java +++ b/src/test/java/org/osm2world/core/test/TestWorldModule.java @@ -8,10 +8,9 @@ import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.frontend_pbf.FrontendPbf.WorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -24,7 +23,7 @@ protected void applyToNode(MapNode node) { node.addRepresentation(new TestNodeWorldObject(node)); } - public static class TestNodeWorldObject extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static class TestNodeWorldObject extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public TestNodeWorldObject(MapNode node) { super(node); @@ -36,7 +35,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { VectorXYZ base = node.getPos().xyz(0); From adaa5b63cad9c865e6b84e3ac328a9f426b8fef0 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 30 Oct 2024 13:23:27 +0100 Subject: [PATCH 10/85] Add easy AttachmentSurface creation to ProceduralWorldObject --- .../world/data/ProceduralWorldObject.java | 20 ++++++++ .../world/modules/StreetFurnitureModule.java | 51 +++++++------------ 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java index 2e1163db9..c1d4091ec 100644 --- a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java @@ -1,6 +1,7 @@ package org.osm2world.core.world.data; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import javax.annotation.Nullable; @@ -10,6 +11,7 @@ import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.world.attachment.AttachmentSurface; /** * a model which is generated by code during runtime, @@ -23,20 +25,32 @@ class Target implements CommonTarget { private final List meshes = new ArrayList<>(); private final List subModels = new ArrayList<>(); + private final List attachmentSurfaces = new ArrayList<>(); private @Nullable LODRange currentLodRange = null; + private List currentAttachmentTypes = List.of(); public void setCurrentLodRange(LevelOfDetail minLod, LevelOfDetail maxLod) { this.currentLodRange = new LODRange(minLod, maxLod); } + public void setCurrentAttachmentTypes(String... attachmentTypes) { + this.currentAttachmentTypes = List.of(attachmentTypes); + } + @Override public void drawMesh(Mesh mesh) { + if (currentLodRange == null) { meshes.add(mesh); } else { meshes.add(new Mesh(mesh.geometry, mesh.material, currentLodRange.min(), currentLodRange.max())); } + + if (!currentAttachmentTypes.isEmpty()) { + attachmentSurfaces.add(AttachmentSurface.fromMeshes(currentAttachmentTypes, List.of(mesh))); + } + } public void addSubModel(ModelInstance subModel) { @@ -59,4 +73,10 @@ default List getSubModels() { return target.subModels; } + @Override + default Collection getAttachmentSurfaces() { + var target = new Target(); + buildMeshesAndModels(target); + return target.attachmentSurfaces; + } } diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index 6db051174..e9d68e038 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.*; -import org.apache.commons.lang3.tuple.Pair; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapWaySegment; import org.osm2world.core.map_data.data.TagSet; @@ -42,7 +41,6 @@ import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.ExtrudeOption; import org.osm2world.core.target.common.material.ConfMaterial; import org.osm2world.core.target.common.material.ImmutableMaterial; @@ -50,7 +48,6 @@ import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.ExtrusionGeometry; -import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.LegacyModel; @@ -58,8 +55,10 @@ import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; -import org.osm2world.core.world.attachment.AttachmentSurface.Builder; -import org.osm2world.core.world.data.*; +import org.osm2world.core.world.data.NoOutlineNodeWorldObject; +import org.osm2world.core.world.data.NodeModelInstance; +import org.osm2world.core.world.data.NodeWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** @@ -1726,7 +1725,7 @@ public void buildMeshesAndModels(Target target) { } - public static final class BusStop extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class BusStop extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public BusStop(MapNode node) { super(node); @@ -1738,17 +1737,18 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD3, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD3, LOD4); double height = parseHeight(node.getTags(), 3.0); double signHeight = 0.7; double signWidth = 0.4; + /* draw pole */ + + target.setCurrentAttachmentTypes("bus_stop", "pole"); + Material poleMaterial = STEEL; double directionAngle = parseDirection(node.getTags(), PI); @@ -1759,7 +1759,7 @@ public void renderTo(Target target) { getBase(), height - signHeight, 0.05, 0.05, false, true); - if (target instanceof AttachmentSurface.Builder) return; + target.setCurrentAttachmentTypes(); /* draw sign */ target.drawBox(BUS_STOP_SIGN, @@ -1772,13 +1772,6 @@ public void renderTo(Target target) { } - @Override - public Collection getAttachmentSurfaces() { - Builder builder = new AttachmentSurface.Builder("bus_stop", "pole"); - this.renderTo(builder); - return singleton(builder.build()); - } - } public static final class ParcelLocker implements Model { @@ -1894,7 +1887,7 @@ public void buildMeshesAndModels(Target target) { } - public static final class StreetLamp extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public static final class StreetLamp extends NoOutlineNodeWorldObject implements ProceduralWorldObject { public StreetLamp(MapNode node) { super(node); @@ -1906,12 +1899,9 @@ public GroundState getGroundState() { } @Override - public Pair getLodRange() { - return Pair.of(LOD2, LOD4); - } + public void buildMeshesAndModels(Target target) { - @Override - public void renderTo(Target target) { + target.setCurrentLodRange(LOD2, LOD4); double lampHeight = 0.8; double lampHalfWidth = 0.4; @@ -1933,11 +1923,13 @@ public void renderTo(Target target) { /* draw pole */ + target.setCurrentAttachmentTypes("street_lamp"); + target.drawExtrudedShape(material, new CircleXZ(NULL_VECTOR, 1), asList(getBase(), getBase().addY(0.5), getBase().addY(0.5 + poleHeight)), null, asList(0.16, 0.08, 0.08), null, null); - if (target instanceof AttachmentSurface.Builder) return; + target.setCurrentAttachmentTypes(); /* draw lamp */ @@ -1964,13 +1956,6 @@ public void renderTo(Target target) { target.drawTriangleFan(material, vs, texCoordLists(vs, material, GLOBAL_X_Z)); } - @Override - public Collection getAttachmentSurfaces() { - Builder builder = new AttachmentSurface.Builder("street_lamp"); - this.renderTo(builder); - return singleton(builder.build()); - } - } public static final class Board extends NoOutlineNodeWorldObject implements ProceduralWorldObject { From 6c1d0c1d927d7d657eaddb62d79ded84f76552a5 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 31 Oct 2024 12:55:36 +0100 Subject: [PATCH 11/85] Support attachment for vending machines and rename class --- .../frontend_pbf/FrontendPbfTarget.java | 2 +- .../world/modules/StreetFurnitureModule.java | 87 ++++++++++++------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java index f82881cfe..67ca85b06 100644 --- a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java +++ b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java @@ -91,7 +91,7 @@ public class FrontendPbfTarget extends MeshTarget { private enum TextureAtlasMode { ALWAYS, RUNTIME_ONLY, NEVER }; private final List> LOD_2_FEATURES = asList(HandRail.class, WasteBasket.class, - VendingMachineVice.class, PostBox.class, Bench.class, GritBin.class, BollardRow.class); + VendingMachine.class, PostBox.class, Bench.class, GritBin.class, BollardRow.class); /** * materials which default to being shadowless diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index e9d68e038..c254d2681 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -128,7 +128,7 @@ protected void applyToNode(MapNode node) { } if (node.getTags().contains("amenity", "vending_machine") && (node.getTags().containsAny(asList("vending"), asList("bicycle_tube", "cigarettes", "condoms")))) { - node.addRepresentation(new VendingMachineVice(node)); + node.addRepresentation(new VendingMachine(node)); } if (node.getTags().contains("amenity", "recycling") && (node.getTags().contains("recycling_type", "container"))) { @@ -1579,17 +1579,49 @@ public void buildMeshesAndModels(Target target) { } - public static final class VendingMachineVice extends NoOutlineNodeWorldObject implements ProceduralWorldObject { + public static final class VendingMachine extends NoOutlineNodeWorldObject implements ProceduralWorldObject { - private static enum Type {WALL, PILLAR} + private final AttachmentConnector connector; + + public VendingMachine(MapNode node) { - public VendingMachineVice(MapNode node) { super(node); + + String support = node.getTags().getValue("support"); + + List attachmentTypes = List.of(); + + if (support != null && !"ground".equals(support)) { + attachmentTypes = List.of(support); + } else if (isInWall(node) && !"ground".equals(support)) { + attachmentTypes = List.of("wall"); + } + + if (attachmentTypes.isEmpty()) { + connector = null; + } else { + connector = new AttachmentConnector(attachmentTypes, + node.getPos().xyz(0), this, 0.83, true); + } + } @Override public GroundState getGroundState() { - return GroundState.ON; + if (connector != null && connector.isAttached()) { + return GroundState.ATTACHED; + } else { + return GroundState.ON; + } + } + + @Override + public Iterable getAttachmentConnectors() { + if (connector == null) { + return emptyList(); + } else { + return singleton(connector); + } } @Override @@ -1597,12 +1629,7 @@ public void buildMeshesAndModels(Target target) { target.setCurrentLodRange(LOD3, LOD4); - double directionAngle = parseDirection(node.getTags(), PI); - VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); - - Material machineMaterial = null; - Material poleMaterial = STEEL; - Type type = null; + Material machineMaterial; if (node.getTags().contains("vending", "bicycle_tube") && node.getTags().containsAny(asList("operator"), asList("Continental", "continental"))) { @@ -1613,37 +1640,31 @@ public void buildMeshesAndModels(Target target) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, new Color(0.8f, 0.73f, 0.5f)); } else if (node.getTags().contains("vending", "condoms")) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, new Color(0.39f, 0.15f, 0.11f)); - } - - // get Type of vending machine - if (isInWall(node)) { - type = Type.WALL; } else { - type = Type.PILLAR; + machineMaterial = new ImmutableMaterial(Interpolation.FLAT, LIGHT_GRAY); } - // default dimensions will differ depending on the post box type - double height = 0f; + double height = parseHeight(node.getTags(), 1.8f); - switch (type) { - case WALL: + VectorXYZ pos; + VectorXYZ direction = VectorXZ.fromAngle(parseDirection(node.getTags(), PI)).xyz(0); - break; - case PILLAR: - height = parseHeight(node.getTags(), 1.8f); + if (connector != null && connector.isAttached()) { - target.drawBox(poleMaterial, - getBase().add(new VectorXYZ(0, 0, -0.05).rotateY(faceVector.angle())), - faceVector, height - 0.3, 0.1, 0.1); - target.drawBox(machineMaterial, - getBase().addY(height - 1).add(new VectorXYZ(0, 0, 0.1).rotateY(directionAngle)), - faceVector, 1, 1, 0.2); + pos = connector.getAttachedPos(); + direction = connector.getAttachedSurfaceNormal(); + + } else { + + pos = getBase().addY(0.8).add(direction.mult(0.05)); + + /* draw pole */ + target.drawBox(STEEL, getBase(), direction.xz(), height - 0.3, 0.1, 0.1); - break; - default: - assert false : "unknown or unsupported Vending machine Type"; } + target.drawBox(machineMaterial, pos.add(direction.mult(0.1)), direction.xz(), height - 0.8, 1, 0.2); + } } From ed6acb266b8497aad5e96720a372f89ea65f7041 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Fri, 1 Nov 2024 12:57:11 +0100 Subject: [PATCH 12/85] Route Model rendering through ModelInstance to enable future changes --- .../osm2world/core/target/CommonTarget.java | 7 +++-- .../org/osm2world/core/target/Target.java | 9 ------- .../org/osm2world/core/target/TargetUtil.java | 2 +- .../core/target/common/model/Model.java | 18 ++++++------- .../target/common/model/ModelInstance.java | 27 ++++++++++++++----- ...{LegacyModel.java => ProceduralModel.java} | 16 ++++++----- .../frontend_pbf/FrontendPbfTarget.java | 7 ++--- .../core/world/data/NodeModelInstance.java | 3 ++- .../core/world/data/WorldObject.java | 2 +- .../core/world/modules/MastModule.java | 4 +-- .../core/world/modules/PowerModule.java | 4 +-- .../world/modules/StreetFurnitureModule.java | 9 ++++--- .../core/world/modules/TreeModule.java | 12 +++++---- .../modules/building/roof/ChimneyRoof.java | 4 ++- .../core/target/gltf/GltfModelTest.java | 4 +-- 15 files changed, 70 insertions(+), 58 deletions(-) rename src/main/java/org/osm2world/core/target/common/model/{LegacyModel.java => ProceduralModel.java} (54%) diff --git a/src/main/java/org/osm2world/core/target/CommonTarget.java b/src/main/java/org/osm2world/core/target/CommonTarget.java index 1b0323b26..8e61dffde 100644 --- a/src/main/java/org/osm2world/core/target/CommonTarget.java +++ b/src/main/java/org/osm2world/core/target/CommonTarget.java @@ -22,8 +22,7 @@ import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.mesh.MeshUtil; import org.osm2world.core.target.common.mesh.TriangleGeometry; -import org.osm2world.core.target.common.model.InstanceParameters; -import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.world.data.ProceduralWorldObject; /** @@ -212,8 +211,8 @@ default void drawColumn(@Nonnull Material material, @Nullable Integer corners, /** * draws an instanced model. */ - default void drawModel(Model model, InstanceParameters params) { - model.render(this, params); + default void drawModel(ModelInstance modelInstance) { + modelInstance.render(this); } void drawMesh(Mesh mesh); diff --git a/src/main/java/org/osm2world/core/target/Target.java b/src/main/java/org/osm2world/core/target/Target.java index bb617cfc3..9f17a17f6 100644 --- a/src/main/java/org/osm2world/core/target/Target.java +++ b/src/main/java/org/osm2world/core/target/Target.java @@ -6,8 +6,6 @@ import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.mesh.TriangleGeometry; -import org.osm2world.core.target.common.model.InstanceParameters; -import org.osm2world.core.target.common.model.Model; import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.world.data.WorldObject; @@ -32,13 +30,6 @@ default LevelOfDetail getLod() { */ default void beginObject(@Nullable WorldObject object) {} - /** - * draws an instanced model. - */ - public default void drawModel(Model model, InstanceParameters params) { - model.render(this, params); - } - public default void drawMesh(Mesh mesh) { if (mesh.lodRangeContains(getLod())) { TriangleGeometry tg = mesh.geometry.asTriangles(); diff --git a/src/main/java/org/osm2world/core/target/TargetUtil.java b/src/main/java/org/osm2world/core/target/TargetUtil.java index 2a4abffa3..fefe3da29 100644 --- a/src/main/java/org/osm2world/core/target/TargetUtil.java +++ b/src/main/java/org/osm2world/core/target/TargetUtil.java @@ -89,7 +89,7 @@ public static final void renderObject(Target target, WorldObject object) { ((LegacyWorldObject)object).renderTo(target); } else { object.buildMeshes().forEach(target::drawMesh); - object.getSubModels().forEach(it -> target.drawModel(it.model, it.params)); + object.getSubModels().forEach(it -> it.render(target)); } } diff --git a/src/main/java/org/osm2world/core/target/common/model/Model.java b/src/main/java/org/osm2world/core/target/common/model/Model.java index 7c1017d99..3d0dd3339 100644 --- a/src/main/java/org/osm2world/core/target/common/model/Model.java +++ b/src/main/java/org/osm2world/core/target/common/model/Model.java @@ -1,9 +1,9 @@ package org.osm2world.core.target.common.model; +import static org.osm2world.core.math.VectorXYZ.NULL_VECTOR; + import java.util.List; -import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.mesh.Mesh; /** @@ -12,17 +12,15 @@ public interface Model { /** - * returns the meshes making up an instance of this {@link Model}. + * returns the meshes making up this {@link Model} */ - public List buildMeshes(InstanceParameters params); + default List getMeshes() { + return buildMeshes(new InstanceParameters(NULL_VECTOR, 0)); + } /** - * draws an instance of the model to any {@link Target} - * - * @param target target for the model; != null + * returns the meshes making up an instance of this {@link Model}. */ - public default void render(CommonTarget target, InstanceParameters params) { - buildMeshes(params).forEach(target::drawMesh); - } + List buildMeshes(InstanceParameters params); } \ No newline at end of file diff --git a/src/main/java/org/osm2world/core/target/common/model/ModelInstance.java b/src/main/java/org/osm2world/core/target/common/model/ModelInstance.java index f136f196a..f289d1c12 100644 --- a/src/main/java/org/osm2world/core/target/common/model/ModelInstance.java +++ b/src/main/java/org/osm2world/core/target/common/model/ModelInstance.java @@ -1,14 +1,27 @@ package org.osm2world.core.target.common.model; -/** one instance of a {@link Model} */ -public class ModelInstance { +import java.util.List; - public final Model model; - public final InstanceParameters params; +import org.osm2world.core.target.CommonTarget; +import org.osm2world.core.target.common.mesh.Mesh; - public ModelInstance(Model model, InstanceParameters params) { - this.model = model; - this.params = params; +/** + * one instance of a {@link Model} + */ +public record ModelInstance(Model model, InstanceParameters params) { + + /** + * returns the model's meshes, with transformations based on {@link #params} already applied to them. + */ + public List getMeshes() { + return model.buildMeshes(params); + } + + /** + * draws the result of {@link #getMeshes()} to any target + */ + public void render(CommonTarget target) { + getMeshes().forEach(target::drawMesh); } } diff --git a/src/main/java/org/osm2world/core/target/common/model/LegacyModel.java b/src/main/java/org/osm2world/core/target/common/model/ProceduralModel.java similarity index 54% rename from src/main/java/org/osm2world/core/target/common/model/LegacyModel.java rename to src/main/java/org/osm2world/core/target/common/model/ProceduralModel.java index e64a0a657..8419cc967 100644 --- a/src/main/java/org/osm2world/core/target/common/model/LegacyModel.java +++ b/src/main/java/org/osm2world/core/target/common/model/ProceduralModel.java @@ -3,25 +3,27 @@ import java.util.List; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.MeshTarget; import org.osm2world.core.target.common.mesh.Mesh; /** - * a {@link Model} that still uses "draw" methods of {@link Target} - * instead of the new {@link #buildMeshes(InstanceParameters)} method. - * This exists to smooth the transition. + * a model which is generated by code during runtime, + * rather than being loaded as a static asset. */ -public interface LegacyModel extends Model { +public interface ProceduralModel extends Model { @Override - public default List buildMeshes(InstanceParameters params) { + default List buildMeshes(InstanceParameters params) { MeshTarget target = new MeshTarget(); this.render(target, params); return target.getMeshes(); } - @Override + /** + * draws an instance of the model to any {@link CommonTarget} + * + * @param target target for the model; != null + */ void render(CommonTarget target, InstanceParameters params); } diff --git a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java index 67ca85b06..5b4215a32 100644 --- a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java +++ b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java @@ -53,6 +53,7 @@ import org.osm2world.core.target.common.model.ExternalResourceModel; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.target.common.texcoord.GlobalXZTexCoordFunction; import org.osm2world.core.target.frontend_pbf.FrontendPbf.*; import org.osm2world.core.target.frontend_pbf.FrontendPbf.Animation.AnimationType; @@ -255,9 +256,9 @@ public FrontendPbfTarget(OutputStream outputStream, AxisAlignedRectangleXZ bbox, } @Override - public void drawModel(Model model, InstanceParameters params) { + public void drawModel(ModelInstance modelInstance) { - if (!bbox.contains(params.position().xz())) return; + if (!bbox.contains(modelInstance.params().position().xz())) return; MeshMetadata worldObjectMetadata = new MeshMetadata(currentWorldObject.getPrimaryMapElement().getElementWithId(), currentWorldObject.getClass()); @@ -267,7 +268,7 @@ public void drawModel(Model model, InstanceParameters params) { } Multimap map = modelInstancesByWO.get(worldObjectMetadata); - map.put(model, params); + map.put(modelInstance.model(), modelInstance.params()); } diff --git a/src/main/java/org/osm2world/core/world/data/NodeModelInstance.java b/src/main/java/org/osm2world/core/world/data/NodeModelInstance.java index f24379756..4385abb8b 100644 --- a/src/main/java/org/osm2world/core/world/data/NodeModelInstance.java +++ b/src/main/java/org/osm2world/core/world/data/NodeModelInstance.java @@ -8,6 +8,7 @@ import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; public class NodeModelInstance extends NoOutlineNodeWorldObject { @@ -32,7 +33,7 @@ public NodeModelInstance(MapNode node, Model model) { @Override public List buildMeshes() { - return model.buildMeshes(new InstanceParameters(getBase(), direction)); + return new ModelInstance(model, new InstanceParameters(getBase(), direction)).getMeshes(); } @Override diff --git a/src/main/java/org/osm2world/core/world/data/WorldObject.java b/src/main/java/org/osm2world/core/world/data/WorldObject.java index 24b4b678b..503e2b9cc 100644 --- a/src/main/java/org/osm2world/core/world/data/WorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/WorldObject.java @@ -38,7 +38,7 @@ public interface WorldObject { */ public default List buildMeshesForModelHierarchy() { List result = new ArrayList<>(buildMeshes()); - getSubModels().forEach(it -> result.addAll(it.model.buildMeshes(it.params))); + getSubModels().forEach(it -> result.addAll(it.getMeshes())); return result; } diff --git a/src/main/java/org/osm2world/core/world/modules/MastModule.java b/src/main/java/org/osm2world/core/world/modules/MastModule.java index 349a5ba38..93bcbbf58 100644 --- a/src/main/java/org/osm2world/core/world/modules/MastModule.java +++ b/src/main/java/org/osm2world/core/world/modules/MastModule.java @@ -19,9 +19,9 @@ import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.model.InstanceParameters; -import org.osm2world.core.target.common.model.LegacyModel; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.target.common.model.ProceduralModel; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; @@ -73,7 +73,7 @@ public void buildMeshesAndModels(Target target) { } - private static final Model MOBILE_PHONE_ANTENNA_MODEL = new LegacyModel() { + private static final Model MOBILE_PHONE_ANTENNA_MODEL = new ProceduralModel() { @Override public void render(CommonTarget target, InstanceParameters params) { diff --git a/src/main/java/org/osm2world/core/world/modules/PowerModule.java b/src/main/java/org/osm2world/core/world/modules/PowerModule.java index 7eaed3d24..81fc77b3e 100644 --- a/src/main/java/org/osm2world/core/world/modules/PowerModule.java +++ b/src/main/java/org/osm2world/core/world/modules/PowerModule.java @@ -46,9 +46,9 @@ import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; -import org.osm2world.core.target.common.model.LegacyModel; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.target.common.model.ProceduralModel; import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.util.ValueParseUtil; @@ -231,7 +231,7 @@ public void buildMeshesAndModels(Target target) { public static final class WindTurbine extends NoOutlineNodeWorldObject implements ProceduralWorldObject { /** model of a rotor with 1 m rotor diameter */ - public static final Model ROTOR = new LegacyModel() { + public static final Model ROTOR = new ProceduralModel() { @Override public void render(CommonTarget target, InstanceParameters params) { diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index c254d2681..31949fd08 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -50,8 +50,9 @@ import org.osm2world.core.target.common.mesh.ExtrusionGeometry; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; -import org.osm2world.core.target.common.model.LegacyModel; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.target.common.model.ProceduralModel; import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -1168,13 +1169,15 @@ public void buildMeshesAndModels(Target target) { target.setCurrentLodRange(LOD2, LOD4); double diameter = parseWidth(node.getTags(), 1f); - new ClockFace(TIME).render(target, new InstanceParameters(connector.getAttachedPos(), + var instance = new ModelInstance(new ClockFace(TIME), new InstanceParameters( + connector.getAttachedPos(), connector.getAttachedSurfaceNormal().xz().angle(), null, diameter, null)); + instance.render(target); } - private static class ClockFace implements LegacyModel { + private static class ClockFace implements ProceduralModel { private final LocalTime time; diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index d206148c4..e2df61d1a 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -31,6 +31,7 @@ import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.target.povray.POVRayTarget; import org.osm2world.core.target.povray.RenderableToPOVRay; import org.osm2world.core.world.data.*; @@ -205,14 +206,14 @@ private void addTreeDeclarationsTo(POVRayTarget target) { target.append("#ifndef (broad_leaved_tree)\n"); target.append("#declare broad_leaved_tree = object { union {\n"); - target.drawModel(new TreeGeometryModel(LeafType.BROADLEAVED, LeafCycle.DECIDUOUS, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null)); + target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.BROADLEAVED, LeafCycle.DECIDUOUS, null), + new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null))); target.append("} }\n#end\n\n"); target.append("#ifndef (coniferous_tree)\n"); target.append("#declare coniferous_tree = object { union {\n"); - target.drawModel(new TreeGeometryModel(LeafType.NEEDLELEAVED, LeafCycle.EVERGREEN, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null)); + target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.NEEDLELEAVED, LeafCycle.EVERGREEN, null), + new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null))); target.append("} }\n#end\n\n"); } @@ -281,7 +282,8 @@ private void renderTreeModel(Target target, MapElement element, VectorXYZ base, existingModels.add(model); } - target.drawModel(model, new InstanceParameters(base, 0, height, null, null)); + target.drawModel(new ModelInstance(model, + new InstanceParameters(base, 0, height, null, null))); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/ChimneyRoof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/ChimneyRoof.java index 64ba37873..f29103996 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/ChimneyRoof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/ChimneyRoof.java @@ -17,6 +17,7 @@ import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.model.ExternalResourceModel; import org.osm2world.core.target.common.model.InstanceParameters; +import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.target.common.texcoord.NamedTexCoordFunction; /** the top of a chimney, modeled as a special kind of "roof" */ @@ -72,7 +73,8 @@ public void renderTo(Target target, double baseEle) { /* mark the location where a client might want to emit a smoke effect */ var smokeEmitterModel = new ExternalResourceModel("smokeEmitter"); - target.drawModel(smokeEmitterModel, new InstanceParameters(chimneyHole.getCenter().xyz(chimneyHoleEle), 0)); + target.drawModel(new ModelInstance(smokeEmitterModel, + new InstanceParameters(chimneyHole.getCenter().xyz(chimneyHoleEle), 0))); } diff --git a/src/test/java/org/osm2world/core/target/gltf/GltfModelTest.java b/src/test/java/org/osm2world/core/target/gltf/GltfModelTest.java index 97ea427a3..939ce7a51 100644 --- a/src/test/java/org/osm2world/core/target/gltf/GltfModelTest.java +++ b/src/test/java/org/osm2world/core/target/gltf/GltfModelTest.java @@ -45,7 +45,7 @@ public void testLoadFromFile_BoxVertexColors() throws IOException { for (String extension : List.of(".gltf", "_embedded.gltf")) { var model = loadGltfTestModel("BoxVertexColors", extension); - var meshes = model.buildMeshes(new InstanceParameters(VectorXYZ.NULL_VECTOR, 0)); + var meshes = model.getMeshes(); assertEquals(1, meshes.size()); assertEquals(12, meshes.get(0).geometry.asTriangles().triangles.size()); @@ -61,7 +61,7 @@ public void testLoadFromFile_Triangle() throws IOException { for (String extension : List.of(".gltf", "_embedded.gltf")) { var model = loadGltfTestModel(assetName, extension); - var meshes = model.buildMeshes(new InstanceParameters(VectorXYZ.NULL_VECTOR, 0)); + var meshes = model.getMeshes(); assertEquals(1, meshes.size()); assertEquals(1, meshes.get(0).geometry.asTriangles().triangles.size()); From 7f220bd12146341bdb2ffc2fcbed110329a66964 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Fri, 1 Nov 2024 15:18:33 +0100 Subject: [PATCH 13/85] Upgrade mvn javadoc plugin version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c9a078674..933f0f1ee 100644 --- a/pom.xml +++ b/pom.xml @@ -332,7 +332,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.10.1 all,-missing From 4162eeaeb41f3fd3c45ab599fd8de2aed3face01 Mon Sep 17 00:00:00 2001 From: dkiselev Date: Fri, 1 Nov 2024 16:00:39 -0300 Subject: [PATCH 14/85] Update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 20396ba12..fd95f7282 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ nbactions.xml nbbuild.xml nb-configuration.xml +# VSCode +.vscode + # Miscellaneous *~ *.swp From ed85efe9d54b46a901ceb8783e02a85ccae33145 Mon Sep 17 00:00:00 2001 From: Dmitry Kiselev Date: Mon, 4 Nov 2024 11:15:53 -0400 Subject: [PATCH 15/85] Resolve config paths relative to config location --- .../org/osm2world/console/ImageExporter.java | 18 ++--- .../java/org/osm2world/console/OSM2World.java | 11 ++- .../org/osm2world/core/ConversionFacade.java | 71 +++++++++++-------- .../target/common/material/Materials.java | 23 +++--- .../core/target/common/model/Models.java | 11 ++- .../org/osm2world/core/util/ConfigUtil.java | 42 ++++++++++- 6 files changed, 115 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/osm2world/console/ImageExporter.java b/src/main/java/org/osm2world/console/ImageExporter.java index 015010f71..71214773e 100644 --- a/src/main/java/org/osm2world/console/ImageExporter.java +++ b/src/main/java/org/osm2world/console/ImageExporter.java @@ -5,7 +5,9 @@ import static org.osm2world.core.target.jogl.JOGLRenderingParameters.Winding.CCW; import static org.osm2world.core.util.ConfigUtil.*; -import java.awt.*; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; @@ -25,6 +27,7 @@ import org.osm2world.core.target.common.rendering.Camera; import org.osm2world.core.target.common.rendering.Projection; import org.osm2world.core.target.jogl.*; +import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.util.Resolution; import com.jogamp.opengl.*; @@ -101,14 +104,11 @@ private ImageExporter(Configuration config, Results results, } if (config.containsKey(BG_IMAGE_KEY)) { - String fileString = config.getString(BG_IMAGE_KEY); - if (fileString != null) { - backgroundImage = new File(fileString); - if (!backgroundImage.exists()) { - System.err.println("background image file doesn't exist: " - + backgroundImage); - backgroundImage = null; - } + backgroundImage = ConfigUtil.resolveFileConfigProperty(config, config.getString(BG_IMAGE_KEY)); + if (backgroundImage == null || !backgroundImage.exists()) { + System.err.println("background image file doesn't exist: " + + backgroundImage); + backgroundImage = null; } } diff --git a/src/main/java/org/osm2world/console/OSM2World.java b/src/main/java/org/osm2world/console/OSM2World.java index 5f03b5d09..297aa4942 100644 --- a/src/main/java/org/osm2world/console/OSM2World.java +++ b/src/main/java/org/osm2world/console/OSM2World.java @@ -1,9 +1,9 @@ package org.osm2world.console; import static java.util.Arrays.asList; +import static org.osm2world.console.CLIArgumentsUtil.getProgramMode; import static org.osm2world.console.CLIArgumentsUtil.ProgramMode.CONVERT; import static org.osm2world.console.CLIArgumentsUtil.ProgramMode.GUI; -import static org.osm2world.console.CLIArgumentsUtil.getProgramMode; import static org.osm2world.core.GlobalValues.VERSION_STRING; import java.io.File; @@ -15,7 +15,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; -import javax.swing.*; +import javax.swing.UIManager; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; @@ -238,6 +238,13 @@ public static Configuration loadConfigFiles(@Nullable LevelOfDetail lod, File... config.load(it); } + Arrays.stream(configFiles) + .filter(f -> f.exists()) + .findFirst() + .ifPresent(f -> { + config.addProperty("configPath", f.getAbsoluteFile().getParent()); + }); + if (lod != null) { config.clearProperty("lod"); config.addProperty("lod", lod.ordinal()); diff --git a/src/main/java/org/osm2world/core/ConversionFacade.java b/src/main/java/org/osm2world/core/ConversionFacade.java index 321b79809..612b7a277 100644 --- a/src/main/java/org/osm2world/core/ConversionFacade.java +++ b/src/main/java/org/osm2world/core/ConversionFacade.java @@ -9,9 +9,13 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Stream; import javax.annotation.Nullable; @@ -37,6 +41,7 @@ import org.osm2world.core.target.TargetUtil; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.model.Models; +import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.util.functions.Factory; import org.osm2world.core.world.attachment.AttachmentConnector; @@ -91,33 +96,37 @@ public TerrainElevationData getEleData() { /** * generates a default list of modules for the conversion */ - static final List createDefaultModuleList() { - - return Arrays.asList((WorldModule) - new RoadModule(), - new RailwayModule(), - new AerowayModule(), - new BuildingModule(), - new ParkingModule(), - new TreeModule(), - new StreetFurnitureModule(), - new TrafficSignModule(), - new BicycleParkingModule(), - new WaterModule(), - new PoolModule(), - new GolfModule(), - new SportsModule(), - new CliffModule(), - new BarrierModule(), - new PowerModule(), - new MastModule(), - new BridgeModule(), - new TunnelModule(), - new SurfaceAreaModule(), - new InvisibleModule(), - new IndoorModule() - ); - + static final List createDefaultModuleList(Configuration config) { + + List excludedModules = config.getList("excludeWorldModule") + .stream().map(m -> m.toString()).toList(); + + return Stream.of((WorldModule) + new RoadModule(), + new RailwayModule(), + new AerowayModule(), + new BuildingModule(), + new ParkingModule(), + new TreeModule(), + new StreetFurnitureModule(), + new TrafficSignModule(), + new BicycleParkingModule(), + new WaterModule(), + new PoolModule(), + new GolfModule(), + new SportsModule(), + new CliffModule(), + new BarrierModule(), + new PowerModule(), + new MastModule(), + new BridgeModule(), + new TunnelModule(), + new SurfaceAreaModule(), + new InvisibleModule(), + new IndoorModule() + ) + .filter(m -> !excludedModules.contains(m.getClass().getSimpleName())) + .toList(); } private Function mapProjectionFactory = MetricMapProjection::new; @@ -266,7 +275,7 @@ public Results createRepresentations(MapProjection mapProjection, MapData mapDat updatePhase(Phase.REPRESENTATION); if (worldModules == null) { - worldModules = createDefaultModuleList(); + worldModules = createDefaultModuleList(config); } Materials.configureMaterials(config); @@ -281,11 +290,11 @@ public Results createRepresentations(MapProjection mapProjection, MapData mapDat /* determine elevations */ updatePhase(Phase.ELEVATION); - String srtmDir = config.getString("srtmDir", null); + File srtmDir = ConfigUtil.resolveFileConfigProperty(config, config.getString("srtmDir", null)); TerrainElevationData eleData = null; if (srtmDir != null) { - eleData = new SRTMData(new File(srtmDir), mapProjection); + eleData = new SRTMData(srtmDir, mapProjection); } /* create terrain and attach connectors */ diff --git a/src/main/java/org/osm2world/core/target/common/material/Materials.java b/src/main/java/org/osm2world/core/target/common/material/Materials.java index 56ad431c9..55d067973 100644 --- a/src/main/java/org/osm2world/core/target/common/material/Materials.java +++ b/src/main/java/org/osm2world/core/target/common/material/Materials.java @@ -3,10 +3,10 @@ import static java.awt.Color.*; import static java.util.Collections.emptyList; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; import java.io.File; import java.lang.reflect.Field; -import java.util.List; import java.util.*; import java.util.Map.Entry; import java.util.function.Function; @@ -495,8 +495,9 @@ public static final void configureMaterials(Configuration config) { File displacementTexture = null; if (config.containsKey(keyPrefix + "_dir")) { - File textureDir = new File(config.getString(keyPrefix + "_dir")); - if (textureDir.exists() && textureDir.isDirectory()) { + + File textureDir = ConfigUtil.resolveFileConfigProperty(config, config.getString(keyPrefix + "_dir")); + if (textureDir!= null && textureDir.exists() && textureDir.isDirectory()) { for (File file : textureDir.listFiles()) { if (file.getName().contains("_Color.")) { baseColorTexture = file; @@ -624,16 +625,10 @@ public static final void configureMaterials(Configuration config) { relativeFontSize, wrap, coordFunction); } else if ("image".equals(type)) { - - File file = null; - - String fileName = config.getString(keyPrefix + "_file"); - if (fileName != null) { - file = new File(fileName); - if (!file.exists() || file.isDirectory()) { - System.err.println("File referenced in config does not exist: " + file); - file = null; - } + File file = ConfigUtil.resolveFileConfigProperty(config, config.getString(keyPrefix + "_file")); + + if (file == null || file.isDirectory()) { + file = null; } if (file == null) { file = defaultFile; } diff --git a/src/main/java/org/osm2world/core/target/common/model/Models.java b/src/main/java/org/osm2world/core/target/common/model/Models.java index 4bf41fb2e..7a6ca495f 100644 --- a/src/main/java/org/osm2world/core/target/common/model/Models.java +++ b/src/main/java/org/osm2world/core/target/common/model/Models.java @@ -10,6 +10,7 @@ import org.apache.commons.configuration.Configuration; import org.osm2world.core.target.gltf.GltfModel; +import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.world.creation.WorldModule; /** @@ -73,12 +74,16 @@ public static void configureModels(Configuration config) { if (matcher.matches()) { String modelName = matcher.group(1); - List fileNames = config.getList(key); + List fileNames = config.getList(key).stream().map(f -> f.toString()).toList(); try { List ms = new ArrayList<>(fileNames.size()); - for (Object fileName : fileNames) { - ms.add(GltfModel.loadFromFile(new File(fileName.toString()))); + for (String fileName : fileNames) { + File modelFile = ConfigUtil.resolveFileConfigProperty(config, fileName); + if (modelFile == null) { + System.err.println("Can't read model file " + fileName); + } + ms.add(GltfModel.loadFromFile(modelFile)); } models.put(modelName.toLowerCase(Locale.ROOT), ms); } catch (IOException e) { diff --git a/src/main/java/org/osm2world/core/util/ConfigUtil.java b/src/main/java/org/osm2world/core/util/ConfigUtil.java index 9a3790592..958fc7515 100644 --- a/src/main/java/org/osm2world/core/util/ConfigUtil.java +++ b/src/main/java/org/osm2world/core/util/ConfigUtil.java @@ -2,15 +2,20 @@ import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.GraphicsEnvironment; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.FileConfiguration; import org.osm2world.core.target.common.mesh.LevelOfDetail; /** @@ -130,4 +135,37 @@ public static void parseFonts(Configuration config) { } } -} \ No newline at end of file + /** + * If config references some files by path e.g. textures + * resolve file paths relative to config location + */ + public static File resolveFileConfigProperty(Configuration config, String fileName) { + if (fileName == null) { + return null; + } + + File file = new File(fileName); + + String basePath = null; + if (config.containsKey("configPath")) { + basePath = config.getString("configPath"); + } + + if (basePath == null && config instanceof FileConfiguration fc && fc.getFile() != null) { + basePath = fc.getFile().getAbsoluteFile().getParent(); + } + + if (basePath != null) { + file = Path.of(basePath).normalize() + .resolve(Path.of(fileName).normalize()).toFile(); + } + + if (!file.exists()) { + System.err.println("File referenced in config does not exist: " + file); + return null; + } + + return file; + } + +} From f0bbd603cd571ef4a9ea63d1f31700852bc95353 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 12:01:47 +0100 Subject: [PATCH 16/85] Implement getEleAt for areas and use it for parked cars --- .../org/osm2world/core/math/TriangleXYZ.java | 8 +++++++ .../world/data/AbstractAreaWorldObject.java | 24 +++++++++++++++++++ .../core/world/modules/ParkingModule.java | 14 ++++------- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/TriangleXYZ.java b/src/main/java/org/osm2world/core/math/TriangleXYZ.java index e39434832..3cda902a7 100644 --- a/src/main/java/org/osm2world/core/math/TriangleXYZ.java +++ b/src/main/java/org/osm2world/core/math/TriangleXYZ.java @@ -93,6 +93,14 @@ public TriangleXYZ shift(VectorXYZ v) { return new TriangleXYZ(v1.add(v), v2.add(v), v3.add(v)); } + /** + * returns the projection of this triangle into XZ plane. + * Fails if this triangle is vertical. + */ + public TriangleXZ xz() { + return new TriangleXZ(v1.xz(), v2.xz(), v3.xz()); + } + @Override public String toString() { return "[" + v1 + ", " + v2 + ", " + v3 + "]"; diff --git a/src/main/java/org/osm2world/core/world/data/AbstractAreaWorldObject.java b/src/main/java/org/osm2world/core/world/data/AbstractAreaWorldObject.java index 36096fb1e..b912fd324 100644 --- a/src/main/java/org/osm2world/core/world/data/AbstractAreaWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/AbstractAreaWorldObject.java @@ -2,6 +2,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singleton; +import static org.osm2world.core.math.GeometryUtil.interpolateOnTriangle; import java.util.ArrayList; import java.util.Collection; @@ -181,6 +182,29 @@ protected List getTriangulation() { } + /** + * returns elevation at any point within the triangulation of this area + */ + public double getEleAt(VectorXZ pos) { + + if (getConnectorIfAttached() != null) { + return getConnectorIfAttached().getAttachedPos().getY(); + } else if (getEleConnectors().eleConnectors.stream().allMatch(it -> it.getPosXYZ().y == 0.0)) { + // fast case for disabled elevation + return 0.0; + } + + var containingTriangle = getTriangulation().stream().filter(t -> t.xz().contains(pos)).findFirst(); + + if (containingTriangle.isPresent()) { + TriangleXYZ t = containingTriangle.get(); + return interpolateOnTriangle(pos, t.xz(), t.v1.y, t.v2.y, t.v3.y); + } else { + throw new IllegalArgumentException(pos + " is not within the triangulation of " + this); + } + + } + @Override public Collection getGroundFootprint() { // cache the otherwise unchanged result diff --git a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java index a2d369e62..c656b70a4 100644 --- a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java @@ -103,14 +103,6 @@ public void buildMeshesAndModels(Target target) { /* draw cars on the parking spaces */ - double ele = 0; - - if (getConnectorIfAttached() != null) { - ele = getConnectorIfAttached().getAttachedPos().getY(); - } else { - //TODO add elevation support - } - double carDensity = config.getDouble("carDensity", 0.3); var random = new Random(area.getId()); @@ -124,6 +116,10 @@ public void buildMeshesAndModels(Target target) { if (carModel != null) { SimplePolygonXZ bbox = parkingSpace.getOuterPolygon().minimumRotatedBoundingBox(); + VectorXZ pos = bbox.getCenter(); + + // determine elevation + double ele = getEleAt(pos); // determine the car's facing direction based on which side of the box is longer VectorXZ direction = bbox.getVertex(1).subtract(bbox.getVertex(0)); @@ -133,7 +129,7 @@ public void buildMeshesAndModels(Target target) { direction = direction.normalize(); target.addSubModel(new ModelInstance(carModel, new InstanceParameters( - bbox.getCenter().xyz(ele), direction.angle(), carColor, new LODRange(LOD3, LOD4)))); + pos.xyz(ele), direction.angle(), carColor, new LODRange(LOD3, LOD4)))); } } From 4007af1762d56cb10d6c6ae7415d5604ae56a93c Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 12:11:32 +0100 Subject: [PATCH 17/85] Do not subtract most linear barriers' footprints from the ground --- .../core/world/modules/BarrierModule.java | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java index 874535fd4..2367dfe3d 100644 --- a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java @@ -35,10 +35,7 @@ import org.osm2world.core.map_data.data.TagSet; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.*; -import org.osm2world.core.math.shapes.CircleXZ; -import org.osm2world.core.math.shapes.PolylineXZ; -import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.math.shapes.SimpleClosedShapeXZ; +import org.osm2world.core.math.shapes.*; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.ExtrusionGeometry; @@ -412,6 +409,11 @@ public void buildMeshesAndModels(Target target) { } + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class Balustrade extends LinearBarrier { @@ -537,6 +539,12 @@ public void buildMeshesAndModels(Target target) { } } + + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class PoleFence extends LinearBarrier { @@ -617,6 +625,12 @@ public void buildMeshesAndModels(Target target) { } } + + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class TrellisWorkFence extends LinearBarrier { @@ -685,6 +699,11 @@ public void buildMeshesAndModels(Target target) { } + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class CableBarrier extends PoleFence { @@ -836,6 +855,11 @@ public void buildMeshesAndModels(Target target) { } + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class JerseyBarrier extends LinearBarrier { @@ -893,6 +917,11 @@ public void buildMeshesAndModels(Target target) { } + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class BollardRow extends AbstractNetworkWaySegmentWorldObject { From 0936690c42bffd91eb82636a30b1942d0ca2df08 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 15:01:03 +0100 Subject: [PATCH 18/85] Randomize car locations slightly --- .../osm2world/core/world/modules/ParkingModule.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java index c656b70a4..462dcd4cc 100644 --- a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java @@ -116,10 +116,6 @@ public void buildMeshesAndModels(Target target) { if (carModel != null) { SimplePolygonXZ bbox = parkingSpace.getOuterPolygon().minimumRotatedBoundingBox(); - VectorXZ pos = bbox.getCenter(); - - // determine elevation - double ele = getEleAt(pos); // determine the car's facing direction based on which side of the box is longer VectorXZ direction = bbox.getVertex(1).subtract(bbox.getVertex(0)); @@ -128,6 +124,12 @@ public void buildMeshesAndModels(Target target) { } direction = direction.normalize(); + // determine direction (with some randomness) + VectorXZ pos = bbox.getCenter().add(direction.mult(-0.2 + random.nextDouble(0.4))); + + // determine elevation + double ele = getEleAt(pos); + target.addSubModel(new ModelInstance(carModel, new InstanceParameters( pos.xyz(ele), direction.angle(), carColor, new LODRange(LOD3, LOD4)))); From 47172533f8edc15c4b185c5c0368d2febf851391 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 15:02:30 +0100 Subject: [PATCH 19/85] Add bike models to bicycle parking --- .../world/modules/BicycleParkingModule.java | 48 ++++++++++++++----- .../core/world/modules/ParkingModule.java | 2 +- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java b/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java index 54ff75952..92d9ce94a 100644 --- a/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java @@ -1,5 +1,6 @@ package org.osm2world.core.world.modules; +import static java.awt.Color.*; import static java.lang.Math.max; import static java.lang.Math.toRadians; import static java.util.Collections.emptyList; @@ -20,32 +21,29 @@ import static org.osm2world.core.util.ValueParseUtil.parseUInt; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; +import java.awt.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Random; import javax.annotation.Nullable; import org.osm2world.core.map_data.data.*; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.EleConnectorGroup; -import org.osm2world.core.math.LineSegmentXZ; -import org.osm2world.core.math.SimplePolygonXZ; -import org.osm2world.core.math.VectorXYZ; -import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineShapeXZ; import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; -import org.osm2world.core.target.common.mesh.ExtrusionGeometry; -import org.osm2world.core.target.common.mesh.Geometry; -import org.osm2world.core.target.common.mesh.Mesh; -import org.osm2world.core.target.common.mesh.TriangleGeometry; +import org.osm2world.core.target.common.mesh.*; import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.target.common.model.Models; import org.osm2world.core.world.data.AreaWorldObject; import org.osm2world.core.world.data.NodeWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; @@ -57,6 +55,9 @@ */ public class BicycleParkingModule extends AbstractModule { + private static final List BIKE_COLORS = List.of( + BLACK, BLACK, BLUE, RED, LIGHT_GRAY, WHITE, GREEN, YELLOW, ORANGE, PINK); + @Override protected void applyToElement(MapElement element) { if (element.getTags().contains("amenity", "bicycle_parking")) { @@ -80,7 +81,7 @@ protected void applyToElement(MapElement element) { } } - public static abstract class BicycleStands implements WorldObject { + public abstract class BicycleStands implements WorldObject { protected static final double DEFAULT_DISTANCE_BETWEEN_STANDS = 1.0; private static final double STAND_DEFAULT_LENGTH = 1.0; @@ -172,6 +173,9 @@ public List buildMeshes() { @Override public List getSubModels() { + double bikeDensity = config.getDouble("parkedVehicleDensity", 1.0); // 0.3); + var random = new Random(getPrimaryMapElement().getElementWithId().getId()); + /* place models for the stands */ TagSet tags = getPrimaryMapElement().getTags(); @@ -198,6 +202,26 @@ public List getSubModels() { result.add(new ModelInstance(model, new InstanceParameters(standConnector.getPosXYZ(), localDirection))); + // maybe add a bike model next to it + + if (random.nextDouble() > bikeDensity) continue; + + Model bikeModel = Models.getModel("BIKE", random); + Color bikeColor = BIKE_COLORS.get(random.nextInt(BIKE_COLORS.size())); + + double bikeDirection = random.nextBoolean() + ? localDirection + : Angle.ofRadians(localDirection).plus(Angle.ofDegrees(180)).radians; + + VectorXYZ bikePos = standConnector.getPosXYZ() + .add(VectorXZ.fromAngle(bikeDirection).mult(-0.1 + random.nextDouble(0.2))) + .add(VectorXZ.fromAngle(localDirection).rightNormal().mult(random.nextBoolean() ? 0.2 : -0.2)); + + if (bikeModel != null) { + result.add(new ModelInstance(bikeModel, + new InstanceParameters(bikePos, bikeDirection, bikeColor, new LODRange(LOD3, LOD4)))); + } + } return result; @@ -223,7 +247,7 @@ public int getOverlapPriority() { } } - public static final class BicycleStandsArea extends BicycleStands implements AreaWorldObject { + public final class BicycleStandsArea extends BicycleStands implements AreaWorldObject { private final MapArea area; @@ -263,7 +287,7 @@ protected Collection area() { } - public static final class BicycleStandsWay extends BicycleStands implements WaySegmentWorldObject { + public final class BicycleStandsWay extends BicycleStands implements WaySegmentWorldObject { private final MapWay way; private final MapWaySegment waySegment; @@ -285,7 +309,7 @@ protected PolylineShapeXZ lineThroughStandCenters() { } - public static final class BicycleStandsNode extends BicycleStands implements NodeWorldObject { + public final class BicycleStandsNode extends BicycleStands implements NodeWorldObject { private final MapNode node; private final double direction; diff --git a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java index 462dcd4cc..3d8cf7f1c 100644 --- a/src/main/java/org/osm2world/core/world/modules/ParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/ParkingModule.java @@ -103,7 +103,7 @@ public void buildMeshesAndModels(Target target) { /* draw cars on the parking spaces */ - double carDensity = config.getDouble("carDensity", 0.3); + double carDensity = config.getDouble("parkedVehicleDensity", 0.3); var random = new Random(area.getId()); for (MapArea parkingSpace : parkingSpaces) { From 30c851fcd48dc0e5ac9bd6d0649d0a0b1ddb1d87 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 17:05:04 +0100 Subject: [PATCH 20/85] Drop width and length instance parameters --- .../common/model/InstanceParameters.java | 14 +++++------- .../frontend_pbf/FrontendPbfTarget.java | 2 +- .../core/world/modules/PowerModule.java | 2 +- .../core/world/modules/RailwayModule.java | 22 ++++--------------- .../world/modules/StreetFurnitureModule.java | 6 ++--- .../core/world/modules/TreeModule.java | 6 ++--- 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/model/InstanceParameters.java b/src/main/java/org/osm2world/core/target/common/model/InstanceParameters.java index 87785185d..1ce8700b6 100644 --- a/src/main/java/org/osm2world/core/target/common/model/InstanceParameters.java +++ b/src/main/java/org/osm2world/core/target/common/model/InstanceParameters.java @@ -16,29 +16,27 @@ * @param position position of the model's origin; != null * @param direction rotation of the model in the XZ plane, as an angle in radians * @param height height of the model; null for default (unspecified) height - * @param width width of the model; null for default (unspecified) width - * @param length length of the model; null for default (unspecified) length * @param color color of the main part of the model; null for unaltered colors. * This only works with specially prepared models that have a material with FF00FF placeholder color * @param lodRange LOD at which this instance is visible. */ -public record InstanceParameters(VectorXYZ position, double direction, Double height, Double width, Double length, +public record InstanceParameters(VectorXYZ position, double direction, Double height, @Nullable Color color, LODRange lodRange) { - public InstanceParameters(VectorXYZ position, double direction, Double height, Double width, Double length) { - this(position, direction, height, width, length, null, new LODRange(LOD0, LOD4)); + public InstanceParameters(VectorXYZ position, double direction, Double height) { + this(position, direction, height, null, new LODRange(LOD0, LOD4)); } public InstanceParameters(VectorXYZ position, double direction, @Nullable Color color, LODRange lodRange) { - this(position, direction, null, null, null, color, lodRange); + this(position, direction, null, color, lodRange); } public InstanceParameters(VectorXYZ position, double direction, LODRange lodRange) { - this(position, direction, null, null, null, null, lodRange); + this(position, direction, null, null, lodRange); } public InstanceParameters(VectorXYZ position, double direction) { - this(position, direction, null, null, null); + this(position, direction, null, new LODRange(LOD0, LOD4)); } } diff --git a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java index 5b4215a32..4ffc7cea2 100644 --- a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java +++ b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java @@ -408,7 +408,7 @@ private TextureLayer convertTextureLayer(org.osm2world.core.target.common.materi } private FrontendPbf.WorldObject convertModel(Model m, @Nullable TextureAtlasGroup textureAtlasGroup) { - InstanceParameters params = new InstanceParameters(NULL_VECTOR, 0.0, 1.0, null, null); + InstanceParameters params = new InstanceParameters(NULL_VECTOR, 0.0, 1.0); List meshes = m.buildMeshes(params); if (textureAtlasGroup != null) { var tempMeshStore = new MeshStore(meshes, null); diff --git a/src/main/java/org/osm2world/core/world/modules/PowerModule.java b/src/main/java/org/osm2world/core/world/modules/PowerModule.java index 81fc77b3e..f7f4b9bd5 100644 --- a/src/main/java/org/osm2world/core/world/modules/PowerModule.java +++ b/src/main/java/org/osm2world/core/world/modules/PowerModule.java @@ -346,7 +346,7 @@ public void buildMeshesAndModels(Target target) { /* draw rotor blades */ target.addSubModel(new ModelInstance(ROTOR, new InstanceParameters( position.addY(poleHeight).add(-poleRadiusTop*2.5, nacelleHeight/2, 0), - 0, rotorDiameter, rotorDiameter, rotorDiameter))); + 0, rotorDiameter))); } diff --git a/src/main/java/org/osm2world/core/world/modules/RailwayModule.java b/src/main/java/org/osm2world/core/world/modules/RailwayModule.java index 4d5932a15..2f203c3bb 100644 --- a/src/main/java/org/osm2world/core/world/modules/RailwayModule.java +++ b/src/main/java/org/osm2world/core/world/modules/RailwayModule.java @@ -2,7 +2,6 @@ import static java.util.Arrays.asList; import static java.util.Collections.*; -import static java.util.stream.Collectors.toList; import static org.osm2world.core.math.GeometryUtil.closeLoop; import static org.osm2world.core.math.GeometryUtil.equallyDistributePointsAlong; import static org.osm2world.core.math.VectorXYZ.Y_UNIT; @@ -109,24 +108,11 @@ private record SleeperModel(double sleeperWidth) implements Model { public List buildMeshes(InstanceParameters params) { VectorXYZ position = params.position(); - Double height = params.height(); - Double width = params.width(); - Double length = params.length(); - if (height == null) { - height = SLEEPER_HEIGHT; - } - if (length == null) { - length = SLEEPER_LENGTH; - } - if (width == null) { - width = sleeperWidth; - } - - SimplePolygonShapeXZ box = new AxisAlignedRectangleXZ(NULL_VECTOR, width, length); + SimplePolygonShapeXZ box = new AxisAlignedRectangleXZ(NULL_VECTOR, sleeperWidth, SLEEPER_LENGTH); box = box.rotatedCW(params.direction()); - return singletonList(new Mesh(new ExtrusionGeometry(box, asList(position, position.addY(height)), + return singletonList(new Mesh(new ExtrusionGeometry(box, asList(position, position.addY(SLEEPER_HEIGHT)), null, null, null, EnumSet.of(END_CAP), WOOD.getTextureDimensions()), WOOD, LOD4)); } @@ -269,8 +255,8 @@ public List getSubModels() { return sleeperPositions.stream() .map(it -> new ModelInstance(sleeperModel, - new InstanceParameters(it, segment.getDirection().angle(), null, sleeperWidth, null))) - .collect(toList()); + new InstanceParameters(it, segment.getDirection().angle()))) + .toList(); } diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index 31949fd08..fdd709d12 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -1172,7 +1172,7 @@ public void buildMeshesAndModels(Target target) { var instance = new ModelInstance(new ClockFace(TIME), new InstanceParameters( connector.getAttachedPos(), connector.getAttachedSurfaceNormal().xz().angle(), - null, diameter, null)); + diameter)); instance.render(target); } @@ -1188,8 +1188,8 @@ public ClockFace(LocalTime currentTime) { @Override public void render(CommonTarget target, InstanceParameters params) { - double diameter = params.width() != null ? params.width() : 1.0; - double thickness = params.length() != null ? params.length() : 0.08; + double diameter = params.height() != null ? params.height() : 1.0; + double thickness = 0.08; VectorXZ faceNormal = VectorXZ.fromAngle(params.direction()); diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index e2df61d1a..3f29c758e 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -207,13 +207,13 @@ private void addTreeDeclarationsTo(POVRayTarget target) { target.append("#ifndef (broad_leaved_tree)\n"); target.append("#declare broad_leaved_tree = object { union {\n"); target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.BROADLEAVED, LeafCycle.DECIDUOUS, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null))); + new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0))); target.append("} }\n#end\n\n"); target.append("#ifndef (coniferous_tree)\n"); target.append("#declare coniferous_tree = object { union {\n"); target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.NEEDLELEAVED, LeafCycle.EVERGREEN, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0, null, null))); + new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0))); target.append("} }\n#end\n\n"); } @@ -283,7 +283,7 @@ private void renderTreeModel(Target target, MapElement element, VectorXYZ base, } target.drawModel(new ModelInstance(model, - new InstanceParameters(base, 0, height, null, null))); + new InstanceParameters(base, 0, height))); } From 299828348843fd5cc7fcf98977157daabfffcb1a Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 4 Nov 2024 18:05:17 +0100 Subject: [PATCH 21/85] Implement Geometry.transform --- .../org/osm2world/core/math/TriangleXYZ.java | 10 +++++ .../org/osm2world/core/math/Vector3D.java | 12 ++++++ .../core/target/common/mesh/Geometry.java | 19 +++++++++- .../target/common/mesh/TriangleGeometry.java | 29 ++++++++++++++ .../osm2world/core/math/TriangleXYZTest.java | 29 ++++++++++++++ .../common/mesh/TriangleGeometryTest.java | 38 ++++++++++++++++++- .../org/osm2world/core/test/TestUtil.java | 13 +++---- 7 files changed, 139 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/TriangleXYZ.java b/src/main/java/org/osm2world/core/math/TriangleXYZ.java index 3cda902a7..b7f4b95b6 100644 --- a/src/main/java/org/osm2world/core/math/TriangleXYZ.java +++ b/src/main/java/org/osm2world/core/math/TriangleXYZ.java @@ -93,6 +93,16 @@ public TriangleXYZ shift(VectorXYZ v) { return new TriangleXYZ(v1.add(v), v2.add(v), v3.add(v)); } + public TriangleXYZ rotateY(double angleRad) { + return new TriangleXYZ(v1.rotateY(angleRad), v2.rotateY(angleRad), v3.rotateY(angleRad)); + } + + public TriangleXYZ scale(VectorXYZ scaleOrigin, double scaleFactor) { + TriangleXYZ t = this.shift(scaleOrigin.invert()); + t = new TriangleXYZ(t.v1.mult(scaleFactor), t.v2.mult(scaleFactor), t.v3.mult(scaleFactor)); + return t.shift(scaleOrigin); + } + /** * returns the projection of this triangle into XZ plane. * Fails if this triangle is vertical. diff --git a/src/main/java/org/osm2world/core/math/Vector3D.java b/src/main/java/org/osm2world/core/math/Vector3D.java index 35afd5992..0b7a2a8c9 100644 --- a/src/main/java/org/osm2world/core/math/Vector3D.java +++ b/src/main/java/org/osm2world/core/math/Vector3D.java @@ -8,4 +8,16 @@ public interface Vector3D extends BoundedObject { public VectorXZ xz(); + private VectorXYZ xyz() { + if (this instanceof VectorXYZ) { + return (VectorXYZ) this; + } else { + return new VectorXYZ(getX(), getY(), getZ()); + } + } + + public static double distance(Vector3D v1, Vector3D v2) { + return v1.xyz().distanceTo(v2.xyz()); + } + } diff --git a/src/main/java/org/osm2world/core/target/common/mesh/Geometry.java b/src/main/java/org/osm2world/core/target/common/mesh/Geometry.java index 0a1d1e4ec..d3e51a875 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/Geometry.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/Geometry.java @@ -1,10 +1,15 @@ package org.osm2world.core.target.common.mesh; -import static java.util.Collections.*; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; import static java.util.stream.Collectors.toList; import java.util.List; +import javax.annotation.Nullable; + +import org.osm2world.core.math.Angle; +import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.common.mesh.TriangleGeometry.CalculatedNormals; @@ -79,4 +84,16 @@ public static Geometry combine(List geometries) { } + /** + * Transforms this geometry (first translate, then rotate, then scale). + * + * @param translation vector to shift this geometry by + * @param rotation clockwise rotation around y-axis + * @param scale scaling factor, greater than 0 + * @return a {@link Geometry} with the transformations applied + */ + default Geometry transform(@Nullable VectorXYZ translation, @Nullable Angle rotation, @Nullable Double scale) { + return asTriangles().transform(translation, rotation, scale); + } + } diff --git a/src/main/java/org/osm2world/core/target/common/mesh/TriangleGeometry.java b/src/main/java/org/osm2world/core/target/common/mesh/TriangleGeometry.java index aee55538b..31e9cf65a 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/TriangleGeometry.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/TriangleGeometry.java @@ -5,6 +5,7 @@ import static java.util.Collections.nCopies; import static java.util.stream.Collectors.toList; import static org.osm2world.core.math.GeometryUtil.*; +import static org.osm2world.core.math.VectorXYZ.NULL_VECTOR; import java.awt.*; import java.util.ArrayList; @@ -12,6 +13,7 @@ import javax.annotation.Nullable; +import org.osm2world.core.math.Angle; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; @@ -324,4 +326,31 @@ private List> applyDefaultTexCoordFunctions(List verti } + public TriangleGeometry transform(@Nullable VectorXYZ translation, @Nullable Angle rotation, @Nullable Double scale) { + + VectorXYZ t = (translation != null) ? translation : NULL_VECTOR; + Angle r = (rotation != null) ? rotation : Angle.ofRadians(0); + double s = (scale != null) ? scale : 1.0; + + if (s <= 0) throw new IllegalArgumentException("Illegal scale: " + scale); + + if (t.equals(NULL_VECTOR) && r.radians == 0.0 && s == 1.0) return this; + + List newTriangles = triangles.stream() + .map(it -> it.shift(t)) + .map(it -> it.rotateY(r.radians)) + .map(it -> it.scale(NULL_VECTOR, s)) + .toList(); + + if (normalData instanceof CalculatedNormals calculatedNormals) { + return new TriangleGeometry(newTriangles, calculatedNormals.normalMode, texCoords, colors); + } else { + List newNormals = normalData.normals().stream() + .map(it -> it.rotateY(r.radians)) + .toList(); + return new TriangleGeometry(newTriangles, newNormals, texCoords, colors); + } + + } + } diff --git a/src/test/java/org/osm2world/core/math/TriangleXYZTest.java b/src/test/java/org/osm2world/core/math/TriangleXYZTest.java index 93a322bc3..cb6f59a37 100644 --- a/src/test/java/org/osm2world/core/math/TriangleXYZTest.java +++ b/src/test/java/org/osm2world/core/math/TriangleXYZTest.java @@ -1,6 +1,7 @@ package org.osm2world.core.math; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.osm2world.core.math.VectorXYZ.*; import static org.osm2world.core.test.TestUtil.assertAlmostEquals; @@ -93,4 +94,32 @@ public void testSplitTriangleOnLine() { } + @Test + public void testRotateY_ZeroAngle() { + TriangleXYZ triangle = new TriangleXYZ(new VectorXYZ(0, 0, 0), new VectorXYZ(1, 2, 3), new VectorXYZ(4, 5, 6)); + assertAlmostEquals(triangle, triangle.rotateY(0)); + } + + @Test + public void testRotateY_NonZeroAngle() { + var triangle = new TriangleXYZ(new VectorXYZ(0, 0, 0), new VectorXYZ(1, 0, 0), new VectorXYZ(0, 0, 1)); + var result = triangle.rotateY(Math.PI); + assertAlmostEquals(new TriangleXYZ(new VectorXYZ(0, 0, 0), new VectorXYZ(-1, 0, 0), new VectorXYZ(0, 0, -1)), result); + } + + @Test + public void testRotateY_NegativeAngle() { + TriangleXYZ triangle = new TriangleXYZ(new VectorXYZ(0, 0, 0), new VectorXYZ(1, 2, 3), new VectorXYZ(4, 5, 6)); + var resultTriangle = triangle.rotateY(-Math.PI / 4); + assertNotEquals(triangle, resultTriangle); + } + + @Test + public void testScale() { + var triangle = new TriangleXYZ(new VectorXYZ(9, 0, 0), new VectorXYZ(11, 0, 0), new VectorXYZ(10, 1, 1)); + var result = triangle.scale(new VectorXYZ(10, 0, 0), 2.0); + assertAlmostEquals(new TriangleXYZ(new VectorXYZ(8, 0, 0), new VectorXYZ(12, 0, 0), new VectorXYZ(10, 2, 2)), result); + + } + } diff --git a/src/test/java/org/osm2world/core/target/common/mesh/TriangleGeometryTest.java b/src/test/java/org/osm2world/core/target/common/mesh/TriangleGeometryTest.java index 3acd727fa..e64a176cc 100644 --- a/src/test/java/org/osm2world/core/target/common/mesh/TriangleGeometryTest.java +++ b/src/test/java/org/osm2world/core/target/common/mesh/TriangleGeometryTest.java @@ -1,15 +1,23 @@ package org.osm2world.core.target.common.mesh; import static java.awt.Color.RED; +import static java.awt.Color.YELLOW; +import static java.lang.Math.PI; import static java.util.Arrays.asList; -import static java.util.Collections.*; -import static org.junit.Assert.*; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.osm2world.core.target.common.mesh.MeshTestUtil.containsTriangle; +import static org.osm2world.core.test.TestUtil.assertSameCyclicOrder; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; +import org.osm2world.core.math.Angle; +import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.target.common.material.Material.Interpolation; @@ -60,4 +68,30 @@ public void testSmoothTriangleStrip() { } + @Test + public void testTransform() { + + var t = new TriangleXYZ(new VectorXYZ(0, 0, 0), new VectorXYZ(1, 0, 0), new VectorXYZ(0, 1, 0)); + + var builder = new TriangleGeometry.Builder(0, YELLOW, Interpolation.FLAT); + builder.addTriangles(t); + TriangleGeometry geometry = builder.build(); + + List result0 = geometry.transform(new VectorXYZ(0, 5, 0), null, null).triangles; + assertEquals(1, result0.size()); + assertSameCyclicOrder(false, result0.get(0).verticesNoDup(), + new VectorXYZ(0, 5, 0), new VectorXYZ(1, 5, 0), new VectorXYZ(0, 6, 0)); + + List result1 = geometry.transform(new VectorXYZ(0, 5, 0), Angle.ofRadians(-PI/2), null).triangles; + assertEquals(1, result1.size()); + assertSameCyclicOrder(false, result1.get(0).verticesNoDup(), + new VectorXYZ(0, 5, 0), new VectorXYZ(0, 5, 1), new VectorXYZ(0, 6, 0)); + + List result2 = geometry.transform(new VectorXYZ(0, 0, 0), Angle.ofRadians(-PI/2), 0.5).triangles; + assertEquals(1, result2.size()); + assertSameCyclicOrder(false, result2.get(0).verticesNoDup(), + new VectorXYZ(0, 0, 0), new VectorXYZ(0, 0, 0.5), new VectorXYZ(0, 0.5, 0)); + + } + } diff --git a/src/test/java/org/osm2world/core/test/TestUtil.java b/src/test/java/org/osm2world/core/test/TestUtil.java index 297b36bbe..1ed359b1c 100644 --- a/src/test/java/org/osm2world/core/test/TestUtil.java +++ b/src/test/java/org/osm2world/core/test/TestUtil.java @@ -9,10 +9,7 @@ import java.util.List; import java.util.*; -import org.osm2world.core.math.PolygonXYZ; -import org.osm2world.core.math.TriangleXYZ; -import org.osm2world.core.math.VectorXYZ; -import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; import org.osm2world.core.util.color.LColor; @@ -175,14 +172,14 @@ public static final void assertAlmostEquals(LColor expected, LColor actual) { * @param actual the actual sequence, to be compared with expected, != null * @param expected the expected sequence, != null */ - public static final void assertSameCyclicOrder(boolean reversible, - List actual, VectorXZ... expected) { + public static final void assertSameCyclicOrder(boolean reversible, + List actual, V... expected) { if (actual.size() != expected.length) { fail("expected size " + expected.length + ", found list of size " + actual.size()); } - List actualModified = new ArrayList<>(actual); + List actualModified = new ArrayList<>(actual); for (boolean reverse : asList(false, true)) { @@ -200,7 +197,7 @@ public static final void assertSameCyclicOrder(boolean reversible, for (int i = 0; i < actualModified.size(); i++) { int iWithOffset = (i + offset) % actualModified.size(); - if (VectorXZ.distance(expected[i], actualModified.get(iWithOffset)) > 0.0001) { + if (Vector3D.distance(expected[i], actualModified.get(iWithOffset)) > 0.0001) { matches = false; break; } From ffe2eba931511f77349bbd68103ef9e7aa8d86c9 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 5 Nov 2024 11:38:18 +0100 Subject: [PATCH 22/85] Use LODRange for Mesh LOD --- .../org/osm2world/core/target/Target.java | 2 +- .../core/target/common/MeshTarget.java | 12 +++++------ .../core/target/common/mesh/LODRange.java | 9 +++++++++ .../core/target/common/mesh/Mesh.java | 20 +++++++------------ .../frontend_pbf/FrontendPbfTarget.java | 4 ++-- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/Target.java b/src/main/java/org/osm2world/core/target/Target.java index 9f17a17f6..2f1188dca 100644 --- a/src/main/java/org/osm2world/core/target/Target.java +++ b/src/main/java/org/osm2world/core/target/Target.java @@ -31,7 +31,7 @@ default LevelOfDetail getLod() { default void beginObject(@Nullable WorldObject object) {} public default void drawMesh(Mesh mesh) { - if (mesh.lodRangeContains(getLod())) { + if (mesh.lodRange.contains(getLod())) { TriangleGeometry tg = mesh.geometry.asTriangles(); drawTriangles(mesh.material, tg.triangles, tg.normalData.normals(), tg.texCoords); } diff --git a/src/main/java/org/osm2world/core/target/common/MeshTarget.java b/src/main/java/org/osm2world/core/target/common/MeshTarget.java index 5a1b06952..6ff147d33 100644 --- a/src/main/java/org/osm2world/core/target/common/MeshTarget.java +++ b/src/main/java/org/osm2world/core/target/common/MeshTarget.java @@ -72,8 +72,8 @@ public FilterLod(LevelOfDetail targetLod) { @Override public MeshStore apply(MeshStore meshStore) { return new MeshStore(meshStore.meshesWithMetadata().stream() - .filter(m -> m.mesh().lodRangeContains(targetLod)) - .collect(toList())); + .filter(m -> m.mesh().lodRange.contains(targetLod)) + .toList()); } } @@ -112,8 +112,8 @@ public MergeMeshes(Set options) { /** checks if two meshes should be merged according to the MergeOptions */ public boolean shouldBeMerged(MeshWithMetadata m1, MeshWithMetadata m2) { - if (m1.mesh().lodRangeMin != m2.mesh().lodRangeMin - || m1.mesh().lodRangeMax != m2.mesh().lodRangeMax) { + if (m1.mesh().lodRange.min() != m2.mesh().lodRange.min() + || m1.mesh().lodRange.max() != m2.mesh().lodRange.max()) { return false; } @@ -226,7 +226,7 @@ public MeshStore apply(MeshStore meshStore) { .withTransparency(layer > 0 ? Material.Transparency.BINARY : null) .withLayers(List.of(mesh.material.getTextureLayers().get(layer))); - Mesh newMesh = new Mesh(newGeometry, singleLayerMaterial, mesh.lodRangeMin, mesh.lodRangeMax); + Mesh newMesh = new Mesh(newGeometry, singleLayerMaterial, mesh.lodRange); result.add(new MeshWithMetadata(newMesh, meshWithMetadata.metadata())); @@ -466,7 +466,7 @@ private static MeshStore replaceTexturesWithAtlas(MeshStore meshStore, TextureAt builder.addTriangles(tg.triangles, newTexCoords, tg.colors, tg.normalData.normals()); Material newMaterial = mesh.material.withLayers(newTextureLayers); - Mesh newMesh = new Mesh(builder.build(), newMaterial, mesh.lodRangeMin, mesh.lodRangeMax); + Mesh newMesh = new Mesh(builder.build(), newMaterial, mesh.lodRange); result.add(new MeshWithMetadata(newMesh, meshWithMetadata.metadata())); diff --git a/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java b/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java index 603ac3bf7..ac67fede7 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java @@ -13,4 +13,13 @@ public LODRange(LevelOfDetail lod) { this(lod, lod); } + @Override + public String toString() { + return "LOD " + min.ordinal() + "-" + max.ordinal(); + } + + public boolean contains(LevelOfDetail lod) { + return min.ordinal() <= lod.ordinal() && lod.ordinal() <= max.ordinal(); + } + } diff --git a/src/main/java/org/osm2world/core/target/common/mesh/Mesh.java b/src/main/java/org/osm2world/core/target/common/mesh/Mesh.java index 3cedbaea7..713f0d328 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/Mesh.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/Mesh.java @@ -6,8 +6,7 @@ public class Mesh { public final Geometry geometry; public final Material material; - public final LevelOfDetail lodRangeMin; - public final LevelOfDetail lodRangeMax; + public final LODRange lodRange; public Mesh(Geometry geometry, Material material) { this(geometry, material, LevelOfDetail.LOD0, LevelOfDetail.LOD4); @@ -18,29 +17,24 @@ public Mesh(Geometry geometry, Material material, LevelOfDetail lod) { } public Mesh(Geometry geometry, Material material, LevelOfDetail lodRangeMin, LevelOfDetail lodRangeMax) { + this(geometry, material, new LODRange(lodRangeMin, lodRangeMax)); + } + + public Mesh(Geometry geometry, Material material, LODRange lodRange) { this.geometry = geometry; this.material = material; - this.lodRangeMin = lodRangeMin; - this.lodRangeMax = lodRangeMax; + this.lodRange = lodRange; - if (lodRangeMin.compareTo(lodRangeMax) > 0) { - throw new IllegalArgumentException("invalid LOD range: " + lodRangeMin + "-" + lodRangeMax); - } if (geometry instanceof TriangleGeometry tg && tg.texCoords.size() != material.getNumTextureLayers()) { throw new IllegalArgumentException("incorrect number of texCoord layers"); } } - public boolean lodRangeContains(LevelOfDetail lod) { - return lodRangeMin.ordinal() <= lod.ordinal() && lod.ordinal() <= lodRangeMax.ordinal(); - } - @Override public String toString() { - String lodString = "LOD " + lodRangeMin.ordinal() + "-" + lodRangeMax.ordinal() + ", "; - return "Mesh(" + lodString + material + ", " + geometry.getClass().getSimpleName() + ")"; + return "Mesh(" + lodRange + ", " + geometry.getClass().getSimpleName() + ")"; } } diff --git a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java index 4ffc7cea2..4f5a560bd 100644 --- a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java +++ b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java @@ -450,8 +450,8 @@ private List buildWorldObjects(@Nullable MeshMetadata w List meshesAtLod = new ArrayList<>(); for (Mesh m : meshes) { - if (m.lodRangeMin == LevelOfDetail.values()[minLod] - && m.lodRangeMax == LevelOfDetail.values()[maxLod]) { + if (m.lodRange.min() == LevelOfDetail.values()[minLod] + && m.lodRange.max() == LevelOfDetail.values()[maxLod]) { meshesAtLod.add(m); } } From a02db64088bc2ff1c3b60bab1a4c43da2dc1fd99 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 5 Nov 2024 14:05:09 +0100 Subject: [PATCH 23/85] Offer a method to calculate the intersection of LODRanges --- .../core/target/common/mesh/LODRange.java | 16 ++++++++++++++ .../core/target/common/mesh/LODRangeTest.java | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/test/java/org/osm2world/core/target/common/mesh/LODRangeTest.java diff --git a/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java b/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java index ac67fede7..a007f6b8c 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/LODRange.java @@ -1,5 +1,9 @@ package org.osm2world.core.target.common.mesh; +import java.util.Arrays; + +import javax.annotation.Nullable; + /** a range between two {@link LevelOfDetail} values (inclusive) */ public record LODRange(LevelOfDetail min, LevelOfDetail max) { @@ -22,4 +26,16 @@ public boolean contains(LevelOfDetail lod) { return min.ordinal() <= lod.ordinal() && lod.ordinal() <= max.ordinal(); } + /** returns the intersection of the ranges, or null if the intersection is empty */ + public static @Nullable LODRange intersection(LODRange... ranges) { + if (ranges.length == 0) throw new IllegalArgumentException(); + var min = Arrays.stream(ranges).map(it -> it.min).max(LevelOfDetail::compareTo).get(); + var max = Arrays.stream(ranges).map(it -> it.max).min(LevelOfDetail::compareTo).get(); + if (min.ordinal() > max.ordinal()) { + return null; + } else { + return new LODRange(min, max); + } + } + } diff --git a/src/test/java/org/osm2world/core/target/common/mesh/LODRangeTest.java b/src/test/java/org/osm2world/core/target/common/mesh/LODRangeTest.java new file mode 100644 index 000000000..2b7d5feb6 --- /dev/null +++ b/src/test/java/org/osm2world/core/target/common/mesh/LODRangeTest.java @@ -0,0 +1,22 @@ +package org.osm2world.core.target.common.mesh; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; + +import org.junit.Test; + +public class LODRangeTest { + + @Test + public void testIntersection() { + + assertEquals(new LODRange(LOD1, LOD3), LODRange.intersection(new LODRange(LOD1, LOD3))); + assertEquals(new LODRange(LOD1, LOD3), LODRange.intersection(new LODRange(LOD0, LOD3), new LODRange(LOD1, LOD4))); + assertEquals(new LODRange(LOD2), LODRange.intersection(new LODRange(LOD1, LOD2), new LODRange(LOD2, LOD4))); + assertEquals(new LODRange(LOD2), LODRange.intersection(new LODRange(LOD1, LOD2), new LODRange(LOD1, LOD3), new LODRange(LOD2, LOD4))); + assertNull(LODRange.intersection(new LODRange(LOD0, LOD1), new LODRange(LOD2, LOD3))); + + } + +} \ No newline at end of file From c6b3cc77604b28a75507bd497edb72b553ab7b98 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 6 Nov 2024 15:44:21 +0100 Subject: [PATCH 24/85] Port Building from LegacyWorldObject to ProceduralWorldObject --- .../java/org/osm2world/core/target/Renderable.java | 2 +- .../org/osm2world/core/world/modules/TreeModule.java | 10 +++++----- .../core/world/modules/building/Building.java | 9 ++++----- .../core/world/modules/building/BuildingPart.java | 9 ++++----- .../osm2world/core/world/modules/building/Door.java | 4 ++-- .../osm2world/core/world/modules/building/Floor.java | 4 ++-- .../core/world/modules/building/GeometryWindow.java | 4 ++-- .../core/world/modules/building/TexturedWindow.java | 4 ++-- .../osm2world/core/world/modules/building/Wall.java | 6 +++--- .../core/world/modules/building/WallElement.java | 4 ++-- .../core/world/modules/building/WallSurface.java | 4 ++-- .../building/indoor/BuildingPartInterior.java | 12 +++++------- .../core/world/modules/building/indoor/Ceiling.java | 8 ++++---- .../world/modules/building/indoor/IndoorArea.java | 11 +++++------ .../world/modules/building/indoor/IndoorFloor.java | 6 +++--- .../world/modules/building/indoor/IndoorModule.java | 7 +++---- .../world/modules/building/indoor/IndoorRoom.java | 9 ++++----- .../world/modules/building/indoor/IndoorWall.java | 8 ++++---- .../world/modules/building/roof/ChimneyRoof.java | 4 ++-- .../world/modules/building/roof/HeightfieldRoof.java | 4 ++-- .../core/world/modules/building/roof/Roof.java | 8 ++++---- .../world/modules/building/roof/SpindleRoof.java | 6 +++--- 22 files changed, 68 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/Renderable.java b/src/main/java/org/osm2world/core/target/Renderable.java index f2eb1317c..72b36b565 100644 --- a/src/main/java/org/osm2world/core/target/Renderable.java +++ b/src/main/java/org/osm2world/core/target/Renderable.java @@ -10,6 +10,6 @@ public interface Renderable { * Most objects will use the same code for all {@link Target} implementations, * but some may use special-case handling with instanceof checks. */ - public void renderTo(Target target); + public void renderTo(CommonTarget target); } diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index 3f29c758e..c62d4dc18 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -23,7 +23,7 @@ import org.osm2world.core.math.GeometryUtil; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.FaceTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; @@ -245,7 +245,7 @@ private void renderTreePovrayModel(POVRayTarget target, MapElement element, Vect } - private void renderTreeModel(Target target, MapElement element, VectorXYZ base, + private void renderTreeModel(CommonTarget target, MapElement element, VectorXYZ base, LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { // "random" decision to flip the tree texture based on z coord @@ -394,7 +394,7 @@ public GroundState getGroundState() { } @Override - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { if (target instanceof POVRayTarget) { renderTreePovrayModel((POVRayTarget)target, node, getBase(), leafType, leafCycle, species); } else { @@ -487,7 +487,7 @@ public void addDeclarationsTo(POVRayTarget target) { } @Override - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { for (EleConnector treeConnector : treeConnectors) { @@ -596,7 +596,7 @@ public void addDeclarationsTo(POVRayTarget target) { } @Override - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { for (EleConnector treeConnector : treeConnectors) { diff --git a/src/main/java/org/osm2world/core/world/modules/building/Building.java b/src/main/java/org/osm2world/core/world/modules/building/Building.java index bc7164672..edc09797c 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Building.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Building.java @@ -21,17 +21,16 @@ import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.indoor.IndoorWall; /** * a building. Rendering a building is implemented as rendering all of its {@link BuildingPart}s. */ -public class Building implements AreaWorldObject, LegacyWorldObject { +public class Building implements AreaWorldObject, ProceduralWorldObject { private final MapArea area; @@ -160,8 +159,8 @@ public double getGroundLevelEle() { } @Override - public void renderTo(Target target) { - FaultTolerantIterationUtil.forEach(parts, part -> part.renderTo(target)); + public void buildMeshesAndModels(Target target) { + FaultTolerantIterationUtil.forEach(parts, part -> part.buildMeshesAndModels(target)); IndoorWall.renderNodePolygons(target, wallNodePolygonSegments); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index f1720eeaf..bfa970cdb 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -28,12 +28,11 @@ import org.osm2world.core.math.algorithms.CAGUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level; @@ -47,7 +46,7 @@ * Consists of {@link Wall}s, a {@link Roof}, and maybe a {@link Floor}. * This is the core class of the {@link BuildingModule}. */ -public class BuildingPart implements AreaWorldObject, LegacyWorldObject { +public class BuildingPart implements AreaWorldObject, ProceduralWorldObject { static final double DEFAULT_RIDGE_HEIGHT = 5; @@ -434,7 +433,7 @@ static List splitIntoWalls(MapArea buildingPartArea, BuildingPart building } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { if (walls == null) { // the reason why this is called here rather than the constructor is tunnel=building_passage: @@ -456,7 +455,7 @@ public void renderTo(Target target) { floors.forEach(f -> f.renderTo(target)); if (buildingPartInterior != null){ - buildingPartInterior.renderTo(target); + buildingPartInterior.buildMeshesAndModels(target); } } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Door.java b/src/main/java/org/osm2world/core/world/modules/building/Door.java index e70282404..f4db5ed13 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Door.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Door.java @@ -14,7 +14,7 @@ import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; public class Door implements WallElement { @@ -47,7 +47,7 @@ public double insetDistance() { } @Override - public void renderTo(Target target, WallSurface surface) { + public void renderTo(CommonTarget target, WallSurface surface) { Material doorMaterial = ENTRANCE_DEFAULT; diff --git a/src/main/java/org/osm2world/core/world/modules/building/Floor.java b/src/main/java/org/osm2world/core/world/modules/building/Floor.java index 8a2a8707a..217e0950e 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Floor.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Floor.java @@ -11,8 +11,8 @@ import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.TriangleXZ; import org.osm2world.core.math.algorithms.TriangulationUtil; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; class Floor implements Renderable { @@ -30,7 +30,7 @@ public Floor(BuildingPart buildingPart, Material material, PolygonWithHolesXZ po } @Override - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { double floorEle = buildingPart.building.getGroundLevelEle() + floorHeight - 0.01; diff --git a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java index ba47cd282..55a6f699e 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java @@ -23,7 +23,7 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.JTSBufferUtil; import org.osm2world.core.math.shapes.*; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.ExtrudeOption; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.util.enums.LeftRightBoth; @@ -246,7 +246,7 @@ public double insetDistance() { } @Override - public void renderTo(Target target, WallSurface surface) { + public void renderTo(CommonTarget target, WallSurface surface) { VectorXYZ windowNormal = surface.normalAt(outline().getCentroid()); diff --git a/src/main/java/org/osm2world/core/world/modules/building/TexturedWindow.java b/src/main/java/org/osm2world/core/world/modules/building/TexturedWindow.java index 95395f6b2..3164e2c27 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/TexturedWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/TexturedWindow.java @@ -11,7 +11,7 @@ import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; class TexturedWindow implements Window { @@ -43,7 +43,7 @@ public double insetDistance() { } @Override - public void renderTo(Target target, WallSurface surface) { + public void renderTo(CommonTarget target, WallSurface surface) { PolygonXYZ frontOutline = surface.convertTo3D(outline()); diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/Wall.java index 40a10f54a..adaa6b895 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Wall.java @@ -24,8 +24,8 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.PolylineShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -81,11 +81,11 @@ public Wall(@Nullable MapWay wallWay, BuildingPart buildingPart, List n } @Override - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { renderTo(target, true); } - public void renderTo(Target target, boolean renderElements) { + public void renderTo(CommonTarget target, boolean renderElements) { BuildingDefaults defaults = BuildingDefaults.getDefaultsFor(tags); diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallElement.java b/src/main/java/org/osm2world/core/world/modules/building/WallElement.java index 6f8e50ff7..1edf4b742 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallElement.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallElement.java @@ -1,7 +1,7 @@ package org.osm2world.core.world.modules.building; import org.osm2world.core.math.SimplePolygonXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; /** * something that can be placed into a wall, such as a window or door @@ -20,6 +20,6 @@ interface WallElement { */ public double insetDistance(); - public void renderTo(Target target, WallSurface surface); + public void renderTo(CommonTarget target, WallSurface surface); } \ No newline at end of file diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java index 563e00987..dae5ed49b 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java @@ -27,7 +27,7 @@ import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.TextureDataDimensions; import org.osm2world.core.target.common.material.TextureLayer; @@ -160,7 +160,7 @@ public void addElementIfSpaceFree(WallElement element) { * @param windowHeight the height for window textures will be replaced with this value if it's non-null * @param renderElements whether the {@link WallElement}s inserted into this surface should also be rendered */ - public void renderTo(Target target, VectorXZ textureOrigin, + public void renderTo(CommonTarget target, VectorXZ textureOrigin, boolean applyWindowTexture, Double windowHeight, boolean renderElements) { /* render the elements on the wall */ diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java index 90d1b8c5e..b62d5bd0c 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java @@ -5,9 +5,8 @@ import java.util.List; import org.osm2world.core.map_data.data.MapElement; -import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; import org.osm2world.core.world.attachment.AttachmentSurface; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.BuildingPart; /** @@ -15,7 +14,7 @@ * This could be merged into {@link BuildingPart}, but is kept separate for now * as it might be helpful to eventually allow interiors spanning entire buildings. */ -public class BuildingPartInterior implements Renderable { +public class BuildingPartInterior { private final List walls = new ArrayList<>(); private final List rooms = new ArrayList<>(); @@ -63,16 +62,15 @@ public Collection getAttachmentSurfaces() { } - @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(ProceduralWorldObject.Target target) { IndoorWall.allRenderedWallSegments = new ArrayList<>(); //FIXME this is not thread-safe! walls.forEach(w -> w.renderTo(target)); - rooms.forEach(r -> r.renderTo(target)); + rooms.forEach(r -> r.buildMeshesAndModels(target)); - areas.forEach(a -> a.renderTo(target)); + areas.forEach(a -> a.buildMeshesAndModels(target)); } } diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java index cfd01f6ab..3718ca7ad 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java @@ -14,7 +14,7 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.modules.building.BuildingPart; @@ -55,7 +55,7 @@ public Collection getAttachmentSurfaces() { } - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { if (render && polygon != null) { @@ -71,7 +71,7 @@ public void renderTo(Target target) { } } - private void renderSurface(Target target, double floorEle){ + private void renderSurface(CommonTarget target, double floorEle){ Collection triangles = TriangulationUtil.triangulate(polygon); List trianglesXYZ = triangles.stream() @@ -82,7 +82,7 @@ private void renderSurface(Target target, double floorEle){ triangleTexCoordLists(trianglesXYZ, material, GLOBAL_X_Z)); } - private void renderSides(Target target, List sides, double floorEle) { + private void renderSides(CommonTarget target, List sides, double floorEle) { VectorXYZ bottom = new VectorXYZ(0, floorEle - 0.2, 0); VectorXYZ top = new VectorXYZ(0, floorEle, 0); diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorArea.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorArea.java index d3dfd766e..985d4b9bc 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorArea.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorArea.java @@ -7,13 +7,12 @@ import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.math.PolygonWithHolesXZ; -import org.osm2world.core.target.Target; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; -public class IndoorArea implements AreaWorldObject, LegacyWorldObject { +public class IndoorArea implements AreaWorldObject, ProceduralWorldObject { private final IndoorFloor floor; @@ -36,9 +35,9 @@ public Collection getAttachmentSurfaces() { return floor.getAttachmentSurfaces(); } - @Override - public void renderTo(Target target) { - floor.renderTo(target); + @Override + public void buildMeshesAndModels(Target target) { + floor.renderTo(target); } @Override diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java index c9cf52f84..d0eea4b8e 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java @@ -14,7 +14,7 @@ import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.algorithms.CAGUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -64,7 +64,7 @@ public Collection getAttachmentSurfaces() { } - private void renderTo(Target target, boolean attachmentSurfaceBool) { + private void renderTo(CommonTarget target, boolean attachmentSurfaceBool) { if (!attachmentSurfaceBool && level != buildingPart.levelStructure.levels.get(0).level) { ceiling.renderTo(target); @@ -108,7 +108,7 @@ private void renderTo(Target target, boolean attachmentSurfaceBool) { } } - public void renderTo(Target target) { + public void renderTo(CommonTarget target) { renderTo(target, false); } } diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java index 1a818fced..c5de1602b 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java @@ -15,11 +15,10 @@ import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.TriangulationUtil; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.data.AbstractAreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.Door; import org.osm2world.core.world.modules.building.DoorParameters; import org.osm2world.core.world.modules.building.WallSurface; @@ -34,7 +33,7 @@ protected void applyToArea(MapArea area) { } } - private static class Elevator extends AbstractAreaWorldObject implements LegacyWorldObject { + private static class Elevator extends AbstractAreaWorldObject implements ProceduralWorldObject { private final double carHeight = 2.2; @@ -94,7 +93,7 @@ public Iterable getAttachmentConnectors() { } @Override - public void renderTo(Target target) { + public void buildMeshesAndModels(Target target) { List outerLineSegments = area.getOuterPolygon().makeClockwise().getSegments(); List doorNodes = area.getBoundaryNodes().stream().filter(n -> n.getTags().containsKey("door")).collect(toList()); diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java index d17c76b1a..b7941845b 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java @@ -9,14 +9,13 @@ import org.apache.commons.lang.math.IntRange; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_elevation.data.EleConnector; -import org.osm2world.core.target.Target; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.LegacyWorldObject; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.building.BuildingDefaults; -public class IndoorRoom implements AreaWorldObject, LegacyWorldObject { +public class IndoorRoom implements AreaWorldObject, ProceduralWorldObject { private final IndoorWall wall; private final IndoorFloor floor; @@ -58,8 +57,8 @@ public Collection getAttachmentSurfaces() { } - @Override - public void renderTo(Target target) { + @Override + public void buildMeshesAndModels(Target target) { wall.renderTo(target); diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java index 1c67cdcf1..1d43014b3 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java @@ -18,8 +18,8 @@ import org.osm2world.core.map_data.data.*; import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.TriangulationUtil; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -697,7 +697,7 @@ private static boolean roughlyEqual(VectorXZ v1, VectorXZ v2){ return v1.subtract(v2).lengthSquared() < 0.00001; } - public static void renderNodePolygons(Target target, Map> nodeToLineSegments){ + public static void renderNodePolygons(CommonTarget target, Map> nodeToLineSegments){ for (Map.Entry> entry : nodeToLineSegments.entrySet()) { NodeWithLevelAndHeights nodeAndLevel = entry.getKey(); @@ -794,7 +794,7 @@ public static void renderNodePolygons(Target target, Map getInnerSegments() { } @Override - public void renderTo(Target target, double baseEle) { + public void renderTo(CommonTarget target, double baseEle) { double chimneyHoleEle = baseEle - 3.0; diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java index 090a2cdd2..de0620a1a 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java @@ -20,7 +20,7 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.JTSTriangulationUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -120,7 +120,7 @@ public Collection getAttachmentSurfaces(double baseEle, int l } @Override - public void renderTo(Target target, double baseEle) { + public void renderTo(CommonTarget target, double baseEle) { /* subtract attached rooftop areas (parking, helipads, pools, etc.) from the roof polygon */ diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java index 55b8cc2eb..5b02a2a8e 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java @@ -18,8 +18,8 @@ import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.PolygonWithHolesXZ; import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.modules.building.BuildingPart; @@ -62,7 +62,7 @@ public Roof(PolygonWithHolesXZ originalPolygon, TagSet tags, double height, Mate /** * returns the attachment surfaces for this roof * - * @param baseEle the lower elevation of the roof, as in {@link #renderTo(Target, double)} + * @param baseEle the lower elevation of the roof, as in {@link #renderTo(CommonTarget, double)} * @param level the roof's level number. This allows distinction between multiple vertically stacked roofs * when attaching objects to the roofs' {@link AttachmentSurface}s. */ @@ -71,10 +71,10 @@ public Collection getAttachmentSurfaces(double baseEle, int l } /** - * renders the roof. The same as {@link Renderable#renderTo(Target)}, + * renders the roof. The same as {@link Renderable#renderTo(CommonTarget)}, * but it also needs the lower elevation of the roof (which is not yet known at construction time). */ - public abstract void renderTo(Target target, double baseEle); + public abstract void renderTo(CommonTarget target, double baseEle); /** * creates the correct roof for the given roof:shape value diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/SpindleRoof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/SpindleRoof.java index b7c628e32..7e83a7cbe 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/SpindleRoof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/SpindleRoof.java @@ -15,7 +15,7 @@ import org.osm2world.core.map_data.data.TagSet; import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.Target; +import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.TextureData; import org.osm2world.core.target.common.material.TextureLayer; @@ -43,7 +43,7 @@ public Collection getInnerSegments(){ } @Override - public void renderTo(Target target, double baseEle) { + public void renderTo(CommonTarget target, double baseEle) { List heights = new ArrayList<>(); List scaleFactors = new ArrayList<>(); @@ -63,7 +63,7 @@ public void renderTo(Target target, double baseEle) { */ abstract protected List> getSpindleSteps(); - private void renderSpindle(Target target, Material material, SimplePolygonXZ polygon, + private void renderSpindle(CommonTarget target, Material material, SimplePolygonXZ polygon, List heights, List scaleFactors) { checkArgument(heights.size() == scaleFactors.size(), "heights and scaleFactors must have same size"); From 03ec17b352f000f9b2c44100cdf2fe8c9ae50eb5 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 7 Nov 2024 12:00:44 +0100 Subject: [PATCH 25/85] Avoid errors if a traffic sign's segment has no Road object --- .../core/world/modules/traffic_sign/TrafficSignModule.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/traffic_sign/TrafficSignModule.java b/src/main/java/org/osm2world/core/world/modules/traffic_sign/TrafficSignModule.java index 9def7d98b..d901bcc1b 100644 --- a/src/main/java/org/osm2world/core/world/modules/traffic_sign/TrafficSignModule.java +++ b/src/main/java/org/osm2world/core/world/modules/traffic_sign/TrafficSignModule.java @@ -676,9 +676,10 @@ private static VectorXZ calculateSignPosition(MapNode node, MapWaySegment segmen } //get the rendered segment's width - Road r = (Road) segment.getPrimaryRepresentation(); - - double roadWidth = r.getWidth(); + double roadWidth = 2; + if (segment.getPrimaryRepresentation() instanceof Road r) { + roadWidth = r.getWidth(); + } //rightNormal() vector will always be orthogonal to the segment, no matter its direction, //so we use that to place the signs to the left/right of the way From bfbf9c1b972c992781bb3ac0da5bd567c6a0da4c Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 7 Nov 2024 17:18:24 +0100 Subject: [PATCH 26/85] Hide indoor geometry below LOD3 --- .../world/data/ProceduralWorldObject.java | 10 ++++++- .../world/modules/building/BuildingPart.java | 8 +++++- .../modules/building/GeometryWindow.java | 28 ++++++++++++++++--- .../world/modules/building/WallElement.java | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java index c1d4091ec..55854376f 100644 --- a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java @@ -30,8 +30,16 @@ class Target implements CommonTarget { private @Nullable LODRange currentLodRange = null; private List currentAttachmentTypes = List.of(); + public @Nullable LODRange getCurrentLodRange() { + return currentLodRange; + } + + public void setCurrentLodRange(@Nullable LODRange lodRange) { + this.currentLodRange = lodRange; + } + public void setCurrentLodRange(LevelOfDetail minLod, LevelOfDetail maxLod) { - this.currentLodRange = new LODRange(minLod, maxLod); + this.setCurrentLodRange(new LODRange(minLod, maxLod)); } public void setCurrentAttachmentTypes(String... attachmentTypes) { diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index bfa970cdb..4ff74656f 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -4,6 +4,8 @@ import static java.util.Arrays.asList; import static java.util.Collections.*; import static org.osm2world.core.math.SimplePolygonXZ.asSimplePolygon; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.LOD3; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.LOD4; import static org.osm2world.core.util.ValueParseUtil.parseColor; import static org.osm2world.core.util.ValueParseUtil.parseLevels; import static org.osm2world.core.util.color.ColorNameDefinitions.CSS_COLORS; @@ -30,6 +32,7 @@ import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; +import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; import org.osm2world.core.world.data.ProceduralWorldObject; @@ -49,6 +52,7 @@ public class BuildingPart implements AreaWorldObject, ProceduralWorldObject { static final double DEFAULT_RIDGE_HEIGHT = 5; + static final LevelOfDetail INDOOR_MIN_LOD = LOD3; final Building building; final MapArea area; @@ -454,7 +458,9 @@ public void buildMeshesAndModels(Target target) { floors.forEach(f -> f.renderTo(target)); - if (buildingPartInterior != null){ + target.setCurrentLodRange(INDOOR_MIN_LOD, LOD4); + + if (buildingPartInterior != null) { buildingPartInterior.buildMeshesAndModels(target); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java index 55a6f699e..a9eb8ea97 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java @@ -11,6 +11,8 @@ import static org.osm2world.core.math.algorithms.TriangulationUtil.triangulate; import static org.osm2world.core.target.common.ExtrudeOption.END_CAP; import static org.osm2world.core.target.common.material.Materials.STEEL; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.LOD0; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.LOD4; import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.STRIP_WALL; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.texCoordLists; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.triangleTexCoordLists; @@ -26,7 +28,9 @@ import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.ExtrudeOption; import org.osm2world.core.target.common.material.Material; +import org.osm2world.core.target.common.mesh.LODRange; import org.osm2world.core.util.enums.LeftRightBoth; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.WindowParameters.RegionProperties; import org.osm2world.core.world.modules.building.WindowParameters.WindowRegion; @@ -255,14 +259,30 @@ public void renderTo(CommonTarget target, WallSurface surface) { /* draw the window pane */ - Material paneMaterial = transparent ? params.transparentWindowMaterial : params.opaqueWindowMaterial; - List paneTrianglesXZ = paneOutline.getTriangulation(); List paneTriangles = paneTrianglesXZ.stream() .map(t -> surface.convertTo3D(t).shift(toBack)) .collect(toList()); - target.drawTriangles(paneMaterial, paneTriangles, - triangleTexCoordLists(paneTriangles, paneMaterial, surface::texCoordFunction)); + + if (transparent) { + LODRange previousLodRange = null; + if (target instanceof ProceduralWorldObject.Target t) { + previousLodRange = t.getCurrentLodRange(); + t.setCurrentLodRange(BuildingPart.INDOOR_MIN_LOD, LOD4); + } + // TODO: intersect previous lod range with target to avoid loosening LOD, e.g. if GeometryWindow is only used at LOD4 + target.drawTriangles(params.transparentWindowMaterial, paneTriangles, + triangleTexCoordLists(paneTriangles, params.transparentWindowMaterial, surface::texCoordFunction)); + if (target instanceof ProceduralWorldObject.Target t) { + t.setCurrentLodRange(LOD0, BuildingPart.INDOOR_MIN_LOD); + t.drawTriangles(params.opaqueWindowMaterial, paneTriangles, + triangleTexCoordLists(paneTriangles, params.opaqueWindowMaterial, surface::texCoordFunction)); + t.setCurrentLodRange(previousLodRange); + } + } else { + target.drawTriangles(params.opaqueWindowMaterial, paneTriangles, + triangleTexCoordLists(paneTriangles, params.opaqueWindowMaterial, surface::texCoordFunction)); + } /* draw outer frame */ diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallElement.java b/src/main/java/org/osm2world/core/world/modules/building/WallElement.java index 1edf4b742..f004add8c 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallElement.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallElement.java @@ -2,6 +2,7 @@ import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.target.CommonTarget; +import org.osm2world.core.world.data.ProceduralWorldObject; /** * something that can be placed into a wall, such as a window or door From 812722a62cf5367af2e03c3199483b5d2eea7228 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 11:35:43 +0100 Subject: [PATCH 27/85] Drop building=entrance support --- .../java/org/osm2world/core/world/modules/building/Wall.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/Wall.java index adaa6b895..e3e8645ef 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Wall.java @@ -257,8 +257,7 @@ public void renderTo(CommonTarget target, boolean renderElements) { buildingPart.levelStructure.level(level).relativeEle - buildingPart.levelStructure.bottomHeight()); - if ((node.getTags().contains("building", "entrance") - || node.getTags().containsKey("entrance") + if ((node.getTags().containsKey("entrance") || node.getTags().containsKey("door"))) { DoorParameters params = DoorParameters.fromTags(node.getTags(), this.tags); From 6765469efea83500d34ea19ef1803408f092e179 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 12:30:37 +0100 Subject: [PATCH 28/85] Choose default window implementation based on LOD --- .../core/world/modules/building/Door.java | 5 + .../core/world/modules/building/Wall.java | 226 +++++++++++------- 2 files changed, 138 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/Door.java b/src/main/java/org/osm2world/core/world/modules/building/Door.java index f4db5ed13..46fbcb905 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Door.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Door.java @@ -10,6 +10,7 @@ import java.util.List; import org.osm2world.core.conversion.ConversionLog; +import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.math.PolygonXYZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXYZ; @@ -102,4 +103,8 @@ public void renderTo(CommonTarget target, WallSurface surface) { } + public static boolean isDoorNode(MapNode n) { + return n.getTags().containsKey("entrance") || n.getTags().containsKey("door"); + } + } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/Wall.java index e3e8645ef..a999eeb06 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Wall.java @@ -9,6 +9,7 @@ import static org.osm2world.core.math.GeometryUtil.insertIntoPolygon; import static org.osm2world.core.math.VectorXZ.NULL_VECTOR; import static org.osm2world.core.math.VectorXZ.listXYZ; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; import static org.osm2world.core.util.ValueParseUtil.parseLevels; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.inheritTags; @@ -16,6 +17,7 @@ import javax.annotation.Nullable; +import org.apache.commons.configuration.Configuration; import org.osm2world.core.conversion.ConversionLog; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapSegment; @@ -25,10 +27,11 @@ import org.osm2world.core.math.shapes.PolylineShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; +import org.osm2world.core.target.common.mesh.LODRange; import org.osm2world.core.world.attachment.AttachmentSurface; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level.LevelType; @@ -37,7 +40,7 @@ import com.google.common.collect.Streams; -public class Wall implements Renderable { +public class Wall { final @Nullable MapWay wallWay; @@ -80,8 +83,7 @@ public Wall(@Nullable MapWay wallWay, BuildingPart buildingPart, List n buildingPart == null ? 0 : buildingPart.levelStructure.bottomHeight()); } - @Override - public void renderTo(CommonTarget target) { + public void renderTo(ProceduralWorldObject.Target target) { renderTo(target, true); } @@ -123,19 +125,6 @@ public void renderTo(CommonTarget target, boolean renderElements) { } - /* use configuration to determine which implementation of window rendering to use */ - - WindowImplementation windowImplementation; - - if (Streams.stream(buildingPart.tags).anyMatch(t -> t.key.startsWith("window"))) { - //explicitly mapped windows, use different (usually higher LOD) setting - windowImplementation = WindowImplementation.getValue( - buildingPart.config.getString("explicitWindowImplementation"), WindowImplementation.FULL_GEOMETRY); - } else { - windowImplementation = WindowImplementation.getValue( - buildingPart.config.getString("implicitWindowImplementation"), WindowImplementation.FLAT_TEXTURES); - } - /* calculate the lower boundary of the wall */ List bottomPoints = listXYZ(points.vertices(), floorEle); @@ -195,120 +184,141 @@ public void renderTo(CommonTarget target, boolean renderElements) { .map(p -> p.xyz(baseEle + heightWithoutRoof + buildingPart.roof.getRoofHeightAt(p))) .collect(toList()); - /* construct the surface(s) */ - - WallSurface mainSurface, roofSurface; + /* use configuration to determine which implementation of window rendering to use */ - double maxHeight = max(topPoints, comparingDouble(v -> v.y)).y - floorEle; + boolean explicitWindows = Streams.stream(buildingPart.tags).anyMatch(t -> t.key.startsWith("window")); - if (windowImplementation != WindowImplementation.FLAT_TEXTURES - || !hasWindows || maxHeight + floorHeight - heightWithoutRoof < 0.01) { + Map windowImplementations = chooseWindowImplementations(explicitWindows, + getNodes().stream().anyMatch(Door::isDoorNode), buildingPart.config); - roofSurface = null; + for (WindowImplementation windowImplementation : windowImplementations.keySet()) { - try { - mainSurface = new WallSurface(material, bottomPoints, topPoints); - } catch (InvalidGeometryException e) { - mainSurface = null; + if (target instanceof ProceduralWorldObject.Target pt) { + pt.setCurrentLodRange(windowImplementations.get(windowImplementation)); } - } else { - - // using window textures. Need to separate the bit of wall "in the roof" which should not have windows. + /* construct the surface(s) */ - double middlePointsHeight = Math.min(heightWithoutRoof - floorHeight, - min(topPoints, comparingDouble(v -> v.y)).y - floorEle); + WallSurface mainSurface, roofSurface; - List middlePoints = asList( - bottomPoints.get(0).addY(middlePointsHeight), - bottomPoints.get(bottomPoints.size() - 1).addY(middlePointsHeight)); + double maxHeight = max(topPoints, comparingDouble(v -> v.y)).y - floorEle; - try { - mainSurface = new WallSurface(material, bottomPoints, middlePoints); - } catch (InvalidGeometryException e) { - mainSurface = null; - } + if (windowImplementation != WindowImplementation.FLAT_TEXTURES + || !hasWindows || maxHeight + floorHeight - heightWithoutRoof < 0.01) { - try { - roofSurface = new WallSurface(material, middlePoints, topPoints); - } catch (InvalidGeometryException e) { roofSurface = null; + + try { + mainSurface = new WallSurface(material, bottomPoints, topPoints); + } catch (InvalidGeometryException e) { + mainSurface = null; + } + + } else { + + // using window textures. Need to separate the bit of wall "in the roof" which should not have windows. + + double middlePointsHeight = Math.min(heightWithoutRoof - floorHeight, + min(topPoints, comparingDouble(v -> v.y)).y - floorEle); + + List middlePoints = asList( + bottomPoints.get(0).addY(middlePointsHeight), + bottomPoints.get(bottomPoints.size() - 1).addY(middlePointsHeight)); + + try { + mainSurface = new WallSurface(material, bottomPoints, middlePoints); + } catch (InvalidGeometryException e) { + mainSurface = null; + } + + try { + roofSurface = new WallSurface(material, middlePoints, topPoints); + } catch (InvalidGeometryException e) { + roofSurface = null; + } + } - } + boolean individuallyMappedWindows = false; - /* add individually mapped doors and windows (if any) */ - //TODO: doors at corners of the building (or boundaries between building:wall=yes ways) do not work yet - //TODO: cannot place doors into roof walls yet + if (windowImplementation != WindowImplementation.NONE) { - boolean individuallyMappedWindows = false; + /* add individually mapped doors and windows (if any) */ + //TODO: doors at corners of the building (or boundaries between building:wall=yes ways) do not work yet + //TODO: cannot place doors into roof walls yet - for (MapNode node : getNodes()) { + for (MapNode node : getNodes()) { - Set levels = new HashSet<>(); - levels.add(min(parseLevels(node.getTags().getValue("level"), singletonList(0)))); - levels.addAll(parseLevels(node.getTags().getValue("repeat_on"), emptyList())); + Set levels = new HashSet<>(); + levels.add(min(parseLevels(node.getTags().getValue("level"), singletonList(0)))); + levels.addAll(parseLevels(node.getTags().getValue("repeat_on"), emptyList())); - for (int level : levels) { + for (int level : levels) { - if (getBuildingPart().levelStructure.hasLevel(level)) { + if (getBuildingPart().levelStructure.hasLevel(level)) { - VectorXZ pos = new VectorXZ(points.offsetOf(node.getPos()), - buildingPart.levelStructure.level(level).relativeEle - - buildingPart.levelStructure.bottomHeight()); + VectorXZ pos = new VectorXZ(points.offsetOf(node.getPos()), + buildingPart.levelStructure.level(level).relativeEle + - buildingPart.levelStructure.bottomHeight()); - if ((node.getTags().containsKey("entrance") - || node.getTags().containsKey("door"))) { + if (Door.isDoorNode(node)) { - DoorParameters params = DoorParameters.fromTags(node.getTags(), this.tags); - mainSurface.addElementIfSpaceFree(new Door(pos, params)); + DoorParameters params = DoorParameters.fromTags(node.getTags(), this.tags); + mainSurface.addElementIfSpaceFree(new Door(pos, params)); - } else if (node.getTags().containsKey("window") - && !node.getTags().contains("window", "no")) { + } else if (node.getTags().containsKey("window") + && !node.getTags().contains("window", "no")) { - boolean transparent = determineWindowTransparency(node, level); + boolean transparent = determineWindowTransparency(node, level); - TagSet windowTags = inheritTags(node.getTags(), tags); - WindowParameters params = new WindowParameters(windowTags, buildingPart.levelStructure.level(level).height); - GeometryWindow window = new GeometryWindow(new VectorXZ(pos.x, pos.z + params.breast), params, transparent); - mainSurface.addElementIfSpaceFree(window); + TagSet windowTags = inheritTags(node.getTags(), tags); + WindowParameters params = new WindowParameters(windowTags, buildingPart.levelStructure.level(level).height); + GeometryWindow window = new GeometryWindow(new VectorXZ(pos.x, pos.z + params.breast), params, transparent); + mainSurface.addElementIfSpaceFree(window); - individuallyMappedWindows = true; + individuallyMappedWindows = true; + } + } + } + } + + if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { + if (!buildingPart.area.getBoundaryNodes().stream().anyMatch(Door::isDoorNode)) { + placeDefaultGarageDoors(mainSurface); } } - } - } - if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { - if (!buildingPart.area.getBoundaryNodes().stream().anyMatch( - n -> n.getTags().containsKey("entrance") || n.getTags().containsKey("door"))) { - placeDefaultGarageDoors(mainSurface); + /* add windows (after doors, because default windows should be displaced by them) */ + + if (hasWindows && !individuallyMappedWindows + && (windowImplementation == WindowImplementation.INSET_TEXTURES + || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { + placeDefaultWindows(mainSurface, windowImplementation); + } + } - } - /* add windows (after doors, because default windows should be displaced by them) */ + /* draw the wall */ - if (hasWindows && !individuallyMappedWindows - && (windowImplementation == WindowImplementation.INSET_TEXTURES - || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { - placeDefaultWindows(mainSurface, windowImplementation); - } + int levelCount = buildingPart.levelStructure.levels(EnumSet.of(LevelType.ABOVEGROUND)).size(); + double windowHeight = (heightWithoutRoof - buildingPart.levelStructure.bottomHeight()) / levelCount; - /* draw the wall */ + if (mainSurface != null) { + mainSurface.renderTo(target, new VectorXZ(0, -floorHeight), + hasWindows && !individuallyMappedWindows + && windowImplementation == WindowImplementation.FLAT_TEXTURES, + windowHeight, renderElements); + } - int levelCount = buildingPart.levelStructure.levels(EnumSet.of(LevelType.ABOVEGROUND)).size(); - double windowHeight = (heightWithoutRoof - buildingPart.levelStructure.bottomHeight()) / levelCount; + if (roofSurface != null) { + roofSurface.renderTo(target, NULL_VECTOR, false, windowHeight, renderElements); + } - if (mainSurface != null) { - mainSurface.renderTo(target, new VectorXZ(0, -floorHeight), - hasWindows && !individuallyMappedWindows - && windowImplementation == WindowImplementation.FLAT_TEXTURES, - windowHeight, renderElements); } - if (roofSurface != null) { - roofSurface.renderTo(target, NULL_VECTOR, false, windowHeight, renderElements); + if (target instanceof ProceduralWorldObject.Target pt) { + pt.setCurrentLodRange(null); } } @@ -355,6 +365,36 @@ List getNodes() { protected PolylineShapeXZ getPoints() { return points; } + private static Map chooseWindowImplementations( + boolean explicitWindows, boolean hasDoor, Configuration config) { + + var explicitWI = WindowImplementation.getValue(config.getString("explicitWindowImplementation"), null); + var implicitWI = WindowImplementation.getValue(config.getString("implicitWindowImplementation"), null); + + if (explicitWindows) { + if (explicitWI != null) { + return Map.of(explicitWI, new LODRange(LOD0, LOD4)); + } else { + return Map.of( + WindowImplementation.NONE, new LODRange(LOD0), + WindowImplementation.FLAT_TEXTURES, new LODRange(LOD1, LOD2), + WindowImplementation.FULL_GEOMETRY, new LODRange(LOD3, LOD4) + ); + } + } else { + if (implicitWI != null) { + return Map.of(implicitWI, new LODRange(LOD0, LOD4)); + } else { + return Map.of( + WindowImplementation.NONE, new LODRange(LOD0, LOD1), + WindowImplementation.FLAT_TEXTURES, new LODRange(LOD2, LOD3), + WindowImplementation.FULL_GEOMETRY, new LODRange(LOD4) + ); + } + } + + } + /** places the default (i.e. not explicitly mapped) windows rows onto a wall surface */ private void placeDefaultWindows(WallSurface surface, WindowImplementation implementation) { From ffd00ad5c7bf36328f2119862e27dccad35f56d7 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Sun, 11 Jun 2023 14:12:20 +0200 Subject: [PATCH 29/85] Increase offset between texture layers to reduce flickering --- src/main/java/org/osm2world/core/target/common/MeshTarget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/target/common/MeshTarget.java b/src/main/java/org/osm2world/core/target/common/MeshTarget.java index 6ff147d33..5975f8856 100644 --- a/src/main/java/org/osm2world/core/target/common/MeshTarget.java +++ b/src/main/java/org/osm2world/core/target/common/MeshTarget.java @@ -180,7 +180,7 @@ public MeshStore apply(MeshStore meshStore) { /** replaces meshes that have multiple layers of textures with multiple meshes, each of which have only one layer */ public static class EmulateTextureLayers implements MeshProcessingStep { - private static final double OFFSET_PER_LAYER = 1e-3; + private static final double OFFSET_PER_LAYER = 5e-2; /** maximum number of layers for which geometry is created, additional ones are omitted */ private final int maxLayers; From 582ca85d73f3003e95091cdbab01aefce5e271ff Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 13:46:51 +0100 Subject: [PATCH 30/85] Remove door inset at lower levels of detail --- .../core/world/modules/building/Door.java | 2 +- .../modules/building/DoorParameters.java | 16 ++++++-- .../core/world/modules/building/Wall.java | 40 ++++++++++++------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/Door.java b/src/main/java/org/osm2world/core/world/modules/building/Door.java index 46fbcb905..d3abfe4a0 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Door.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Door.java @@ -44,7 +44,7 @@ public SimplePolygonXZ outline() { @Override public double insetDistance() { - return 0.10; + return parameters.inset; } @Override diff --git a/src/main/java/org/osm2world/core/world/modules/building/DoorParameters.java b/src/main/java/org/osm2world/core/world/modules/building/DoorParameters.java index b5f880e15..5fa00536d 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/DoorParameters.java +++ b/src/main/java/org/osm2world/core/world/modules/building/DoorParameters.java @@ -3,9 +3,10 @@ import static java.util.Arrays.asList; import static org.osm2world.core.util.ValueParseUtil.parseColor; import static org.osm2world.core.util.color.ColorNameDefinitions.CSS_COLORS; -import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; +import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseHeight; +import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseWidth; -import java.awt.Color; +import java.awt.*; import javax.annotation.Nullable; @@ -24,16 +25,18 @@ public class DoorParameters { public final double width; public final double height; + public final double inset; public final int numberOfWings; private DoorParameters(String type, String materialName, Color color, - double width, double height, int numberOfWings) { + double width, double height, double inset, int numberOfWings) { this.type = type; this.materialName = materialName; this.color = color; this.width = width; this.height = height; + this.inset = inset; this.numberOfWings = numberOfWings; } @@ -74,6 +77,7 @@ public static DoorParameters fromTags(TagSet tags, @Nullable TagSet parentTags) double defaultWidth = 1.0; double defaultHeight = 2.0; + double defaultInset = 0.1; switch (type) { case "overhead": @@ -92,8 +96,12 @@ public static DoorParameters fromTags(TagSet tags, @Nullable TagSet parentTags) /* return the result */ - return new DoorParameters(type, materialName, color, width, height, numberOfWings); + return new DoorParameters(type, materialName, color, width, height, defaultInset, numberOfWings); } + public DoorParameters withInset(double inset) { + return new DoorParameters(type, materialName, color, width, height, inset, numberOfWings); + } + } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/Wall.java index a999eeb06..ef824ba49 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Wall.java @@ -30,6 +30,7 @@ import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.LODRange; +import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; @@ -193,8 +194,10 @@ public void renderTo(CommonTarget target, boolean renderElements) { for (WindowImplementation windowImplementation : windowImplementations.keySet()) { + LODRange lodRange = windowImplementations.get(windowImplementation); + if (target instanceof ProceduralWorldObject.Target pt) { - pt.setCurrentLodRange(windowImplementations.get(windowImplementation)); + pt.setCurrentLodRange(lodRange); } /* construct the surface(s) */ @@ -264,6 +267,10 @@ public void renderTo(CommonTarget target, boolean renderElements) { if (Door.isDoorNode(node)) { DoorParameters params = DoorParameters.fromTags(node.getTags(), this.tags); + if (lodRange.max().ordinal() < 3 + || ConfigUtil.readLOD(buildingPart.config).ordinal() < 3) { + params = params.withInset(0.0); + } mainSurface.addElementIfSpaceFree(new Door(pos, params)); } else if (node.getTags().containsKey("window") @@ -283,20 +290,20 @@ public void renderTo(CommonTarget target, boolean renderElements) { } } - if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { - if (!buildingPart.area.getBoundaryNodes().stream().anyMatch(Door::isDoorNode)) { - placeDefaultGarageDoors(mainSurface); - } - } - - /* add windows (after doors, because default windows should be displaced by them) */ + } - if (hasWindows && !individuallyMappedWindows - && (windowImplementation == WindowImplementation.INSET_TEXTURES - || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { - placeDefaultWindows(mainSurface, windowImplementation); + if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { + if (!buildingPart.area.getBoundaryNodes().stream().anyMatch(Door::isDoorNode)) { + placeDefaultGarageDoors(mainSurface); } + } + + /* add windows (after doors, because default windows should be displaced by them) */ + if (hasWindows && !individuallyMappedWindows + && (windowImplementation == WindowImplementation.INSET_TEXTURES + || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { + placeDefaultWindows(mainSurface, windowImplementation); } /* draw the wall */ @@ -428,13 +435,18 @@ private void placeDefaultWindows(WallSurface surface, WindowImplementation imple private void placeDefaultGarageDoors(WallSurface surface) { TagSet doorTags = TagSet.of("door", "overhead"); + DoorParameters params = DoorParameters.fromTags(doorTags, this.tags); + + if (ConfigUtil.readLOD(buildingPart.config).ordinal() < 3) { + params = params.withInset(0.0); + } - double doorDistance = 1.25 * DoorParameters.fromTags(doorTags, this.tags).width; + double doorDistance = 1.25 * params.width; int numDoors = (int) round(surface.getLength() / doorDistance); for (int i = 0; i < numDoors; i++) { VectorXZ pos = new VectorXZ(surface.getLength() / numDoors * (i + 0.5), 0); - surface.addElementIfSpaceFree(new Door(pos, DoorParameters.fromTags(doorTags, TagSet.of()))); + surface.addElementIfSpaceFree(new Door(pos, params)); } } From d4065fa08e4b4f9451de5993653272e214d0f4c2 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 14:14:19 +0100 Subject: [PATCH 31/85] Avoid default door placement on narrow sides of long garages --- .../java/org/osm2world/core/world/modules/building/Wall.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/Wall.java index ef824ba49..ae06cf336 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Wall.java @@ -294,7 +294,10 @@ public void renderTo(CommonTarget target, boolean renderElements) { if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { if (!buildingPart.area.getBoundaryNodes().stream().anyMatch(Door::isDoorNode)) { - placeDefaultGarageDoors(mainSurface); + if (points.getLength() > buildingPart.area.getOuterPolygon().getOutlineLength() / 8) { + // not the narrow side of a long building with several garages + placeDefaultGarageDoors(mainSurface); + } } } From c997ac2eb942b81be1b6b5b84ff9d3a504a81b7e Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 14:33:32 +0100 Subject: [PATCH 32/85] Avoid loosening LOD when window transparency depends on LOD --- .../core/world/modules/building/GeometryWindow.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java index a9eb8ea97..440270be8 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java @@ -266,13 +266,18 @@ public void renderTo(CommonTarget target, WallSurface surface) { if (transparent) { LODRange previousLodRange = null; + boolean skip = false; if (target instanceof ProceduralWorldObject.Target t) { previousLodRange = t.getCurrentLodRange(); - t.setCurrentLodRange(BuildingPart.INDOOR_MIN_LOD, LOD4); + // intersect with previousLodRange to avoid loosening LOD, e.g. if GeometryWindow is only used at LOD4 + var lodRange = LODRange.intersection(previousLodRange, new LODRange(BuildingPart.INDOOR_MIN_LOD, LOD4)); + t.setCurrentLodRange(lodRange); + skip = (lodRange == null); + } + if (!skip) { + target.drawTriangles(params.transparentWindowMaterial, paneTriangles, + triangleTexCoordLists(paneTriangles, params.transparentWindowMaterial, surface::texCoordFunction)); } - // TODO: intersect previous lod range with target to avoid loosening LOD, e.g. if GeometryWindow is only used at LOD4 - target.drawTriangles(params.transparentWindowMaterial, paneTriangles, - triangleTexCoordLists(paneTriangles, params.transparentWindowMaterial, surface::texCoordFunction)); if (target instanceof ProceduralWorldObject.Target t) { t.setCurrentLodRange(LOD0, BuildingPart.INDOOR_MIN_LOD); t.drawTriangles(params.opaqueWindowMaterial, paneTriangles, From 0aa50d8bcf439e7f96629f9165a37db02f87c6f1 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 14:38:04 +0100 Subject: [PATCH 33/85] Use floating point division for radial window panes --- .../osm2world/core/world/modules/building/GeometryWindow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java index 440270be8..afee9db5e 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java @@ -217,7 +217,7 @@ private static List innerPaneBorderPathsRadial(SimpleClosedShap if (regionBorderSegment == null) { minAngle = Angle.ofDegrees(0); - step = Angle.ofDegrees(360 / panesHorizontal); + step = Angle.ofDegrees(360.0 / panesHorizontal); } else { // TODO: to support other regions than TOP, use regionBorderSegment's direction minAngle = Angle.ofDegrees(270); From 7aca92d4bab842841ef98a9d59a2c2d101d39787 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 15:24:05 +0100 Subject: [PATCH 34/85] Check raw ground footprint even for CONTAIN overlaps --- .../org/osm2world/core/world/data/WorldObject.java | 7 ------- .../core/world/modules/building/Building.java | 14 ++------------ .../core/world/modules/building/BuildingPart.java | 9 +++++++++ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/data/WorldObject.java b/src/main/java/org/osm2world/core/world/data/WorldObject.java index 503e2b9cc..d657fb602 100644 --- a/src/main/java/org/osm2world/core/world/data/WorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/WorldObject.java @@ -11,7 +11,6 @@ import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.map_data.data.overlaps.MapOverlap; -import org.osm2world.core.map_data.data.overlaps.MapOverlapType; import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.GroundState; @@ -176,12 +175,6 @@ default Collection getGroundFootprint() { if (bothOnGround && otherWO.getOverlapPriority() > this.getOverlapPriority()) { - if (overlap.type == MapOverlapType.CONTAIN - && overlap.e1 == getPrimaryMapElement()) { - // completely within other element, no ground area left - return emptyList(); - } - try { subtractPolys.addAll(otherWO.getRawGroundFootprint()); } catch (InvalidGeometryException ignored) { diff --git a/src/main/java/org/osm2world/core/world/modules/building/Building.java b/src/main/java/org/osm2world/core/world/modules/building/Building.java index edc09797c..dabea4de6 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Building.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Building.java @@ -165,18 +165,8 @@ public void buildMeshesAndModels(Target target) { } @Override - public Collection getRawGroundFootprint(){ - Collection shapes = new ArrayList<>(); - - for (BuildingPart part : parts) { - - if (part.levelStructure.bottomHeight() <= 0 && part.getIndoor() != null) { - shapes.add(part.getPolygon()); - } - - } - - return shapes; + public Collection getRawGroundFootprint() { + return List.of(); // BuildingParts return their own footprint if necessary } @Override diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index 4ff74656f..5278a654f 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -497,6 +497,15 @@ public PolygonWithHolesXZ getPolygon() { return polygon; } + @Override + public Collection getRawGroundFootprint(){ + if (levelStructure.bottomHeight() <= 0 && getIndoor() != null) { + return List.of(getPolygon()); + } else { + return emptyList(); + } + } + public Roof getRoof() { return roof; } From 313859f858a42d54076cd8c1e1d2ce148f1ba4a6 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 15:34:08 +0100 Subject: [PATCH 35/85] Rename Wall to ExteriorBuildingWall and Floor to BuildingBottom --- .../{Floor.java => BuildingBottom.java} | 5 ++-- .../world/modules/building/BuildingPart.java | 28 +++++++++---------- .../{Wall.java => ExteriorBuildingWall.java} | 9 ++++-- .../modules/building/indoor/IndoorWall.java | 2 +- .../modules/building/BuildingPartTest.java | 5 ++-- 5 files changed, 27 insertions(+), 22 deletions(-) rename src/main/java/org/osm2world/core/world/modules/building/{Floor.java => BuildingBottom.java} (87%) rename src/main/java/org/osm2world/core/world/modules/building/{Wall.java => ExteriorBuildingWall.java} (98%) diff --git a/src/main/java/org/osm2world/core/world/modules/building/Floor.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java similarity index 87% rename from src/main/java/org/osm2world/core/world/modules/building/Floor.java rename to src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java index 217e0950e..7a5de65cc 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Floor.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java @@ -15,14 +15,15 @@ import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.material.Material; -class Floor implements Renderable { +/** the underside of a {@link BuildingPart} */ +class BuildingBottom implements Renderable { private final BuildingPart buildingPart; private final Material material; private final PolygonWithHolesXZ polygon; private final double floorHeight; - public Floor(BuildingPart buildingPart, Material material, PolygonWithHolesXZ polygon, double floorHeight) { + public BuildingBottom(BuildingPart buildingPart, Material material, PolygonWithHolesXZ polygon, double floorHeight) { this.buildingPart = buildingPart; this.material = material; this.polygon = polygon; diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index 5278a654f..cdfa1261f 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -46,7 +46,7 @@ /** * part of a building, as defined by the Simple 3D Buildings standard. - * Consists of {@link Wall}s, a {@link Roof}, and maybe a {@link Floor}. + * Consists of {@link ExteriorBuildingWall}s, a {@link Roof}, and maybe a {@link BuildingBottom}. * This is the core class of the {@link BuildingModule}. */ public class BuildingPart implements AreaWorldObject, ProceduralWorldObject { @@ -67,8 +67,8 @@ public class BuildingPart implements AreaWorldObject, ProceduralWorldObject { Roof roof; - private List walls = null; - private List floors = null; + private List walls = null; + private List bottoms = null; private final @Nullable BuildingPartInterior buildingPartInterior; @@ -165,16 +165,16 @@ private void createComponents() { walls = splitIntoWalls(area, this); if (floorHeight > 0) { - floors = singletonList(new Floor(this, materialWall, polygon, floorHeight)); + bottoms = singletonList(new BuildingBottom(this, materialWall, polygon, floorHeight)); } else { - floors = emptyList(); + bottoms = emptyList(); } } else { Map polygonFloorHeightMap = new HashMap<>(); - /* construct those polygons where the area does not overlap with terrain boundaries */ + /* construct those polygons where the area does not overlap with the footprint of buildingPassages */ List subtractPolygons = new ArrayList<>(); @@ -268,18 +268,18 @@ private void createComponents() { /* create the walls and floors */ - floors = new ArrayList<>(); + bottoms = new ArrayList<>(); walls = new ArrayList<>(); for (PolygonWithHolesXZ polygon : polygonFloorHeightMap.keySet()) { - floors.add(new Floor(this, materialWall, polygon, polygonFloorHeightMap.get(polygon))); + bottoms.add(new BuildingBottom(this, materialWall, polygon, polygonFloorHeightMap.get(polygon))); for (SimplePolygonXZ ring : polygon.getRings()) { ring = polygon.getOuter().equals(ring) ? ring.makeCounterclockwise() : ring.makeClockwise(); ring = ring.getSimplifiedPolygon(); for (int i = 0; i < ring.size(); i++) { - walls.add(new Wall(null, this, + walls.add(new ExteriorBuildingWall(null, this, asList(ring.getVertex(i), ring.getVertexAfter(i)), emptyMap(), polygonFloorHeightMap.get(polygon))); @@ -340,9 +340,9 @@ private void createComponents() { * @return list of walls, each represented as a list of nodes. * The list of nodes is ordered such that the building part's outside is to the right. */ - static List splitIntoWalls(MapArea buildingPartArea, BuildingPart buildingPart) { + static List splitIntoWalls(MapArea buildingPartArea, BuildingPart buildingPart) { - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (List nodeRing : buildingPartArea.getRings()) { @@ -420,7 +420,7 @@ static List splitIntoWalls(MapArea buildingPartArea, BuildingPart building } } - result.add(new Wall(wallWay, buildingPart, currentWallNodes)); + result.add(new ExteriorBuildingWall(wallWay, buildingPart, currentWallNodes)); } currentWallNodes = new ArrayList<>(); @@ -456,7 +456,7 @@ public void buildMeshesAndModels(Target target) { // TODO don't render floors inside building - floors.forEach(f -> f.renderTo(target)); + bottoms.forEach(f -> f.renderTo(target)); target.setCurrentLodRange(INDOOR_MIN_LOD, LOD4); @@ -486,7 +486,7 @@ public Collection getAttachmentSurfaces() { surfaces.addAll(roof.getAttachmentSurfaces( building.getGroundLevelEle() + levelStructure.heightWithoutRoof(), roofAttachmentLevel)); - for (Wall wall : walls) { + for (ExteriorBuildingWall wall : walls) { surfaces.addAll(wall.getAttachmentSurfaces()); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Wall.java b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java similarity index 98% rename from src/main/java/org/osm2world/core/world/modules/building/Wall.java rename to src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java index ae06cf336..ebb6e14ed 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Wall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java @@ -41,7 +41,10 @@ import com.google.common.collect.Streams; -public class Wall { +/** + * an outer wall of a {@link BuildingPart} + */ +public class ExteriorBuildingWall { final @Nullable MapWay wallWay; @@ -58,7 +61,7 @@ public class Wall { /** the tags for this part, including tags inherited from {@link #buildingPart} and its {@link Building} */ private final TagSet tags; - public Wall(@Nullable MapWay wallWay, BuildingPart buildingPart, List points, + public ExteriorBuildingWall(@Nullable MapWay wallWay, BuildingPart buildingPart, List points, Map pointNodeMap, double floorHeight) { this.wallWay = wallWay; @@ -77,7 +80,7 @@ public Wall(@Nullable MapWay wallWay, BuildingPart buildingPart, List } - public Wall(@Nullable MapWay wallWay, BuildingPart buildingPart, List nodes) { + public ExteriorBuildingWall(@Nullable MapWay wallWay, BuildingPart buildingPart, List nodes) { this(wallWay, buildingPart, nodes.stream().map(MapNode::getPos).collect(toList()), nodes.stream().collect(toMap(MapNode::getPos, n -> n)), diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java index 1d43014b3..a8ef339dc 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java @@ -915,7 +915,7 @@ private void renderTo(CommonTarget target, Boolean renderSides, Boolean attachme if (node.getTags().containsKey("window") && !node.getTags().contains("window", "no")) { - boolean transparent = Wall.determineWindowTransparency(node, level); + boolean transparent = ExteriorBuildingWall.determineWindowTransparency(node, level); TagSet windowTags = inheritTags(node.getTags(), data.getTags()); WindowParameters params = new WindowParameters(windowTags, data.getBuildingPart().levelStructure.level(level).height); diff --git a/src/test/java/org/osm2world/core/world/modules/building/BuildingPartTest.java b/src/test/java/org/osm2world/core/world/modules/building/BuildingPartTest.java index 08af98faf..9189e935a 100644 --- a/src/test/java/org/osm2world/core/world/modules/building/BuildingPartTest.java +++ b/src/test/java/org/osm2world/core/world/modules/building/BuildingPartTest.java @@ -1,7 +1,8 @@ package org.osm2world.core.world.modules.building; import static java.util.Arrays.asList; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.osm2world.core.math.GeometryUtil.closeLoop; import java.util.List; @@ -31,7 +32,7 @@ public void testSplitIntoWalls() { /* test the basic case */ - List result = BuildingPart.splitIntoWalls(buildingPartArea, null); + List result = BuildingPart.splitIntoWalls(buildingPartArea, null); assertEquals(4, result.size()); From 98edc3fbea456b412bd5e1dd0421f3499bb3091a Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 12 Nov 2024 17:30:23 +0100 Subject: [PATCH 36/85] Assign an empty footprint to BollardRow --- .../java/org/osm2world/core/world/modules/BarrierModule.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java index 2367dfe3d..1b3749bbd 100644 --- a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java @@ -963,6 +963,11 @@ public double getWidth() { return 0.15; } + @Override + public Collection getRawGroundFootprint() { + return emptyList(); + } + } public static class ChainRow extends PoleFence{ From 2a76af27eae687b885d5716e346f54d62beeeb4b Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 13 Nov 2024 12:58:33 +0100 Subject: [PATCH 37/85] Ignore overlapping features for bicycle stand placement --- .../core/world/modules/BicycleParkingModule.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java b/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java index 92d9ce94a..9ac355ab2 100644 --- a/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BicycleParkingModule.java @@ -139,11 +139,11 @@ public EleConnectorGroup getEleConnectors() { : equallyDistributePointsAlong(distanceBetweenStands, true,listXYZ(centerline.vertices(), 0)); for (VectorXYZ standLocation : standLocations) { - if (area() == null || area().stream().anyMatch(it -> it.contains(standLocation.xz()))) { - EleConnector connector = new EleConnector(standLocation.xz(), null, getGroundState()); - standEleConnectors.add(connector); - eleConnectors.add(connector); - } + // no longer check for overlaps: would prevent stands without surface=* even on empty terrain, and any stands on sidewalks etc. + // if (area() == null || area().stream().anyMatch(it -> it.contains(standLocation.xz()))) { + EleConnector connector = new EleConnector(standLocation.xz(), null, getGroundState()); + standEleConnectors.add(connector); + eleConnectors.add(connector); } } From fa2fa3ef19856026fea0b20b40335cc9a3574d4c Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 13 Nov 2024 13:28:38 +0100 Subject: [PATCH 38/85] Fix incorrect texcoord list length with golf hole rendering --- src/main/java/org/osm2world/core/target/CommonTarget.java | 3 +++ src/main/java/org/osm2world/core/world/modules/GolfModule.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/target/CommonTarget.java b/src/main/java/org/osm2world/core/target/CommonTarget.java index 8e61dffde..fa6a8eaed 100644 --- a/src/main/java/org/osm2world/core/target/CommonTarget.java +++ b/src/main/java/org/osm2world/core/target/CommonTarget.java @@ -94,6 +94,9 @@ default void drawConvexPolygon(@Nonnull Material material, @Nonnull List> texCoordLists) { if (Objects.equals(vs.get(0), vs.get(vs.size() - 1))) { vs = vs.subList(0, vs.size() - 1); + texCoordLists = texCoordLists.stream() + .map(tcl -> tcl.subList(0, tcl.size() - 1)) + .toList(); } drawTriangleFan(material, vs, texCoordLists); } diff --git a/src/main/java/org/osm2world/core/world/modules/GolfModule.java b/src/main/java/org/osm2world/core/world/modules/GolfModule.java index df4c5c14f..af2373bb4 100644 --- a/src/main/java/org/osm2world/core/world/modules/GolfModule.java +++ b/src/main/java/org/osm2world/core/world/modules/GolfModule.java @@ -309,7 +309,7 @@ private static void drawPin(Target target, VectorXZ pos, List upperHo texCoordLists(vs, groundMaterial, STRIP_WALL)); target.drawConvexPolygon(groundMaterial, lowerHoleRing, - texCoordLists(vs, groundMaterial, GLOBAL_X_Z)); + texCoordLists(lowerHoleRing, groundMaterial, GLOBAL_X_Z)); /* draw pole and flag */ From 4cbb35ab5cae662381c02daaf73485f007738b84 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 13 Nov 2024 13:36:22 +0100 Subject: [PATCH 39/85] Assign LOD to golf features --- .../osm2world/core/world/modules/GolfModule.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/osm2world/core/world/modules/GolfModule.java b/src/main/java/org/osm2world/core/world/modules/GolfModule.java index af2373bb4..3160ec46b 100644 --- a/src/main/java/org/osm2world/core/world/modules/GolfModule.java +++ b/src/main/java/org/osm2world/core/world/modules/GolfModule.java @@ -10,6 +10,7 @@ import static org.osm2world.core.math.algorithms.TriangulationUtil.triangulationXZtoXYZ; import static org.osm2world.core.target.common.material.Materials.PLASTIC; import static org.osm2world.core.target.common.material.Materials.SAND; +import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.GLOBAL_X_Z; import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.STRIP_WALL; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.texCoordLists; @@ -113,12 +114,22 @@ public Collection getRawGroundFootprint() { @Override public void buildMeshesAndModels(Target target) { + /* triangulate the bunker's area normally at low LOD */ + + target.setCurrentLodRange(LOD0, LOD1); + + List basicTriangulation = getTriangulation(); + target.drawTriangles(SAND, basicTriangulation, + triangleTexCoordLists(basicTriangulation, SAND, GLOBAL_X_Z)); + /* draw the bunker as a depression by shrinking the outline polygon and lowering it at each step. * * The first step gets special handling and is primarily intended for bunkers in uneven terrain. * It involves an almost vertical drop towards the lowest point of the bunker outline * that is textured with ground, not sand. */ + target.setCurrentLodRange(LOD2, LOD4); + List resultingTriangulation = new ArrayList<>(); double[] dropSteps = {-0.03, -0.07, -0.05, -0.02}; @@ -279,6 +290,8 @@ public void buildMeshesAndModels(Target target) { /* render pin */ + target.setCurrentLodRange(LOD3, LOD4); + PolygonXYZ upperHoleRing = pinConnectors.getPosXYZ(pinHoleLoop); drawPin(target, pinPosition, upperHoleRing.vertices()); From 47c46be791a65ee280e7744ef319f7e32a5635a2 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 13 Nov 2024 14:32:40 +0100 Subject: [PATCH 40/85] Remove pointless color multiplication in MoveColorsToVertices --- .../java/org/osm2world/core/target/common/MeshTarget.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/MeshTarget.java b/src/main/java/org/osm2world/core/target/common/MeshTarget.java index 5975f8856..bac8fa4c0 100644 --- a/src/main/java/org/osm2world/core/target/common/MeshTarget.java +++ b/src/main/java/org/osm2world/core/target/common/MeshTarget.java @@ -264,10 +264,7 @@ public MeshStore apply(MeshStore meshStore) { if (mesh.geometry instanceof TriangleGeometry tg) { List colors = (tg.colors != null) ? tg.colors - : new ArrayList<>(nCopies(tg.vertices().size(), WHITE)); - for (int i = 0; i < colors.size(); i++) { - colors.set(i, LColor.fromAWT(colors.get(i)).multiply(mesh.material.getLColor()).toAWT()); - } + : nCopies(tg.vertices().size(), mesh.material.getColor()); TriangleGeometry.Builder builder = new TriangleGeometry.Builder(tg.texCoords.size(), null, null); builder.addTriangles(tg.triangles, tg.texCoords, colors, tg.normalData.normals()); From ad02053c845a79275cc08e6078d6429529857f50 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 13 Nov 2024 14:45:36 +0100 Subject: [PATCH 41/85] Correctly pass blue color component to glColor in JOGLTarget --- .../org/osm2world/core/target/jogl/JOGLTargetFixedFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/target/jogl/JOGLTargetFixedFunction.java b/src/main/java/org/osm2world/core/target/jogl/JOGLTargetFixedFunction.java index d18cbbe08..86f71530e 100644 --- a/src/main/java/org/osm2world/core/target/jogl/JOGLTargetFixedFunction.java +++ b/src/main/java/org/osm2world/core/target/jogl/JOGLTargetFixedFunction.java @@ -355,7 +355,7 @@ static final void setMaterial(GL2 gl, Material material, } //TODO: glMaterialfv could be redundant if color was used for ambient and diffuse - gl.glColor3f(c.getRed()/255f, c.getGreen()/255f, c.getBlue()); + gl.glColor3f(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f); gl.glMaterialfv(GL_FRONT, GL_AMBIENT, getFloatBuffer(multiplyColor(c, AMBIENT_FACTOR))); From b0bee428491982bdcdc3067a5e445ed8f616cc53 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 12:50:46 +0100 Subject: [PATCH 42/85] Remove POVRay-specific code for trees --- .../core/target/povray/POVRayWriter.java | 10 -- .../target/povray/RenderableToPOVRay.java | 13 --- .../core/world/modules/TreeModule.java | 103 ++---------------- 3 files changed, 11 insertions(+), 115 deletions(-) delete mode 100644 src/main/java/org/osm2world/core/target/povray/RenderableToPOVRay.java diff --git a/src/main/java/org/osm2world/core/target/povray/POVRayWriter.java b/src/main/java/org/osm2world/core/target/povray/POVRayWriter.java index 26f79c861..abed89a50 100644 --- a/src/main/java/org/osm2world/core/target/povray/POVRayWriter.java +++ b/src/main/java/org/osm2world/core/target/povray/POVRayWriter.java @@ -7,13 +7,11 @@ import org.osm2world.core.GlobalValues; import org.osm2world.core.map_data.data.MapData; -import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.target.TargetUtil; import org.osm2world.core.target.common.lighting.GlobalLightingParameters; import org.osm2world.core.target.common.rendering.Camera; import org.osm2world.core.target.common.rendering.Projection; -import org.osm2world.core.world.data.WorldObject; /** * utility class for creating a POVRay file @@ -70,14 +68,6 @@ private static final void writePOVInstructionStringToStream( target.appendMaterialDefinitions(); - for (MapElement element : mapData.getMapElements()) { - for (WorldObject r : element.getRepresentations()) { - if (r instanceof RenderableToPOVRay) { - ((RenderableToPOVRay)r).addDeclarationsTo(target); - } - } - } - //TODO get terrain boundary elsewhere // if (terrain != null) { // diff --git a/src/main/java/org/osm2world/core/target/povray/RenderableToPOVRay.java b/src/main/java/org/osm2world/core/target/povray/RenderableToPOVRay.java deleted file mode 100644 index cd20b638d..000000000 --- a/src/main/java/org/osm2world/core/target/povray/RenderableToPOVRay.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.osm2world.core.target.povray; - -import org.osm2world.core.target.Renderable; - -public interface RenderableToPOVRay extends Renderable { - - /** - * adds any global declarations that may be necessary. - * This is called before the renderTo calls. - */ - public void addDeclarationsTo(POVRayTarget target); - -} diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index c62d4dc18..672cd78b2 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -32,8 +32,6 @@ import org.osm2world.core.target.common.model.InstanceParameters; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; -import org.osm2world.core.target.povray.POVRayTarget; -import org.osm2world.core.target.povray.RenderableToPOVRay; import org.osm2world.core.world.data.*; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleBillboardUtil; @@ -195,56 +193,6 @@ private double getTreeHeight(MapElement element, } - private POVRayTarget previousDeclarationTarget = null; - - private void addTreeDeclarationsTo(POVRayTarget target) { - if (target != previousDeclarationTarget) { - - //TODO support any combination of leaf type and leaf cycle - - previousDeclarationTarget = target; - - target.append("#ifndef (broad_leaved_tree)\n"); - target.append("#declare broad_leaved_tree = object { union {\n"); - target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.BROADLEAVED, LeafCycle.DECIDUOUS, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0))); - target.append("} }\n#end\n\n"); - - target.append("#ifndef (coniferous_tree)\n"); - target.append("#declare coniferous_tree = object { union {\n"); - target.drawModel(new ModelInstance(new TreeGeometryModel(LeafType.NEEDLELEAVED, LeafCycle.EVERGREEN, null), - new InstanceParameters(VectorXYZ.NULL_VECTOR, 0, 1.0))); - target.append("} }\n#end\n\n"); - - } - } - - private void renderTreePovrayModel(POVRayTarget target, MapElement element, VectorXYZ pos, - LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { - - boolean isConiferousTree = (leafType == LeafType.NEEDLELEAVED); - - double height = getTreeHeight(element, isConiferousTree, false); - - //rotate randomly for variation - float yRotation = (float) Math.random() * 360; - - //add union of stem and leaves - if (isConiferousTree) { - target.append("object { coniferous_tree rotate "); - } else { - target.append("object { broad_leaved_tree rotate "); - } - - target.append(Float.toString(yRotation)); - target.append("*y scale "); - target.append(height); - target.append(" translate "); - target.appendVector(pos.x, 0, pos.z); - target.append(" }\n"); - - } - private void renderTreeModel(CommonTarget target, MapElement element, VectorXYZ base, LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { @@ -360,7 +308,7 @@ public List buildMeshes(InstanceParameters params) { private final List existingModels = new ArrayList<>(); - public class Tree extends NoOutlineNodeWorldObject implements RenderableToPOVRay, LegacyWorldObject { + public class Tree extends NoOutlineNodeWorldObject implements LegacyWorldObject { private final LeafType leafType; private final LeafCycle leafCycle; @@ -395,21 +343,12 @@ public GroundState getGroundState() { @Override public void renderTo(CommonTarget target) { - if (target instanceof POVRayTarget) { - renderTreePovrayModel((POVRayTarget)target, node, getBase(), leafType, leafCycle, species); - } else { - renderTreeModel(target, node, getBase(), leafType, leafCycle, species); - } - } - - @Override - public void addDeclarationsTo(POVRayTarget target) { - addTreeDeclarationsTo(target); + renderTreeModel(target, node, getBase(), leafType, leafCycle, species); } } - public class TreeRow implements WaySegmentWorldObject, RenderableToPOVRay, LegacyWorldObject { + public class TreeRow implements WaySegmentWorldObject, LegacyWorldObject { private final MapWaySegment segment; @@ -481,26 +420,16 @@ public GroundState getGroundState() { return GroundState.ON; } - @Override - public void addDeclarationsTo(POVRayTarget target) { - addTreeDeclarationsTo(target); - } - @Override public void renderTo(CommonTarget target) { for (EleConnector treeConnector : treeConnectors) { - if (target instanceof POVRayTarget) { - renderTreePovrayModel((POVRayTarget)target, segment, treeConnector.getPosXYZ(), - leafType, leafCycle, species); - } else { - renderTreeModel(target, segment, treeConnector.getPosXYZ(), - leafType, leafCycle, species); - } + renderTreeModel(target, segment, treeConnector.getPosXYZ(), + leafType, leafCycle, species); - if (target instanceof FaceTarget) { - ((FaceTarget)target).flushReconstructedFaces(); + if (target instanceof FaceTarget ft) { + ft.flushReconstructedFaces(); } } @@ -512,7 +441,7 @@ public void renderTo(CommonTarget target) { } - public class Forest implements AreaWorldObject, RenderableToPOVRay, LegacyWorldObject { + public class Forest implements AreaWorldObject, LegacyWorldObject { private final MapArea area; private final MapData mapData; @@ -590,26 +519,16 @@ public GroundState getGroundState() { return GroundState.ON; } - @Override - public void addDeclarationsTo(POVRayTarget target) { - addTreeDeclarationsTo(target); - } - @Override public void renderTo(CommonTarget target) { for (EleConnector treeConnector : treeConnectors) { - if (target instanceof POVRayTarget) { - renderTreePovrayModel((POVRayTarget)target, area, treeConnector.getPosXYZ(), + renderTreeModel(target, area, treeConnector.getPosXYZ(), leafType, leafCycle, species); - } else { - renderTreeModel(target, area, treeConnector.getPosXYZ(), - leafType, leafCycle, species); - } - if (target instanceof FaceTarget) { - ((FaceTarget)target).flushReconstructedFaces(); + if (target instanceof FaceTarget ft) { + ft.flushReconstructedFaces(); } } From 50830e87ab130d9b10406f2bdb7ca22aa73a3e42 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 14:39:52 +0100 Subject: [PATCH 43/85] Port TreeModule from LegacyWorldObject to ProceduralWorldObject --- .../core/world/modules/TreeModule.java | 81 +++++++------------ 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index 672cd78b2..63b84b43f 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -6,10 +6,7 @@ import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.filterWorldObjectCollisions; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseHeight; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.util.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -17,14 +14,11 @@ import org.apache.commons.configuration.Configuration; import org.osm2world.core.map_data.data.*; import org.osm2world.core.map_data.data.overlaps.MapOverlap; -import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.GeometryUtil; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.common.FaceTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.ExtrusionGeometry; @@ -193,23 +187,23 @@ private double getTreeHeight(MapElement element, } - private void renderTreeModel(CommonTarget target, MapElement element, VectorXYZ base, - LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { + /** + * retrieves a suitable {@link TreeModel} from {@link #existingModels}, or creates it if necessary + * + * @param seed an object to be used as the seed for random decisions + */ + private TreeModel getTreeModel(VectorXYZ seed, LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { + + var r = new Random((long)(seed.getX() * 10) + (long)(seed.getZ() * 10000)); - // "random" decision to flip the tree texture based on z coord - boolean mirrored = (long)(base.getZ() * 1000) % 2 == 0; + // "random" decision to flip the tree texture + boolean mirrored = r.nextBoolean(); - // if leaf type is unknown, make another "random" decision based on x coord + // if leaf type is unknown, make another random decision if (leafType == null) { - if ((long)(base.getX() * 1000) % 2 == 0) { - leafType = LeafType.NEEDLELEAVED; - } else { - leafType = LeafType.BROADLEAVED; - } + leafType = r.nextBoolean() ? LeafType.NEEDLELEAVED : LeafType.BROADLEAVED; } - double height = getTreeHeight(element, leafType == LeafType.NEEDLELEAVED, species != null); - TreeModel model = null; for (TreeModel existingModel : existingModels) { @@ -230,8 +224,7 @@ private void renderTreeModel(CommonTarget target, MapElement element, VectorXYZ existingModels.add(model); } - target.drawModel(new ModelInstance(model, - new InstanceParameters(base, 0, height))); + return model; } @@ -308,7 +301,7 @@ public List buildMeshes(InstanceParameters params) { private final List existingModels = new ArrayList<>(); - public class Tree extends NoOutlineNodeWorldObject implements LegacyWorldObject { + public class Tree extends NoOutlineNodeWorldObject implements ProceduralWorldObject { private final LeafType leafType; private final LeafCycle leafCycle; @@ -342,13 +335,15 @@ public GroundState getGroundState() { } @Override - public void renderTo(CommonTarget target) { - renderTreeModel(target, node, getBase(), leafType, leafCycle, species); + public void buildMeshesAndModels(Target target) { + TreeModel treeModel = getTreeModel(getBase(), leafType, leafCycle, species); + double height = getTreeHeight(node, leafType == LeafType.NEEDLELEAVED, species != null); + target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(getBase(), 0, height))); } } - public class TreeRow implements WaySegmentWorldObject, LegacyWorldObject { + public class TreeRow implements WaySegmentWorldObject, ProceduralWorldObject { private final MapWaySegment segment; @@ -412,26 +407,19 @@ public Iterable getEleConnectors() { return treeConnectors; } - @Override - public void defineEleConstraints(EleConstraintEnforcer enforcer) {} - @Override public GroundState getGroundState() { return GroundState.ON; } @Override - public void renderTo(CommonTarget target) { + public void buildMeshesAndModels(Target target) { for (EleConnector treeConnector : treeConnectors) { - - renderTreeModel(target, segment, treeConnector.getPosXYZ(), - leafType, leafCycle, species); - - if (target instanceof FaceTarget ft) { - ft.flushReconstructedFaces(); - } - + VectorXYZ pos = treeConnector.getPosXYZ(); + TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species); + double height = getTreeHeight(segment, leafType == LeafType.NEEDLELEAVED, species != null); + target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, height))); } } @@ -441,7 +429,7 @@ public void renderTo(CommonTarget target) { } - public class Forest implements AreaWorldObject, LegacyWorldObject { + public class Forest implements AreaWorldObject, ProceduralWorldObject { private final MapArea area; private final MapData mapData; @@ -511,26 +499,19 @@ public Iterable getEleConnectors() { } - @Override - public void defineEleConstraints(EleConstraintEnforcer enforcer) {} - @Override public GroundState getGroundState() { return GroundState.ON; } @Override - public void renderTo(CommonTarget target) { + public void buildMeshesAndModels(Target target) { for (EleConnector treeConnector : treeConnectors) { - - renderTreeModel(target, area, treeConnector.getPosXYZ(), - leafType, leafCycle, species); - - if (target instanceof FaceTarget ft) { - ft.flushReconstructedFaces(); - } - + VectorXYZ pos = treeConnector.getPosXYZ(); + TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species); + double height = getTreeHeight(area, leafType == LeafType.NEEDLELEAVED, species != null); + target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, height))); } } From 5aaf69a6200132ba1516658a084802c54970cf23 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 14:42:10 +0100 Subject: [PATCH 44/85] Abolish LegacyWorldObject --- .../org/osm2world/core/target/TargetUtil.java | 9 ++--- .../core/world/data/LegacyWorldObject.java | 33 ------------------- 2 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/org/osm2world/core/world/data/LegacyWorldObject.java diff --git a/src/main/java/org/osm2world/core/target/TargetUtil.java b/src/main/java/org/osm2world/core/target/TargetUtil.java index fefe3da29..d4ae095ed 100644 --- a/src/main/java/org/osm2world/core/target/TargetUtil.java +++ b/src/main/java/org/osm2world/core/target/TargetUtil.java @@ -21,7 +21,6 @@ import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.statistics.StatisticsTarget; import org.osm2world.core.util.functions.CheckedConsumer; -import org.osm2world.core.world.data.LegacyWorldObject; import org.osm2world.core.world.data.WorldObject; import com.google.gson.JsonIOException; @@ -85,12 +84,8 @@ public static void renderWorldObjects(Iterator targetIterator, */ public static final void renderObject(Target target, WorldObject object) { target.beginObject(object); - if (object instanceof LegacyWorldObject) { - ((LegacyWorldObject)object).renderTo(target); - } else { - object.buildMeshes().forEach(target::drawMesh); - object.getSubModels().forEach(it -> it.render(target)); - } + object.buildMeshes().forEach(target::drawMesh); + object.getSubModels().forEach(it -> it.render(target)); } public static final List> flipTexCoordsVertically(List> texCoordLists) { diff --git a/src/main/java/org/osm2world/core/world/data/LegacyWorldObject.java b/src/main/java/org/osm2world/core/world/data/LegacyWorldObject.java deleted file mode 100644 index 99fe9e668..000000000 --- a/src/main/java/org/osm2world/core/world/data/LegacyWorldObject.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.osm2world.core.world.data; - -import java.util.List; - -import org.apache.commons.lang3.tuple.Pair; -import org.osm2world.core.target.Renderable; -import org.osm2world.core.target.Target; -import org.osm2world.core.target.common.MeshTarget; -import org.osm2world.core.target.common.mesh.LevelOfDetail; -import org.osm2world.core.target.common.mesh.Mesh; - -/** - * a {@link WorldObject} that still uses "draw" methods of {@link Target} - * instead of the new {@link #buildMeshes()} method. - * This exists to smooth the transition. - */ -public interface LegacyWorldObject extends WorldObject, Renderable { - - @Override - default List buildMeshes() { - MeshTarget meshTarget = new MeshTarget(); - renderTo(meshTarget); - return meshTarget.getMeshes().stream() - .map(mesh -> new Mesh(mesh.geometry, mesh.material, getLodRange().getLeft(), getLodRange().getRight())) - .toList(); - } - - /** returns a pair of min and max LOD for this {@link LegacyWorldObject}'s implicit {@link Mesh}es */ - default Pair getLodRange() { - return Pair.of(LevelOfDetail.LOD0, LevelOfDetail.LOD4); - } - -} From d4ae59da321abd12409790940ea883ac56974a09 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 14:49:29 +0100 Subject: [PATCH 45/85] Abolish the Renderable interface --- .../org/osm2world/core/target/Renderable.java | 15 --------------- .../osm2world/core/world/data/WorldObject.java | 3 +-- .../world/modules/building/BuildingBottom.java | 4 +--- .../world/modules/building/indoor/IndoorWall.java | 4 +--- .../core/world/modules/building/roof/Roof.java | 4 +--- 5 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/org/osm2world/core/target/Renderable.java diff --git a/src/main/java/org/osm2world/core/target/Renderable.java b/src/main/java/org/osm2world/core/target/Renderable.java deleted file mode 100644 index 72b36b565..000000000 --- a/src/main/java/org/osm2world/core/target/Renderable.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.osm2world.core.target; - -/** - * object that can be rendered/exported to a {@link Target} - */ -public interface Renderable { - - /** - * outputs the 3D geometry. - * Most objects will use the same code for all {@link Target} implementations, - * but some may use special-case handling with instanceof checks. - */ - public void renderTo(CommonTarget target); - -} diff --git a/src/main/java/org/osm2world/core/world/data/WorldObject.java b/src/main/java/org/osm2world/core/world/data/WorldObject.java index d657fb602..5ade23776 100644 --- a/src/main/java/org/osm2world/core/world/data/WorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/WorldObject.java @@ -18,7 +18,6 @@ import org.osm2world.core.math.algorithms.CAGUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.SimplePolygonShapeXZ; -import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.ModelInstance; @@ -51,7 +50,7 @@ public default List buildMeshesForModelHierarchy() { /** * returns another world object this is part of, if any (e.g. a room is part of a building). * Parents are responsible for rendering their children, so only root objects (those returning null here) - * will have their {@link Renderable#renderTo(org.osm2world.core.target.Target)} methods called. + * will have their {@link #buildMeshes()} and {@link #getSubModels()} methods called directly. */ public default @Nullable WorldObject getParent() { return null; } diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java index 7a5de65cc..e15316e52 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingBottom.java @@ -12,11 +12,10 @@ import org.osm2world.core.math.TriangleXZ; import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.material.Material; /** the underside of a {@link BuildingPart} */ -class BuildingBottom implements Renderable { +class BuildingBottom { private final BuildingPart buildingPart; private final Material material; @@ -30,7 +29,6 @@ public BuildingBottom(BuildingPart buildingPart, Material material, PolygonWithH this.floorHeight = floorHeight; } - @Override public void renderTo(CommonTarget target) { double floorEle = buildingPart.building.getGroundLevelEle() + floorHeight - 0.01; diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java index a8ef339dc..3bdd1bb2b 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java @@ -19,7 +19,6 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -28,7 +27,7 @@ import com.google.common.collect.Sets; -public class IndoorWall implements Renderable { +public class IndoorWall { private final double straightnessTolerance = 0.001; private final double wallThickness = 0.1; @@ -961,7 +960,6 @@ private void renderTo(CommonTarget target, Boolean renderSides, Boolean attachme } } - @Override public void renderTo(CommonTarget target) { renderTo(target, true, false); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java index 5b02a2a8e..cc34e454f 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/Roof.java @@ -19,7 +19,6 @@ import org.osm2world.core.math.PolygonWithHolesXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.CommonTarget; -import org.osm2world.core.target.Renderable; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.modules.building.BuildingPart; @@ -71,8 +70,7 @@ public Collection getAttachmentSurfaces(double baseEle, int l } /** - * renders the roof. The same as {@link Renderable#renderTo(CommonTarget)}, - * but it also needs the lower elevation of the roof (which is not yet known at construction time). + * renders the roof. Needs the lower elevation of the roof (which is not yet known at construction time). */ public abstract void renderTo(CommonTarget target, double baseEle); From 96cd6709985b89bd731f904c3fdedc781efb09df Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 16:56:08 +0100 Subject: [PATCH 46/85] Offer easy access to a texture's aspect ratio --- .../core/target/common/material/BlankTexture.java | 8 +++++++- .../core/target/common/material/TextureAtlas.java | 13 +++++++++++-- .../core/target/common/material/TextureData.java | 5 +++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/BlankTexture.java b/src/main/java/org/osm2world/core/target/common/material/BlankTexture.java index a1fc02f51..d7dc64a36 100644 --- a/src/main/java/org/osm2world/core/target/common/material/BlankTexture.java +++ b/src/main/java/org/osm2world/core/target/common/material/BlankTexture.java @@ -9,6 +9,7 @@ public final class BlankTexture extends RuntimeTexture { public static final BlankTexture INSTANCE = new BlankTexture(); + private static final Resolution RESOLUTION = new Resolution(128, 128); private BlankTexture(TextureDataDimensions dimensions) { super(dimensions, Wrap.REPEAT, NamedTexCoordFunction.GLOBAL_X_Z); @@ -25,7 +26,12 @@ protected BufferedImage createBufferedImage(Resolution resolution) { @Override protected BufferedImage createBufferedImage() { - return getBufferedImage(new Resolution(128, 128)); + return getBufferedImage(RESOLUTION); + } + + @Override + public float getAspectRatio() { + return RESOLUTION.getAspectRatio(); } @Override diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureAtlas.java b/src/main/java/org/osm2world/core/target/common/material/TextureAtlas.java index 4eaf34696..860263be8 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureAtlas.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureAtlas.java @@ -51,8 +51,7 @@ public TextureAtlas(List textures) { @Override protected BufferedImage createBufferedImage() { - BufferedImage result = new BufferedImage(numTexturesX * TEXTURE_RESOLUTION.width, - numTexturesZ * TEXTURE_RESOLUTION.height, BufferedImage.TYPE_INT_ARGB); + BufferedImage result = new BufferedImage(getResolution().width, getResolution().height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = result.createGraphics(); @@ -98,6 +97,16 @@ VectorXZ mapTexCoord(TextureData texture, VectorXZ texCoord) { } + private Resolution getResolution() { + return new Resolution(numTexturesX * TEXTURE_RESOLUTION.width, + numTexturesZ * TEXTURE_RESOLUTION.height); + } + + @Override + public float getAspectRatio() { + return getResolution().getAspectRatio(); + } + @Override public String toString() { return "TextureAtlas " + textures; diff --git a/src/main/java/org/osm2world/core/target/common/material/TextureData.java b/src/main/java/org/osm2world/core/target/common/material/TextureData.java index 599306078..f71351525 100644 --- a/src/main/java/org/osm2world/core/target/common/material/TextureData.java +++ b/src/main/java/org/osm2world/core/target/common/material/TextureData.java @@ -176,6 +176,11 @@ public void writeRasterImageToStream(OutputStream stream) throws IOException { writeRasterImageToStream(stream, 0.75f); } + /** returns this texture's aspect ratio (same definition as {@link Resolution#getAspectRatio()}) */ + public float getAspectRatio() { + return Resolution.of(getBufferedImage()).getAspectRatio(); + } + /** averages the color values (in linear color space) */ public LColor getAverageColor() { From 73c61adcb4bef33d731c5d8424fabd75e69c78c3 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 14 Nov 2024 17:07:54 +0100 Subject: [PATCH 47/85] Support crown and trunk diameter/circumference for trees --- .../core/world/modules/TreeModule.java | 198 ++++++++++++------ 1 file changed, 136 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index 63b84b43f..c3fc3942a 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -1,10 +1,11 @@ package org.osm2world.core.world.modules; +import static java.lang.Math.PI; import static java.util.Arrays.asList; import static org.osm2world.core.target.common.material.Materials.TREE_CROWN; import static org.osm2world.core.target.common.material.Materials.TREE_TRUNK; +import static org.osm2world.core.util.ValueParseUtil.parseMeasure; import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.filterWorldObjectCollisions; -import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseHeight; import java.util.*; @@ -17,10 +18,10 @@ import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.GeometryUtil; +import org.osm2world.core.math.Vector3D; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.target.common.material.Material; -import org.osm2world.core.target.common.material.Materials; +import org.osm2world.core.target.common.material.*; import org.osm2world.core.target.common.mesh.ExtrusionGeometry; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.InstanceParameters; @@ -29,6 +30,7 @@ import org.osm2world.core.world.data.*; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleBillboardUtil; +import org.osm2world.core.world.modules.common.WorldModuleParseUtil; /** * adds trees, tree rows, tree groups and forests to the world @@ -121,6 +123,63 @@ public static TreeSpecies getValue(TagSet tags) { } + /** + * @param height tree height in meters + * @param crownDiameter diameter of the tree's crown in meters + * @param trunkDiameter diameter of the tree's trunk (at breast height) in meters, or null if unknown + */ + private record TreeDimensions(double height, double crownDiameter, @Nullable Double trunkDiameter) { + + /** + * parse height and other dimensions (optionally modified by some random factor for forests) + * + * @param random randomness generator to slightly vary the values, can be set to null if no randomness is desired + * @param model used to get default ratios between dimensions, such as height to width. Optional. + */ + public static TreeDimensions fromTags(TagSet tags, @Nullable Random random, @Nullable TreeModel model, + double defaultHeight) { + + double defaultHeightToWidth = model != null ? model.defaultHeightToWidth() : 2; + double defaultCrownToTrunk = 30; + + double scaleFactor = random != null ? 0.5 + 0.75 * random.nextDouble() : 1.0; + + Double trunkDiameter = parseMeasure(tags.getValue("diameter")); + + if (trunkDiameter == null) { + Double trunkCircumference = parseMeasure(tags.getValue("circumference")); + if (trunkCircumference != null) { + trunkDiameter = trunkCircumference / PI; + } + } + + Double crownDiameter = parseMeasure(tags.getValue("diameter_crown")); + Double height = parseMeasure(tags.getValue("height")); + + if (height == null) { + height = parseMeasure(tags.getValue("est_height")); + if (height == null) { + if (crownDiameter != null) { + height = crownDiameter * defaultHeightToWidth; + } else if (trunkDiameter != null) { + height = trunkDiameter * defaultCrownToTrunk * defaultHeightToWidth; + } else { + height = defaultHeight; + } + } + } + + if (crownDiameter == null) { + crownDiameter = height / defaultHeightToWidth; + } + + return new TreeDimensions(scaleFactor * height,scaleFactor * crownDiameter, + trunkDiameter != null ? scaleFactor * trunkDiameter : null); + + } + + } + private boolean useBillboards = false; private double defaultTreeHeight = 10; private double defaultTreeHeightForest = 20; @@ -164,35 +223,13 @@ public final void applyTo(MapData mapData) { } - private static final float TREE_RADIUS_PER_HEIGHT = 0.2f; - - /** - * parse height (for forests, add some random factor) - */ - private double getTreeHeight(MapElement element, - boolean isConiferousTree, boolean isFruitTree) { - - float heightFactor = 1; - if (element instanceof MapArea) { - heightFactor = 0.5f + 0.75f * (float)Math.random(); - } - - double defaultHeight = defaultTreeHeight; - if (element instanceof MapArea && !isFruitTree) { - defaultHeight = defaultTreeHeightForest; - } - - return heightFactor * - parseHeight(element.getTags(), (float)defaultHeight); - - } - /** * retrieves a suitable {@link TreeModel} from {@link #existingModels}, or creates it if necessary * - * @param seed an object to be used as the seed for random decisions + * @param seed an object to be used as the seed for random decisions */ - private TreeModel getTreeModel(VectorXYZ seed, LeafType leafType, LeafCycle leafCycle, TreeSpecies species) { + private TreeModel getTreeModel(Vector3D seed, LeafType leafType, LeafCycle leafCycle, TreeSpecies species, + @Nullable TreeDimensions dimensions) { var r = new Random((long)(seed.getX() * 10) + (long)(seed.getZ() * 10000)); @@ -211,6 +248,7 @@ private TreeModel getTreeModel(VectorXYZ seed, LeafType leafType, LeafCycle leaf && existingModel.leafCycle() == leafCycle && existingModel.species() == species && existingModel.mirrored() == mirrored + && existingModel.dimensions() == dimensions && (existingModel instanceof TreeBillboardModel) == useBillboards) { model = existingModel; break; @@ -219,8 +257,8 @@ private TreeModel getTreeModel(VectorXYZ seed, LeafType leafType, LeafCycle leaf if (model == null) { model = useBillboards - ? new TreeBillboardModel(leafType, leafCycle, species, mirrored) - : new TreeGeometryModel(leafType, leafCycle, species); + ? new TreeBillboardModel(leafType, leafCycle, species, mirrored, dimensions) + : new TreeGeometryModel(leafType, leafCycle, species, dimensions); existingModels.add(model); } @@ -234,6 +272,8 @@ private interface TreeModel extends Model { LeafCycle leafCycle(); @Nullable TreeSpecies species(); boolean mirrored(); + double defaultHeightToWidth(); + @Nullable TreeDimensions dimensions(); } @@ -241,21 +281,43 @@ private record TreeBillboardModel( LeafType leafType, LeafCycle leafCycle, @Nullable TreeSpecies species, - boolean mirrored + boolean mirrored, + @Nullable TreeDimensions dimensions ) implements TreeModel { @Override public List buildMeshes(InstanceParameters params) { - Material material = species == TreeSpecies.APPLE_TREE + Material material = getMaterial(); + + return WorldModuleBillboardUtil.buildCrosstree(material, params.position(), + dimensions != null ? dimensions.crownDiameter : defaultHeightToWidth() * params.height(), + params.height(), mirrored); + + } + + private Material getMaterial() { + return species == TreeSpecies.APPLE_TREE ? Materials.TREE_BILLBOARD_BROAD_LEAVED_FRUIT : leafType == LeafType.NEEDLELEAVED ? Materials.TREE_BILLBOARD_CONIFEROUS : Materials.TREE_BILLBOARD_BROAD_LEAVED; + } - return WorldModuleBillboardUtil.buildCrosstree(material, params.position(), - (species != null ? 1.0 : 0.5) * params.height(), params.height(), mirrored); - + @Override + public double defaultHeightToWidth() { + List textureLayers = getMaterial().getTextureLayers(); + if (!textureLayers.isEmpty()) { + TextureData texture = textureLayers.get(0).baseColorTexture; + TextureDataDimensions textureDimensions = texture.dimensions(); + if (textureDimensions.widthPerEntity() != null && textureDimensions.heightPerEntity() != null) { + return textureDimensions.heightPerEntity() / textureDimensions.widthPerEntity(); + } else { + return 1.0 / texture.getAspectRatio(); + } + } else { + return 2; + } } } @@ -263,7 +325,8 @@ public List buildMeshes(InstanceParameters params) { private record TreeGeometryModel( LeafType leafType, LeafCycle leafCycle, - @Nullable TreeSpecies species + @Nullable TreeSpecies species, + @Nullable TreeDimensions dimensions ) implements TreeModel { @Override @@ -280,15 +343,18 @@ public List buildMeshes(InstanceParameters params) { boolean coniferous = (leafType == LeafType.NEEDLELEAVED); double stemRatio = coniferous?0.3:0.5; - double radius = height*TREE_RADIUS_PER_HEIGHT; + double width = dimensions != null ? dimensions.crownDiameter : defaultHeightToWidth() * height; + double trunkRadius = dimensions != null && dimensions.trunkDiameter != null ? dimensions.trunkDiameter / 2 + : width / 8; ExtrusionGeometry trunk = ExtrusionGeometry.createColumn(null, - posXYZ, height*stemRatio,radius / 4, radius / 5, + posXYZ, height*stemRatio,trunkRadius, 0.8 * trunkRadius, false, true, null, TREE_TRUNK.getTextureDimensions()); ExtrusionGeometry crown = ExtrusionGeometry.createColumn(null, - posXYZ.addY(height*stemRatio), height*(1-stemRatio), radius, coniferous ? 0 : radius, - true, true, null, TREE_CROWN.getTextureDimensions()); + posXYZ.addY(height*stemRatio), height*(1-stemRatio), width / 2, + coniferous ? 0 : width / 2, true, true, null, + TREE_CROWN.getTextureDimensions()); return List.of( new Mesh(trunk, TREE_TRUNK), @@ -297,36 +363,43 @@ public List buildMeshes(InstanceParameters params) { } + @Override + public double defaultHeightToWidth() { + return 2.5; + } } private final List existingModels = new ArrayList<>(); public class Tree extends NoOutlineNodeWorldObject implements ProceduralWorldObject { - private final LeafType leafType; - private final LeafCycle leafCycle; - private final TreeSpecies species; + private final TreeDimensions dimensions; + private final TreeModel model; public Tree(MapNode node) { super(node); - LeafType leafType = LeafType.getValue(node.getTags()); - LeafCycle leafCycle = LeafCycle.getValue(node.getTags()); - TreeSpecies species = TreeSpecies.getValue(node.getTags()); + TagSet tags = node.getTags(); + + /* inherit information from the tree row this tree belongs to, if any */ Optional parentTreeRow = node.getConnectedWaySegments().stream() .filter(s -> s.getTags().contains("natural", "tree_row")).findAny(); if (parentTreeRow.isPresent()) { - // inherit information from the tree row this tree belongs to - if (leafType == null) leafType = LeafType.getValue(parentTreeRow.get().getTags()); - if (leafCycle == null) leafCycle = LeafCycle.getValue(parentTreeRow.get().getTags()); - if (species == null) species = TreeSpecies.getValue(parentTreeRow.get().getTags()); + tags = WorldModuleParseUtil.inheritTags(tags, parentTreeRow.get().getTags()); } - this.leafType = leafType; - this.leafCycle = leafCycle; - this.species = species; + /* interpret the tags */ + + var leafType = LeafType.getValue(tags); + var leafCycle = LeafCycle.getValue(tags); + var species = TreeSpecies.getValue(tags); + + TreeModel dimensionlessModel = getTreeModel(node.getPos(), leafType, leafCycle, species, null); + dimensions = TreeDimensions.fromTags(tags, null, dimensionlessModel, defaultTreeHeight); + model = getTreeModel(node.getPos(), leafType, leafCycle, species, dimensions); + } @Override @@ -336,9 +409,7 @@ public GroundState getGroundState() { @Override public void buildMeshesAndModels(Target target) { - TreeModel treeModel = getTreeModel(getBase(), leafType, leafCycle, species); - double height = getTreeHeight(node, leafType == LeafType.NEEDLELEAVED, species != null); - target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(getBase(), 0, height))); + target.addSubModel(new ModelInstance(model, new InstanceParameters(getBase(), 0, dimensions.height))); } } @@ -417,9 +488,10 @@ public void buildMeshesAndModels(Target target) { for (EleConnector treeConnector : treeConnectors) { VectorXYZ pos = treeConnector.getPosXYZ(); - TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species); - double height = getTreeHeight(segment, leafType == LeafType.NEEDLELEAVED, species != null); - target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, height))); + TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species, null); + TreeDimensions dimensions = TreeDimensions.fromTags(segment.getTags(), null, treeModel, defaultTreeHeight); + treeModel = getTreeModel(pos, leafType, leafCycle, species, dimensions); + target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, dimensions.height))); } } @@ -509,9 +581,11 @@ public void buildMeshesAndModels(Target target) { for (EleConnector treeConnector : treeConnectors) { VectorXYZ pos = treeConnector.getPosXYZ(); - TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species); - double height = getTreeHeight(area, leafType == LeafType.NEEDLELEAVED, species != null); - target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, height))); + TreeModel treeModel = getTreeModel(pos, leafType, leafCycle, species, null); + TreeDimensions dimensions = TreeDimensions.fromTags(area.getTags(), new Random(area.getId()), treeModel, + area.getTags().contains("landuse", "orchard") ? defaultTreeHeight : defaultTreeHeightForest); + treeModel = getTreeModel(pos, leafType, leafCycle, species, dimensions); + target.addSubModel(new ModelInstance(treeModel, new InstanceParameters(pos, 0, dimensions.height))); } } From 274824eaa6b715902c0ef6c26a78155d0aeab9a7 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Fri, 15 Nov 2024 12:58:40 +0100 Subject: [PATCH 48/85] Approximate extruded circles with a max error based on LOD --- .../org/osm2world/core/target/Target.java | 4 +- .../core/target/common/MeshTarget.java | 37 +++++++++++++++++++ .../target/common/mesh/ExtrusionGeometry.java | 29 +++++++++------ .../core/target/gltf/GltfTarget.java | 1 + 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/Target.java b/src/main/java/org/osm2world/core/target/Target.java index 2f1188dca..c2ee4b49c 100644 --- a/src/main/java/org/osm2world/core/target/Target.java +++ b/src/main/java/org/osm2world/core/target/Target.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import org.apache.commons.configuration.Configuration; +import org.osm2world.core.target.common.MeshTarget; import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.mesh.TriangleGeometry; @@ -32,7 +33,8 @@ default void beginObject(@Nullable WorldObject object) {} public default void drawMesh(Mesh mesh) { if (mesh.lodRange.contains(getLod())) { - TriangleGeometry tg = mesh.geometry.asTriangles(); + var converter = new MeshTarget.ConvertToTriangles(getLod()); + TriangleGeometry tg = converter.applyToGeometry(mesh.geometry); drawTriangles(mesh.material, tg.triangles, tg.normalData.normals(), tg.texCoords); } } diff --git a/src/main/java/org/osm2world/core/target/common/MeshTarget.java b/src/main/java/org/osm2world/core/target/common/MeshTarget.java index bac8fa4c0..4b45f09ac 100644 --- a/src/main/java/org/osm2world/core/target/common/MeshTarget.java +++ b/src/main/java/org/osm2world/core/target/common/MeshTarget.java @@ -78,6 +78,43 @@ public MeshStore apply(MeshStore meshStore) { } + /** converts all geometry to {@link TriangleGeometry} */ + public record ConvertToTriangles(double desiredMaxError) implements MeshProcessingStep { + + public ConvertToTriangles { + if (!Double.isFinite(desiredMaxError)) { + throw new IllegalArgumentException("invalid parameter: " + desiredMaxError); + } + } + + public ConvertToTriangles(LevelOfDetail lod) { + this(switch (lod) { + case LOD4 -> 0.01; + case LOD3 -> 0.05; + case LOD2 -> 0.20; + case LOD1 -> 1.0; + case LOD0 -> 4.0; + }); + } + + @Override + public MeshStore apply(MeshStore meshStore) { + return new MeshStore(meshStore.meshesWithMetadata().stream() + .map(m -> new MeshWithMetadata(new Mesh(applyToGeometry(m.mesh().geometry), + m.mesh().material, m.mesh().lodRange), m.metadata())) + .toList()); + } + + public TriangleGeometry applyToGeometry(Geometry g) { + if (g instanceof ExtrusionGeometry eg) { + return eg.asTriangles(desiredMaxError); + } else { + return g.asTriangles(); + } + } + + } + public static class MergeMeshes implements MeshProcessingStep { /** options that alter the behavior away from the default */ diff --git a/src/main/java/org/osm2world/core/target/common/mesh/ExtrusionGeometry.java b/src/main/java/org/osm2world/core/target/common/mesh/ExtrusionGeometry.java index 4313ab939..eb3868f76 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/ExtrusionGeometry.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/ExtrusionGeometry.java @@ -1,8 +1,8 @@ package org.osm2world.core.target.common.mesh; -import static java.lang.Math.PI; -import static java.lang.Math.ceil; +import static java.lang.Math.*; import static java.util.Arrays.asList; +import static java.util.Collections.max; import static java.util.Collections.*; import static java.util.stream.Collectors.toList; import static org.osm2world.core.math.GeometryUtil.triangleVertexListFromTriangleStrip; @@ -134,6 +134,14 @@ public static ExtrusionGeometry createColumn(Integer corners, VectorXYZ base, do @Override public TriangleGeometry asTriangles() { + return asTriangles(0.01); + } + + /** + * @param desiredMaxError when approximating round shapes such as circles for the conversion to triangles, + * this controls how coarsely the shape will be approximated + */ + public TriangleGeometry asTriangles(double desiredMaxError) { /* provide defaults for optional parameters */ @@ -182,12 +190,11 @@ public TriangleGeometry asTriangles() { ShapeXZ shape = this.shape; - if (shape instanceof CircleXZ) { + if (shape instanceof CircleXZ circle) { - double desiredMinDetail = 0.03; - CircleXZ circle = (CircleXZ)shape; - double maxCircumference = max(scaleFactors) * 2 * circle.getRadius() * PI; - int numPoints = Integer.max(4, (int) ceil(maxCircumference / desiredMinDetail)); + double maxRadius = max(scaleFactors) * circle.getRadius(); + double minPointsForError = PI / sqrt (2 * desiredMaxError / maxRadius); + int numPoints = Integer.max(4, (int) ceil(minPointsForError)); if (!options.contains(START_CAP) && !options.contains(END_CAP)) { // if the ends aren't visible, it's a lot easier to fake roundness with smooth shading @@ -202,14 +209,14 @@ public TriangleGeometry asTriangles() { Collection rings = singleton(shape); - if (shape instanceof ClosedShapeXZ) { + if (shape instanceof ClosedShapeXZ closedShape) { rings = new ArrayList<>(); - rings.add(((ClosedShapeXZ) shape).getOuter()); + rings.add(closedShape.getOuter()); - boolean outerIsClockwise = ((ClosedShapeXZ) shape).getOuter().isClockwise(); + boolean outerIsClockwise = closedShape.getOuter().isClockwise(); - for (SimpleClosedShapeXZ hole : ((ClosedShapeXZ) shape).getHoles()) { + for (SimpleClosedShapeXZ hole : closedShape.getHoles()) { // inner rings need to be the opposite winding compared to the outer ring SimplePolygonXZ inner = asSimplePolygon(hole); inner = outerIsClockwise ? inner.makeCounterclockwise() : inner.makeClockwise(); diff --git a/src/main/java/org/osm2world/core/target/gltf/GltfTarget.java b/src/main/java/org/osm2world/core/target/gltf/GltfTarget.java index 5e430254e..04ce84855 100644 --- a/src/main/java/org/osm2world/core/target/gltf/GltfTarget.java +++ b/src/main/java/org/osm2world/core/target/gltf/GltfTarget.java @@ -389,6 +389,7 @@ private void writeJson(OutputStream outputStream) throws IOException { List processingSteps = new ArrayList<>(asList( new FilterLod(lod), + new ConvertToTriangles(lod), new EmulateTextureLayers(lod.ordinal() <= 1 ? 1 : Integer.MAX_VALUE), new MoveColorsToVertices(), // after EmulateTextureLayers because colorable is per layer new ReplaceTexturesWithAtlas(t -> getResourceOutputSettings().modeForTexture(t) == REFERENCE), From 5e7942077f7d748a5a84825aff695aa4bd131514 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 18 Nov 2024 12:29:19 +0100 Subject: [PATCH 49/85] Fix equality check to find existing tree models --- src/main/java/org/osm2world/core/world/modules/TreeModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index c3fc3942a..1d02ab938 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -248,7 +248,7 @@ private TreeModel getTreeModel(Vector3D seed, LeafType leafType, LeafCycle leafC && existingModel.leafCycle() == leafCycle && existingModel.species() == species && existingModel.mirrored() == mirrored - && existingModel.dimensions() == dimensions + && Objects.equals(existingModel.dimensions(), dimensions) && (existingModel instanceof TreeBillboardModel) == useBillboards) { model = existingModel; break; From bfc309cc849eefabd203bff9693cf65e9bb1dbf2 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 11:58:10 +0100 Subject: [PATCH 50/85] Organize attributes by material to speed up config parsing --- .../target/common/material/Materials.java | 102 +++++++++--------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/Materials.java b/src/main/java/org/osm2world/core/target/common/material/Materials.java index 55d067973..daa1b3ea7 100644 --- a/src/main/java/org/osm2world/core/target/common/material/Materials.java +++ b/src/main/java/org/osm2world/core/target/common/material/Materials.java @@ -3,23 +3,20 @@ import static java.awt.Color.*; import static java.util.Collections.emptyList; -import java.awt.Color; -import java.awt.Font; +import java.awt.*; import java.io.File; import java.lang.reflect.Field; +import java.util.List; import java.util.*; import java.util.Map.Entry; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.commons.configuration.Configuration; -import org.osm2world.core.target.common.material.Material.AmbientOcclusion; import org.osm2world.core.target.common.material.Material.Interpolation; -import org.osm2world.core.target.common.material.Material.Shadow; import org.osm2world.core.target.common.material.Material.Transparency; import org.osm2world.core.target.common.material.TextTexture.FontStyle; import org.osm2world.core.target.common.material.TextureData.Wrap; @@ -28,8 +25,6 @@ import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.world.creation.WorldModule; -import com.google.common.collect.Streams; - /** * this class defines materials that can be used by all {@link WorldModule}s */ @@ -364,8 +359,8 @@ public static final String getUniqueName(Material material) { return fieldNameMap.get(material); } - private static final String CONF_KEY_REGEX = - "material_(.+)_(interpolation|color|doubleSided|shadow|ssao|transparency|texture\\d*_.+)"; + private static final Pattern CONF_KEY_PATTERN = Pattern.compile( + "material_(.+)_(interpolation|color|doubleSided|shadow|ssao|transparency|texture\\d*_.+)"); /** * configures the attributes of the materials within this class @@ -375,29 +370,45 @@ public static final void configureMaterials(Configuration config) { externalMaterials.clear(); - Map texturePrefixMap = new HashMap<>(); + /* find all material-related properties and organize them by material */ + + Map> attributesPerMaterialName = new HashMap<>(); Iterator keyIterator = config.getKeys(); while (keyIterator.hasNext()) { - String key = keyIterator.next(); + Matcher matcher = CONF_KEY_PATTERN.matcher(key); + if (matcher.matches()) { + String materialName = matcher.group(1); + if (!attributesPerMaterialName.containsKey(materialName)) { + attributesPerMaterialName.put(materialName, new HashSet<>()); + } + attributesPerMaterialName.get(materialName).add(matcher.group(2)); + } + } - Matcher matcher = Pattern.compile(CONF_KEY_REGEX).matcher(key); + /* create each material */ - if (matcher.matches()) { + for (var entry : attributesPerMaterialName.entrySet()) { - String materialName = matcher.group(1); - ConfMaterial material = getMaterial(materialName); + String materialName = entry.getKey(); + Set attributes = entry.getValue(); - /* If material is not defined in Materials.java, create new material - * and add it to externalMaterials map */ - if (material == null) { - material = new ConfMaterial(Interpolation.FLAT, Color.white); - externalMaterials.put(materialName, material); - } + ConfMaterial material = getMaterial(materialName); + + /* If material is not defined in Materials.java, create new material + * and add it to externalMaterials map */ + if (material == null) { + material = new ConfMaterial(Interpolation.FLAT, Color.white); + externalMaterials.put(materialName, material); + } + + String keyPrefix = "material_" + materialName + "_"; + + for (String attribute : attributes) { - String attribute = matcher.group(2); + String key = keyPrefix + attribute; if ("interpolation".equals(attribute)) { @@ -410,14 +421,12 @@ public static final void configureMaterials(Configuration config) { } else if ("color".equals(attribute)) { - Color color = ConfigUtil.parseColor( - config.getString(key)); + Color color = ConfigUtil.parseColor(config.getString(key)); if (color != null) { material.setColor(color); } else { - System.err.println("incorrect color value: " - + config.getString(key)); + System.err.println("incorrect color value: " + config.getString(key)); } } else if ("doubleSided".equals(attribute)) { @@ -428,7 +437,7 @@ public static final void configureMaterials(Configuration config) { } else if ("shadow".equals(attribute)) { String value = config.getString(key).toUpperCase(); - Shadow shadow = Shadow.valueOf(value); + Material.Shadow shadow = Material.Shadow.valueOf(value); if (shadow != null) { material.setShadow(shadow); @@ -437,7 +446,7 @@ public static final void configureMaterials(Configuration config) { } else if ("ssao".equals(attribute)) { String value = config.getString(key).toUpperCase(); - AmbientOcclusion ao = AmbientOcclusion.valueOf(value); + Material.AmbientOcclusion ao = Material.AmbientOcclusion.valueOf(value); if (ao != null) { material.setAmbientOcclusion(ao); @@ -452,27 +461,21 @@ public static final void configureMaterials(Configuration config) { material.setTransparency(transparency); } - } else if (attribute.startsWith("texture")) { - - texturePrefixMap.put("material_" + materialName + "_texture", material); - - } else { - System.err.println("unknown material attribute: " + attribute); + } else if (!attribute.startsWith("texture")) { + System.err.println("unknown material attribute '" + attribute + "' for material " + materialName); } - } - } - /* configure texture layers */ + } - for (String texturePrefix : texturePrefixMap.keySet()) { + /* configure texture layers */ List textureLayers = new ArrayList<>(); for (int i = 0; i < Material.MAX_TEXTURE_LAYERS; i++) { - String keyPrefix = texturePrefix + i; - Stream keyStream = Streams.stream(config.getKeys()); - if (keyStream.anyMatch(k -> k.startsWith(keyPrefix))) { - TextureLayer textureLayer = createTextureLayer(config, keyPrefix); + String attribute = "texture" + i; + if (attributes.stream().anyMatch(a -> a.startsWith(attribute))) { + boolean implicitColorTexture = attributes.stream().noneMatch(a -> a.startsWith(attribute + "_color_")); + TextureLayer textureLayer = createTextureLayer(config, keyPrefix + attribute, implicitColorTexture); if (textureLayer != null) { textureLayers.add(textureLayer); } @@ -481,13 +484,13 @@ public static final void configureMaterials(Configuration config) { } } - texturePrefixMap.get(texturePrefix).setTextureLayers(textureLayers); + material.setTextureLayers(textureLayers); } } - private static @Nullable TextureLayer createTextureLayer(Configuration config, String keyPrefix) { + private static @Nullable TextureLayer createTextureLayer(Configuration config, String keyPrefix, boolean implicitColorTexture) { File baseColorTexture = null; File ormTexture = null; @@ -514,15 +517,8 @@ public static final void configureMaterials(Configuration config) { } } - TextureData baseColorTextureData; - - Stream keyStream = Streams.stream(config.getKeys()); - if (keyStream.anyMatch(k -> k.startsWith(keyPrefix + "_color_"))) { - baseColorTextureData = createTextureData(config, keyPrefix + "_color", baseColorTexture); - } else { - // allow omitting _color for backwards compatibility - baseColorTextureData = createTextureData(config, keyPrefix, baseColorTexture); - } + TextureData baseColorTextureData = createTextureData( + config, keyPrefix + (implicitColorTexture ? "" : "_color"), baseColorTexture); if (baseColorTextureData == null) { System.err.println("Config is missing base color texture for " + keyPrefix); From 28f1af7cb72d495f53c6785cdf6744c3da34020b Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 12:23:00 +0100 Subject: [PATCH 51/85] Provide a utility method to obtain enum values from a config --- .../target/common/material/Materials.java | 78 +++++++------------ .../org/osm2world/core/util/ConfigUtil.java | 15 +++- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/Materials.java b/src/main/java/org/osm2world/core/target/common/material/Materials.java index daa1b3ea7..a9574afb7 100644 --- a/src/main/java/org/osm2world/core/target/common/material/Materials.java +++ b/src/main/java/org/osm2world/core/target/common/material/Materials.java @@ -2,6 +2,7 @@ import static java.awt.Color.*; import static java.util.Collections.emptyList; +import static org.osm2world.core.util.ConfigUtil.readEnum; import java.awt.*; import java.io.File; @@ -410,59 +411,40 @@ public static final void configureMaterials(Configuration config) { String key = keyPrefix + attribute; - if ("interpolation".equals(attribute)) { - - String value = config.getString(key).toUpperCase(); - Interpolation interpolation = Interpolation.valueOf(value); - - if (interpolation != null) { - material.setInterpolation(interpolation); + switch (attribute) { + case "doubleSided" -> { + boolean doubleSided = config.getBoolean(key); + material.setDoubleSided(doubleSided); } - - } else if ("color".equals(attribute)) { - - Color color = ConfigUtil.parseColor(config.getString(key)); - - if (color != null) { - material.setColor(color); - } else { - System.err.println("incorrect color value: " + config.getString(key)); + case "interpolation" -> { + Interpolation interpolation = readEnum(Interpolation.class, config, key); + if (interpolation != null) { material.setInterpolation(interpolation); } } - - } else if ("doubleSided".equals(attribute)) { - - boolean doubleSided = config.getBoolean(key); - material.setDoubleSided(doubleSided); - - } else if ("shadow".equals(attribute)) { - - String value = config.getString(key).toUpperCase(); - Material.Shadow shadow = Material.Shadow.valueOf(value); - - if (shadow != null) { - material.setShadow(shadow); + case "shadow" -> { + Material.Shadow shadow = readEnum(Material.Shadow.class, config, key); + if (shadow != null) { material.setShadow(shadow); } } - - } else if ("ssao".equals(attribute)) { - - String value = config.getString(key).toUpperCase(); - Material.AmbientOcclusion ao = Material.AmbientOcclusion.valueOf(value); - - if (ao != null) { - material.setAmbientOcclusion(ao); + case "ssao" -> { + Material.AmbientOcclusion ao = readEnum(Material.AmbientOcclusion.class, config, key); + if (ao != null) { material.setAmbientOcclusion(ao); } } - - } else if ("transparency".equals(attribute)) { - - String value = config.getString(key).toUpperCase(); - Transparency transparency = Transparency.valueOf(value); - - if (transparency != null) { - material.setTransparency(transparency); + case "transparency" -> { + Transparency transparency = readEnum(Transparency.class, config, key); + if (transparency != null) { material.setTransparency(transparency); } + } + case "color" -> { + Color color = ConfigUtil.parseColor(config.getString(key)); + if (color != null) { + material.setColor(color); + } else { + System.err.println("incorrect color value: " + config.getString(key)); + } + } + default -> { + if (!attribute.startsWith("texture")) { + System.err.println("unknown material attribute '" + attribute + "' for material " + materialName); + } } - - } else if (!attribute.startsWith("texture")) { - System.err.println("unknown material attribute '" + attribute + "' for material " + materialName); } } diff --git a/src/main/java/org/osm2world/core/util/ConfigUtil.java b/src/main/java/org/osm2world/core/util/ConfigUtil.java index 958fc7515..8c58f3a9e 100644 --- a/src/main/java/org/osm2world/core/util/ConfigUtil.java +++ b/src/main/java/org/osm2world/core/util/ConfigUtil.java @@ -2,10 +2,7 @@ import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.GraphicsEnvironment; +import java.awt.*; import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -135,6 +132,16 @@ public static void parseFonts(Configuration config) { } } + public static > @Nullable T readEnum(Class enumClass, Configuration config, String key) { + String value = config.getString(key); + if (value != null) { + try { + return Enum.valueOf(enumClass, value.toUpperCase()); + } catch (IllegalArgumentException ignored) {} + } + return null; + } + /** * If config references some files by path e.g. textures * resolve file paths relative to config location From c9e6456c0cbc608e9dadbaab014c6e3b7bec6119 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 13:29:51 +0100 Subject: [PATCH 52/85] Avoid NPE for building walls with missing mainSurface --- .../building/ExteriorBuildingWall.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java index ebb6e14ed..a3a94372a 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java @@ -205,7 +205,7 @@ public void renderTo(CommonTarget target, boolean renderElements) { /* construct the surface(s) */ - WallSurface mainSurface, roofSurface; + @Nullable WallSurface mainSurface, roofSurface; double maxHeight = max(topPoints, comparingDouble(v -> v.y)).y - floorEle; @@ -247,7 +247,7 @@ public void renderTo(CommonTarget target, boolean renderElements) { boolean individuallyMappedWindows = false; - if (windowImplementation != WindowImplementation.NONE) { + if (mainSurface != null && windowImplementation != WindowImplementation.NONE) { /* add individually mapped doors and windows (if any) */ //TODO: doors at corners of the building (or boundaries between building:wall=yes ways) do not work yet @@ -295,21 +295,27 @@ public void renderTo(CommonTarget target, boolean renderElements) { } - if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { - if (!buildingPart.area.getBoundaryNodes().stream().anyMatch(Door::isDoorNode)) { - if (points.getLength() > buildingPart.area.getOuterPolygon().getOutlineLength() / 8) { - // not the narrow side of a long building with several garages - placeDefaultGarageDoors(mainSurface); + if (mainSurface != null) { + + /* add garage doors */ + + if (tags.containsAny(asList("building", "building:part"), asList("garage", "garages"))) { + if (buildingPart.area.getBoundaryNodes().stream().noneMatch(Door::isDoorNode)) { + if (points.getLength() > buildingPart.area.getOuterPolygon().getOutlineLength() / 8) { + // not the narrow side of a long building with several garages + placeDefaultGarageDoors(mainSurface); + } } } - } - /* add windows (after doors, because default windows should be displaced by them) */ + /* add windows (after doors, because default windows should be displaced by them) */ + + if (hasWindows && !individuallyMappedWindows + && (windowImplementation == WindowImplementation.INSET_TEXTURES + || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { + placeDefaultWindows(mainSurface, windowImplementation); + } - if (hasWindows && !individuallyMappedWindows - && (windowImplementation == WindowImplementation.INSET_TEXTURES - || windowImplementation == WindowImplementation.FULL_GEOMETRY)) { - placeDefaultWindows(mainSurface, windowImplementation); } /* draw the wall */ From 1d829e70b8390dd05af70e3ba131d8841dfc8f6f Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 13:47:35 +0100 Subject: [PATCH 53/85] Use double-sided material for guard rails --- .../osm2world/core/target/common/material/Material.java | 5 +++++ .../org/osm2world/core/world/modules/BarrierModule.java | 9 +-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/Material.java b/src/main/java/org/osm2world/core/target/common/material/Material.java index 9742c2125..641b46367 100644 --- a/src/main/java/org/osm2world/core/target/common/material/Material.java +++ b/src/main/java/org/osm2world/core/target/common/material/Material.java @@ -118,6 +118,11 @@ public Material makeSmooth() { getTransparency(), getShadow(), getAmbientOcclusion(), getTextureLayers()); } + public Material makeDoubleSided() { + return new ImmutableMaterial(getInterpolation(), getColor(), true, + getTransparency(), getShadow(), getAmbientOcclusion(), getTextureLayers()); + } + /** * returns a material that is like this one, * except with a different list of {@link TextureLayer}s diff --git a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java index 1b3749bbd..8bf51adf5 100644 --- a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java @@ -52,8 +52,6 @@ import org.osm2world.core.world.modules.common.AbstractModule; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; -import com.google.common.collect.Lists; - /** * adds barriers to the world */ @@ -804,12 +802,7 @@ public void buildMeshesAndModels(Target target) { List path = addYList(centerline, this.height - SHAPE_GERMAN_B_HEIGHT); - //front - target.drawExtrudedShape(material, SHAPE_GERMAN_B, - path, nCopies(path.size(), Y_UNIT), null, null, null); - - //back - target.drawExtrudedShape(material, new PolylineXZ(Lists.reverse(SHAPE_GERMAN_B.vertices())), + target.drawExtrudedShape(material.makeDoubleSided(), SHAPE_GERMAN_B, path, nCopies(path.size(), Y_UNIT), null, null, null); /* add posts */ From 667cad39561d2e630bf90d830f110f7f45f7b96b Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 14:38:14 +0100 Subject: [PATCH 54/85] Support and use attachment baseEleFunction in ProceduralWO.Target --- .../world/attachment/AttachmentSurface.java | 23 ++++++--- .../world/data/ProceduralWorldObject.java | 11 ++++- .../core/world/modules/BarrierModule.java | 49 +++++-------------- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java b/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java index 6e8f1f0e6..2e634bd4c 100644 --- a/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java +++ b/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java @@ -10,12 +10,9 @@ import java.util.List; import java.util.function.Function; -import org.osm2world.core.math.AxisAlignedRectangleXZ; -import org.osm2world.core.math.BoundedObject; -import org.osm2world.core.math.FaceXYZ; -import org.osm2world.core.math.InvalidGeometryException; -import org.osm2world.core.math.VectorXYZ; -import org.osm2world.core.math.VectorXZ; +import javax.annotation.Nullable; + +import org.osm2world.core.math.*; import org.osm2world.core.target.common.FaceTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.mesh.Geometry; @@ -107,11 +104,23 @@ public static AttachmentSurface fromMeshes(String type, Iterable meshes) { } public static AttachmentSurface fromMeshes(Collection types, Iterable meshes) { + return fromMeshes(types, meshes, null); + } + + public static AttachmentSurface fromMeshes(Collection types, Iterable meshes, + @Nullable Function baseEleFunction) { + List faces = new ArrayList<>(); for (Mesh mesh : meshes) { mesh.geometry.asTriangles().triangles.forEach(t -> faces.add(new FaceXYZ(t.vertices()))); } - return new AttachmentSurface(types, faces); + + if (baseEleFunction != null) { + return new AttachmentSurface(types, faces, baseEleFunction); + } else { + return new AttachmentSurface(types, faces); + } + } public static class Builder extends FaceTarget { diff --git a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java index 55854376f..46f0dfdb6 100644 --- a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java @@ -3,9 +3,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.function.Function; import javax.annotation.Nullable; +import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.mesh.LODRange; import org.osm2world.core.target.common.mesh.LevelOfDetail; @@ -29,6 +31,7 @@ class Target implements CommonTarget { private @Nullable LODRange currentLodRange = null; private List currentAttachmentTypes = List.of(); + private Function currentBaseEleFunction = null; public @Nullable LODRange getCurrentLodRange() { return currentLodRange; @@ -43,6 +46,12 @@ public void setCurrentLodRange(LevelOfDetail minLod, LevelOfDetail maxLod) { } public void setCurrentAttachmentTypes(String... attachmentTypes) { + setCurrentAttachmentTypes(null, attachmentTypes); + } + + public void setCurrentAttachmentTypes(@Nullable Function baseEleFunction, + String... attachmentTypes) { + this.currentBaseEleFunction = baseEleFunction; this.currentAttachmentTypes = List.of(attachmentTypes); } @@ -56,7 +65,7 @@ public void drawMesh(Mesh mesh) { } if (!currentAttachmentTypes.isEmpty()) { - attachmentSurfaces.add(AttachmentSurface.fromMeshes(currentAttachmentTypes, List.of(mesh))); + attachmentSurfaces.add(AttachmentSurface.fromMeshes(currentAttachmentTypes, List.of(mesh), currentBaseEleFunction)); } } diff --git a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java index 8bf51adf5..cf2bd24af 100644 --- a/src/main/java/org/osm2world/core/world/modules/BarrierModule.java +++ b/src/main/java/org/osm2world/core/world/modules/BarrierModule.java @@ -179,12 +179,21 @@ public void buildMeshesAndModels(Target target) { target.setCurrentLodRange(minLod, LOD4); + List leftBottomOutline = getOutline(false); List leftTopOutline = addYList(leftBottomOutline, height); List rightBottomOutline = getOutline(true); List rightTopOutline = addYList(rightBottomOutline, height); + /* define the base ele function */ + + Function baseEleFunction = (VectorXZ point) -> { + PolylineXZ centerlineXZ = new PolylineXZ(getCenterlineXZ()); + double ratio = centerlineXZ.offsetOf(centerlineXZ.closestPoint(point)); + return GeometryUtil.interpolateOn(getCenterline(), ratio).y; + }; + /* close the wall at the end if necessary */ if (getConnectedNetworkSegments(segment.getStartNode(), this.getClass(), s -> s != this).isEmpty()) { @@ -221,6 +230,8 @@ public void buildMeshesAndModels(Target target) { /* draw the sides of the wall */ + target.setCurrentAttachmentTypes(baseEleFunction, "wall"); + reverse(leftTopOutline); reverse(leftBottomOutline); @@ -232,44 +243,6 @@ public void buildMeshesAndModels(Target target) { } - @Override - public Collection getAttachmentSurfaces() { - - /* define the base ele function */ - - Function baseEleFunction = (VectorXZ point) -> { - PolylineXZ centerlineXZ = new PolylineXZ(getCenterlineXZ()); - double ratio = centerlineXZ.offsetOf(centerlineXZ.closestPoint(point)); - return GeometryUtil.interpolateOn(getCenterline(), ratio).y; - }; - - /* return the sides of the wall as attachment surfaces */ - - //TODO avoid copypasted code from renderTo - - List leftBottomOutline = getOutline(false); - List leftTopOutline = addYList(leftBottomOutline, height); - - List rightBottomOutline = getOutline(true); - List rightTopOutline = addYList(rightBottomOutline, height); - - reverse(leftTopOutline); - reverse(leftBottomOutline); - - AttachmentSurface.Builder leftBuilder = new AttachmentSurface.Builder("wall"); - List leftVs = createTriangleStripBetween(leftTopOutline, leftBottomOutline); - leftBuilder.drawTriangleStrip(material, leftVs, texCoordLists(leftVs, material, STRIP_WALL)); - leftBuilder.setBaseEleFunction(baseEleFunction); - - AttachmentSurface.Builder rightBuilder = new AttachmentSurface.Builder("wall"); - List rightVs = createTriangleStripBetween(rightTopOutline, rightBottomOutline); - rightBuilder.drawTriangleStrip(material, rightVs, texCoordLists(rightVs, material, STRIP_WALL)); - rightBuilder.setBaseEleFunction(baseEleFunction); - - return asList(leftBuilder.build(), rightBuilder.build()); - - } - } public static class Wall extends ColoredWall { From 481f4e9265d1e83b76479045a15d6653734b2b03 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 17:00:02 +0100 Subject: [PATCH 55/85] Fix debug arrow rendering --- .../viewer/view/debug/DebugView.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/osm2world/viewer/view/debug/DebugView.java b/src/main/java/org/osm2world/viewer/view/debug/DebugView.java index 334d6e440..259ad081f 100644 --- a/src/main/java/org/osm2world/viewer/view/debug/DebugView.java +++ b/src/main/java/org/osm2world/viewer/view/debug/DebugView.java @@ -4,11 +4,13 @@ import static java.util.Collections.emptyList; import java.awt.*; +import java.util.List; import org.apache.commons.configuration.Configuration; import org.osm2world.core.ConversionFacade.Results; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_elevation.creation.TerrainElevationData; +import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.common.material.ImmutableMaterial; @@ -198,23 +200,21 @@ protected static final void drawArrow(JOGLTarget target, Color color, } else { endDirXZ = endDirXZ.normalize(); } - VectorXZ endNormalXZ = endDirXZ.rightNormal(); - - - ImmutableMaterial colorMaterial = - new ImmutableMaterial(Interpolation.FLAT, color); - - target.drawTriangleStrip(colorMaterial, asList( - lastV, - headStart.subtract(endDirXZ.mult(headLength/2)), - headStart.add(endDirXZ.mult(headLength/2))), - emptyList()); - - target.drawTriangleStrip(colorMaterial, asList( - lastV, - headStart.subtract(endNormalXZ.mult(headLength/2)), - headStart.add(endNormalXZ.mult(headLength/2))), - emptyList()); + VectorXYZ endNormal = endDirXZ.rightNormal().xyz(0); + VectorXYZ endNormal2 = endDir.crossNormalized(endNormal); + + var colorMaterial = new ImmutableMaterial(Interpolation.FLAT, color); + + target.drawTriangles(colorMaterial, List.of( + new TriangleXYZ( + lastV, + headStart.subtract(endNormal.mult(headLength / 2)), + headStart.add(endNormal.mult(headLength / 2))), + new TriangleXYZ( + lastV, + headStart.subtract(endNormal2.mult(headLength / 2)), + headStart.add(endNormal2.mult(headLength / 2))) + ), emptyList()); } From c3447545559e406a9c67db699d7655cd9255a4ec Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 17:00:29 +0100 Subject: [PATCH 56/85] Add another TriangleXYZ area calculation test case --- src/test/java/org/osm2world/core/math/TriangleXYZTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/org/osm2world/core/math/TriangleXYZTest.java b/src/test/java/org/osm2world/core/math/TriangleXYZTest.java index cb6f59a37..1e9114e7d 100644 --- a/src/test/java/org/osm2world/core/math/TriangleXYZTest.java +++ b/src/test/java/org/osm2world/core/math/TriangleXYZTest.java @@ -40,6 +40,13 @@ public void testGetArea() { assertAlmostEquals(0.5, t1.getArea()); + TriangleXYZ t2 = new TriangleXYZ( + new VectorXYZ(0, 0, 0), + new VectorXYZ(1, 0, 0), + new VectorXYZ(0, 0, 5)); + + assertAlmostEquals(2.5, t2.getArea()); + } @Test From 448589eef0fd9c0a832cd96e9c5fd61722e64eaa Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 17:30:03 +0100 Subject: [PATCH 57/85] Allow WasteBasket to attach to indoor floors --- .../world/modules/StreetFurnitureModule.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java index fdd709d12..7e09399be 100644 --- a/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java +++ b/src/main/java/org/osm2world/core/world/modules/StreetFurnitureModule.java @@ -1346,10 +1346,14 @@ public WasteBasket(MapNode node) { super(node); - if (node.getTags().containsKey("support") && - !node.getTags().contains("support", "ground")) { - connector = new AttachmentConnector(singletonList(node.getTags().getValue("support")), + String support = node.getTags().getValue("support"); + + if (support != null && !"ground".equals(support)) { + connector = new AttachmentConnector(List.of(support), node.getPos().xyz(0), this, 0.6, true); + } else if (node.getTags().containsKey("level")) { + connector = new AttachmentConnector(List.of("floor" + node.getTags().getValue("level")), + node.getPos().xyz(0), this, 0, false); } else { connector = null; } @@ -1395,20 +1399,28 @@ public void buildMeshesAndModels(Target target) { /* determine position */ - VectorXYZ pos; + VectorXYZ pos = getBase(); VectorXYZ direction = VectorXZ.fromAngle(parseDirection(node.getTags(), PI)).xyz(0); + boolean onGround = true; + if (connector != null && connector.isAttached()) { pos = connector.getAttachedPos(); - direction = connector.getAttachedSurfaceNormal(); - } else { + if (!connector.compatibleSurfaceTypes.get(0).startsWith("floor")) { + onGround = false; + direction = connector.getAttachedSurfaceNormal(); + } - pos = getBase().addY(0.6).add(direction.mult(0.05)); + } + + if (onGround) { /* draw pole */ - target.drawColumn(material, null, getBase(), 1.2, 0.06, 0.06, false, true); + target.drawColumn(material, null, pos, 1.2, 0.06, 0.06, false, true); + + pos = pos.addY(0.6).add(direction.mult(0.05)); } From 468e2b8744f9231d00c5252dc3b127d3872e0649 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 16:17:29 +0100 Subject: [PATCH 58/85] Port Building to ProceduralWO, drop AttachmentSurface.Builder --- .../org/osm2world/core/ConversionFacade.java | 8 +- .../core/math/FlatSimplePolygonShapeXYZ.java | 14 ++- .../world/attachment/AttachmentSurface.java | 56 ++-------- .../core/world/modules/building/Building.java | 20 ++-- .../world/modules/building/BuildingPart.java | 4 - .../building/ExteriorBuildingWall.java | 32 ++---- .../world/modules/building/WallSurface.java | 37 +++---- .../building/indoor/BuildingPartInterior.java | 4 - .../modules/building/indoor/Ceiling.java | 28 +---- .../modules/building/indoor/IndoorFloor.java | 40 +++---- .../modules/building/indoor/IndoorModule.java | 4 +- .../modules/building/indoor/IndoorRoom.java | 12 +-- .../modules/building/indoor/IndoorWall.java | 100 ++++++------------ .../building/roof/HeightfieldRoof.java | 25 +++-- .../debug/AttachmentSurfaceDebugView.java | 8 +- 15 files changed, 128 insertions(+), 264 deletions(-) diff --git a/src/main/java/org/osm2world/core/ConversionFacade.java b/src/main/java/org/osm2world/core/ConversionFacade.java index 612b7a277..44e19e58c 100644 --- a/src/main/java/org/osm2world/core/ConversionFacade.java +++ b/src/main/java/org/osm2world/core/ConversionFacade.java @@ -30,7 +30,7 @@ import org.osm2world.core.map_data.data.MapMetadata; import org.osm2world.core.map_elevation.creation.*; import org.osm2world.core.map_elevation.data.EleConnector; -import org.osm2world.core.math.FaceXYZ; +import org.osm2world.core.math.FlatSimplePolygonShapeXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.datastructures.IndexGrid; import org.osm2world.core.math.datastructures.SpatialIndex; @@ -372,7 +372,7 @@ protected static void attachConnectorIfValid(AttachmentConnector connector, Atta for (boolean requirePreferredHeight : asList(true, false)) { - Predicate matchesPreferredHeight = (FaceXYZ f) -> { + Predicate matchesPreferredHeight = f -> { if (!requirePreferredHeight) { return true; } else { @@ -382,12 +382,12 @@ protected static void attachConnectorIfValid(AttachmentConnector connector, Atta } }; - Optional closestFace = surface.getFaces().stream() + Optional closestFace = surface.getFaces().stream() .filter(matchesPreferredHeight) .filter(f -> connector.isAcceptableNormal.test(f.getNormal())) .min(comparingDouble(f -> connector.changeXZ ? f.distanceTo(posAtEle) : f.distanceToXZ(posAtEle))); - if (!closestFace.isPresent()) continue; // try again without enforcing the preferred height + if (closestFace.isEmpty()) continue; // try again without enforcing the preferred height VectorXYZ closestPoint = null; diff --git a/src/main/java/org/osm2world/core/math/FlatSimplePolygonShapeXYZ.java b/src/main/java/org/osm2world/core/math/FlatSimplePolygonShapeXYZ.java index 7e08ce43c..daf011248 100644 --- a/src/main/java/org/osm2world/core/math/FlatSimplePolygonShapeXYZ.java +++ b/src/main/java/org/osm2world/core/math/FlatSimplePolygonShapeXYZ.java @@ -1,8 +1,11 @@ package org.osm2world.core.math; -import static java.lang.Math.*; +import static java.lang.Math.PI; +import static java.lang.Math.abs; import static java.util.stream.Collectors.toList; -import static org.osm2world.core.math.VectorXYZ.*; +import static org.osm2world.core.math.AxisAlignedRectangleXZ.bbox; +import static org.osm2world.core.math.VectorXYZ.Y_UNIT; +import static org.osm2world.core.math.VectorXYZ.Z_UNIT; import java.util.List; @@ -12,7 +15,7 @@ * a simple 3D polygon where all vertices are in the same plane. * {@link FaceXYZ} is the most general implementation. */ -public interface FlatSimplePolygonShapeXYZ { +public interface FlatSimplePolygonShapeXYZ extends BoundedObject { /** * returns the polygon's vertices. First and last vertex are equal. @@ -46,6 +49,11 @@ default public VectorXYZ getCenter() { return new VectorXYZ(x, y, z); } + @Override + public default AxisAlignedRectangleXZ boundingBox() { + return bbox(vertices()); + } + /** returns the closest point on this face to a given point in 3D space */ default public VectorXYZ closestPoint(VectorXYZ v) { diff --git a/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java b/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java index 2e634bd4c..919c9546a 100644 --- a/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java +++ b/src/main/java/org/osm2world/core/world/attachment/AttachmentSurface.java @@ -1,9 +1,7 @@ package org.osm2world.core.world.attachment; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.osm2world.core.math.AxisAlignedRectangleXZ.bboxUnion; -import static org.osm2world.core.math.GeometryUtil.closeLoop; import java.util.ArrayList; import java.util.Collection; @@ -13,8 +11,6 @@ import javax.annotation.Nullable; import org.osm2world.core.math.*; -import org.osm2world.core.target.common.FaceTarget; -import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.mesh.Geometry; import org.osm2world.core.target.common.mesh.Mesh; @@ -27,7 +23,7 @@ public class AttachmentSurface implements BoundedObject { private final Collection types; /** the faces (flat polygons) that define the surface. All faces are counterclockwise */ - private final Collection faces; + private final Collection faces; /** * the "bottom" elevation of the surface, e.g. the ground elevation for an outdoor feature, @@ -40,7 +36,7 @@ public class AttachmentSurface implements BoundedObject { private final Collection attachedConnectors = new ArrayList<>(); - public AttachmentSurface(Collection types, Collection faces, + public AttachmentSurface(Collection types, Collection faces, Function baseEleFunction) { if (types.isEmpty() || faces.isEmpty()) throw new IllegalArgumentException(); this.types = types; @@ -48,7 +44,7 @@ public AttachmentSurface(Collection types, Collection faces, this.baseEleFunction = baseEleFunction; } - public AttachmentSurface(Collection types, Collection faces) { + public AttachmentSurface(Collection types, Collection faces) { this(types, faces, pos -> faces.stream().flatMap(f -> f.verticesNoDup().stream()).mapToDouble(v -> v.y).min().orElseGet(() -> 0)); } @@ -57,7 +53,7 @@ public Collection getTypes() { return types; } - public Collection getFaces() { + public Collection getFaces() { return faces; } @@ -110,9 +106,9 @@ public static AttachmentSurface fromMeshes(Collection types, Iterable types, Iterable meshes, @Nullable Function baseEleFunction) { - List faces = new ArrayList<>(); + List faces = new ArrayList<>(); for (Mesh mesh : meshes) { - mesh.geometry.asTriangles().triangles.forEach(t -> faces.add(new FaceXYZ(t.vertices()))); + faces.addAll(mesh.geometry.asTriangles().triangles); } if (baseEleFunction != null) { @@ -123,44 +119,4 @@ public static AttachmentSurface fromMeshes(Collection types, Iterable types; - private final Collection faces = new ArrayList<>(); - private Function baseEleFunction = null; - - public Builder(String... types) { - this.types = asList(types); - } - - public void setBaseEleFunction(Function baseEleFunction) { - this.baseEleFunction = baseEleFunction; - } - - public AttachmentSurface build() { - if (baseEleFunction == null) { - return new AttachmentSurface(types, faces); - } else { - return new AttachmentSurface(types, faces, baseEleFunction); - } - } - - @Override - public void drawFace(Material material, List vs, List normals, - List> texCoordLists) { - vs = closeLoop(vs); - try { - faces.add(new FaceXYZ(vs)); - } catch (InvalidGeometryException e) { - // catch collinear faces - } - } - - @Override - public boolean reconstructFaces() { - return false; - } - - } - } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Building.java b/src/main/java/org/osm2world/core/world/modules/building/Building.java index dabea4de6..6d100db0c 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Building.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Building.java @@ -40,8 +40,6 @@ public class Building implements AreaWorldObject, ProceduralWorldObject { private Map> wallNodePolygonSegments = new HashMap<>(); - private Collection attachmentSurfaces = null; - public Building(MapArea area, Configuration config) { this.area = area; @@ -165,19 +163,17 @@ public void buildMeshesAndModels(Target target) { } @Override - public Collection getRawGroundFootprint() { - return List.of(); // BuildingParts return their own footprint if necessary + public Collection getAttachmentSurfaces() { + List result = new ArrayList<>(ProceduralWorldObject.super.getAttachmentSurfaces()); + for (BuildingPart part : parts) { + result.addAll(part.getAttachmentSurfaces()); + } + return result; } @Override - public Collection getAttachmentSurfaces() { - if (attachmentSurfaces == null) { - attachmentSurfaces = new ArrayList<>(); - for (BuildingPart part : parts) { - attachmentSurfaces.addAll(part.getAttachmentSurfaces()); - } - } - return attachmentSurfaces; + public Collection getRawGroundFootprint() { + return List.of(); // BuildingParts return their own footprint if necessary } public record NodeWithLevelAndHeights( diff --git a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java index cdfa1261f..41f82a1e1 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java +++ b/src/main/java/org/osm2world/core/world/modules/building/BuildingPart.java @@ -486,10 +486,6 @@ public Collection getAttachmentSurfaces() { surfaces.addAll(roof.getAttachmentSurfaces( building.getGroundLevelEle() + levelStructure.heightWithoutRoof(), roofAttachmentLevel)); - for (ExteriorBuildingWall wall : walls) { - surfaces.addAll(wall.getAttachmentSurfaces()); - } - return surfaces; } diff --git a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java index a3a94372a..71a01b866 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java @@ -26,12 +26,10 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.PolylineShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; -import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.LODRange; import org.osm2world.core.util.ConfigUtil; -import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level; @@ -88,10 +86,6 @@ public ExteriorBuildingWall(@Nullable MapWay wallWay, BuildingPart buildingPart, } public void renderTo(ProceduralWorldObject.Target target) { - renderTo(target, true); - } - - public void renderTo(CommonTarget target, boolean renderElements) { BuildingDefaults defaults = BuildingDefaults.getDefaultsFor(tags); @@ -198,10 +192,7 @@ public void renderTo(CommonTarget target, boolean renderElements) { for (WindowImplementation windowImplementation : windowImplementations.keySet()) { LODRange lodRange = windowImplementations.get(windowImplementation); - - if (target instanceof ProceduralWorldObject.Target pt) { - pt.setCurrentLodRange(lodRange); - } + target.setCurrentLodRange(lodRange); /* construct the surface(s) */ @@ -327,30 +318,19 @@ public void renderTo(CommonTarget target, boolean renderElements) { mainSurface.renderTo(target, new VectorXZ(0, -floorHeight), hasWindows && !individuallyMappedWindows && windowImplementation == WindowImplementation.FLAT_TEXTURES, - windowHeight, renderElements); + windowHeight, + "wall", "wall_mounted"); } if (roofSurface != null) { - roofSurface.renderTo(target, NULL_VECTOR, false, windowHeight, renderElements); + roofSurface.renderTo(target, NULL_VECTOR, false, windowHeight, + "wall", "wall_mounted"); } } - if (target instanceof ProceduralWorldObject.Target pt) { - pt.setCurrentLodRange(null); - } - - } + target.setCurrentLodRange(null); - public Collection getAttachmentSurfaces() { - try { - AttachmentSurface.Builder builder = new AttachmentSurface.Builder("wall", "wall_mounted"); - this.renderTo(builder, false); - return singleton(builder.build()); - } catch (Exception e) { - ConversionLog.error("Could not create wall attachment surface for an element", e, wallWay); - return emptyList(); - } } @Override diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java index dae5ed49b..7067f9b9d 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java @@ -27,12 +27,12 @@ import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; -import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.TextureDataDimensions; import org.osm2world.core.target.common.material.TextureLayer; import org.osm2world.core.target.common.texcoord.NamedTexCoordFunction; import org.osm2world.core.target.common.texcoord.TexCoordFunction; +import org.osm2world.core.world.data.ProceduralWorldObject; /** * a simplified representation of a wall as a 2D plane, with its origin in the bottom left corner. @@ -158,38 +158,33 @@ public void addElementIfSpaceFree(WallElement element) { * * @param textureOrigin the origin of the texture coordinates on the wall surface * @param windowHeight the height for window textures will be replaced with this value if it's non-null - * @param renderElements whether the {@link WallElement}s inserted into this surface should also be rendered */ - public void renderTo(CommonTarget target, VectorXZ textureOrigin, - boolean applyWindowTexture, Double windowHeight, boolean renderElements) { + public void renderTo(ProceduralWorldObject.Target target, VectorXZ textureOrigin, + boolean applyWindowTexture, Double windowHeight, String... attachmentTypes) { /* render the elements on the wall */ - if (renderElements) { - for (WallElement e : elements) { - e.renderTo(target, this); - } + for (WallElement e : elements) { + e.renderTo(target, this); } /* draw insets around the elements */ - if (renderElements) { - for (WallElement e : elements) { - if (e.insetDistance() > 0) { + for (WallElement e : elements) { + if (e.insetDistance() > 0) { - PolygonXYZ frontOutline = convertTo3D(e.outline()); - PolygonXYZ backOutline = frontOutline.add(normalAt(e.outline().getCentroid()).mult(-e.insetDistance())); + PolygonXYZ frontOutline = convertTo3D(e.outline()); + PolygonXYZ backOutline = frontOutline.add(normalAt(e.outline().getCentroid()).mult(-e.insetDistance())); - List vsWall = createTriangleStripBetween( - backOutline.vertices(), frontOutline.vertices()); + List vsWall = createTriangleStripBetween( + backOutline.vertices(), frontOutline.vertices()); - Material material = this.material; - // TODO attempt to simulate ambient occlusion with a different baked ao texture? + Material material = this.material; + // TODO attempt to simulate ambient occlusion with a different baked ao texture? - target.drawTriangleStrip(material, vsWall, - texCoordLists(vsWall, material, NamedTexCoordFunction.STRIP_WALL)); + target.drawTriangleStrip(material, vsWall, + texCoordLists(vsWall, material, NamedTexCoordFunction.STRIP_WALL)); - } } } @@ -257,7 +252,9 @@ public void renderTo(CommonTarget target, VectorXZ textureOrigin, /* render the wall */ + target.setCurrentAttachmentTypes(attachmentTypes); target.drawTriangles(material, trianglesXYZ, texCoordLists); + target.setCurrentAttachmentTypes(); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java index b62d5bd0c..6d2d44b6c 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/BuildingPartInterior.java @@ -52,10 +52,6 @@ public Collection getAttachmentSurfaces() { surfaces.addAll(area.getAttachmentSurfaces()); } - for (IndoorWall wall : walls) { - surfaces.addAll(wall.getAttachmentSurfaces()); - } - } return surfaces; diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java index 3718ca7ad..a0fa62bf2 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/Ceiling.java @@ -1,6 +1,5 @@ package org.osm2world.core.world.modules.building.indoor; -import static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.GLOBAL_X_Z; @@ -8,7 +7,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import org.osm2world.core.math.*; @@ -16,7 +14,7 @@ import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; -import org.osm2world.core.world.attachment.AttachmentSurface; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.BuildingPart; public class Ceiling { @@ -28,8 +26,6 @@ public class Ceiling { private Boolean render; final int level; - private AttachmentSurface attachmentSurface; - Ceiling(BuildingPart buildingPart, Material material, PolygonWithHolesXZ polygon, double floorHeightAboveBase, Boolean renderable, int level){ this.buildingPart = buildingPart; this.material = material; @@ -39,23 +35,7 @@ public class Ceiling { this.level = level; } - public Collection getAttachmentSurfaces() { - - if (polygon == null) { - return emptyList(); - } - - if (attachmentSurface == null) { - AttachmentSurface.Builder builder = new AttachmentSurface.Builder("ceiling" + this.level); - this.renderSurface(builder, buildingPart.getBuilding().getGroundLevelEle() + floorHeight); - attachmentSurface = builder.build(); - } - - return Collections.singleton(attachmentSurface); - - } - - public void renderTo(CommonTarget target) { + void renderTo(ProceduralWorldObject.Target target) { if (render && polygon != null) { @@ -71,15 +51,17 @@ public void renderTo(CommonTarget target) { } } - private void renderSurface(CommonTarget target, double floorEle){ + private void renderSurface(ProceduralWorldObject.Target target, double floorEle){ Collection triangles = TriangulationUtil.triangulate(polygon); List trianglesXYZ = triangles.stream() .map(t -> t.makeClockwise().xyz(floorEle - 0.2)) .collect(toList()); + target.setCurrentAttachmentTypes("ceiling" + this.level); target.drawTriangles(material, trianglesXYZ, triangleTexCoordLists(trianglesXYZ, material, GLOBAL_X_Z)); + target.setCurrentAttachmentTypes(); } private void renderSides(CommonTarget target, List sides, double floorEle) { diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java index d0eea4b8e..bc2e947a8 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorFloor.java @@ -2,7 +2,6 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toList; import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.GLOBAL_X_Z; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.triangleTexCoordLists; @@ -14,10 +13,10 @@ import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.algorithms.CAGUtil; import org.osm2world.core.math.shapes.PolygonShapeXZ; -import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.attachment.AttachmentConnector; import org.osm2world.core.world.attachment.AttachmentSurface; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.BuildingPart; public class IndoorFloor { @@ -49,36 +48,27 @@ public Collection getAttachmentSurfaces() { } if (attachmentSurface == null) { - AttachmentSurface.Builder builder = new AttachmentSurface.Builder("floor" + this.level); - boolean tempRender = this.render; - this.render = true; - this.renderTo(builder, true); - this.render = tempRender; - attachmentSurface = builder.build(); + List triangles = triangulateFloorPolygons(singleton(polygon)); + attachmentSurface = new AttachmentSurface(List.of("floor" + this.level), triangles); } - List surfaces = new ArrayList<>(singleton(attachmentSurface)); - surfaces.addAll(ceiling.getAttachmentSurfaces()); - - return surfaces; + return List.of(attachmentSurface); } - private void renderTo(CommonTarget target, boolean attachmentSurfaceBool) { + void renderTo(ProceduralWorldObject.Target target) { - if (!attachmentSurfaceBool && level != buildingPart.levelStructure.levels.get(0).level) { + if (level != buildingPart.levelStructure.levels.get(0).level) { ceiling.renderTo(target); } if (render && polygon != null) { - double floorEle = buildingPart.getBuilding().getGroundLevelEle() + floorHeight + 0.0001; - /* subtract attached areas from the floor polygon */ List subtractPolys = new ArrayList<>(); - if (!attachmentSurfaceBool && attachmentSurface != null) { + if (attachmentSurface != null) { for (AttachmentConnector connector : attachmentSurface.getAttachedConnectors()) { if (connector.object != null) { subtractPolys.addAll(connector.object.getRawGroundFootprint()); @@ -97,10 +87,7 @@ private void renderTo(CommonTarget target, boolean attachmentSurfaceBool) { /* triangulate and render the (remaining) floor polygons */ - List trianglesXYZ = polygons.stream() - .flatMap(p -> p.getTriangulation().stream()) - .map(t -> t.makeCounterclockwise().xyz(floorEle)) - .collect(toList()); + List trianglesXYZ = triangulateFloorPolygons(polygons); target.drawTriangles(material, trianglesXYZ, triangleTexCoordLists(trianglesXYZ, material, GLOBAL_X_Z)); @@ -108,7 +95,12 @@ private void renderTo(CommonTarget target, boolean attachmentSurfaceBool) { } } - public void renderTo(CommonTarget target) { - renderTo(target, false); - } + private List triangulateFloorPolygons(Collection polygons) { + double floorEle = buildingPart.getBuilding().getGroundLevelEle() + floorHeight + 0.0001; + return polygons.stream() + .flatMap(p -> p.getTriangulation().stream()) + .map(t -> t.makeCounterclockwise().xyz(floorEle)) + .toList(); + } + } diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java index c5de1602b..720770fda 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorModule.java @@ -213,8 +213,8 @@ public void buildMeshesAndModels(Target target) { } } - frontSurface.renderTo(target, new VectorXZ(0, carBaseEle), false, null, true); - backSurface.renderTo(target, new VectorXZ(0, carBaseEle), false, null, true); + frontSurface.renderTo(target, new VectorXZ(0, carBaseEle), false, null); + backSurface.renderTo(target, new VectorXZ(0, carBaseEle), false, null); } diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java index b7941845b..9126ebb41 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorRoom.java @@ -2,9 +2,7 @@ import static java.util.Collections.emptyList; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import org.apache.commons.lang.math.IntRange; import org.osm2world.core.map_data.data.MapArea; @@ -46,15 +44,7 @@ public class IndoorRoom implements AreaWorldObject, ProceduralWorldObject { } public Collection getAttachmentSurfaces() { - - List surfaces = new ArrayList<>(); - - surfaces.addAll(floor.getAttachmentSurfaces()); - surfaces.addAll(ceiling.getAttachmentSurfaces()); - surfaces.addAll(wall.getAttachmentSurfaces()); - - return surfaces; - + return floor.getAttachmentSurfaces(); } @Override diff --git a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java index 3bdd1bb2b..f12a2b22b 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/indoor/IndoorWall.java @@ -21,7 +21,7 @@ import org.osm2world.core.target.CommonTarget; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; -import org.osm2world.core.world.attachment.AttachmentSurface; +import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.modules.building.*; import org.osm2world.core.world.modules.building.roof.Roof; @@ -43,8 +43,6 @@ public class IndoorWall { private final IndoorObjectData data; - private List attachmentSurfacesList; - private static final Material defaultInnerMaterial = Materials.CONCRETE; //TODO account for height of wall @@ -139,16 +137,6 @@ private void splitIntoWalls(){ } } - public Collection getAttachmentSurfaces() { - - if (attachmentSurfacesList == null) { - attachmentSurfacesList = new ArrayList<>(); - this.renderTo(null, false, true); - } - - return attachmentSurfacesList; - } - public class SegmentNodes { private List nodes; @@ -793,7 +781,7 @@ public static void renderNodePolygons(CommonTarget target, Map endPoints = getNewEndPoints(wallSegData, level, baseEle + data.getBuildingPart().levelStructure.level(level).relativeEle, ceilingHeight); @@ -850,49 +829,44 @@ private void renderTo(CommonTarget target, Boolean renderSides, Boolean attachme WallSurface backSurface = new WallSurface(material, backBottomPoints, backTopPoints); - /* generate wall edges */ WallSurface rightSurface = null; WallSurface leftSurface = null; - if (renderSides) { - - // TODO avoid needing a try + // TODO avoid needing a try - try { + try { - List bottomVertexLoop = new ArrayList<>(endPoints); - bottomVertexLoop.add(endPoints.get(0)); + List bottomVertexLoop = new ArrayList<>(endPoints); + bottomVertexLoop.add(endPoints.get(0)); - SimplePolygonXZ bottomPolygonXZ = new SimplePolygonXZ(bottomVertexLoop); - List bottomTriangles = TriangulationUtil. - triangulate(bottomPolygonXZ.asPolygonWithHolesXZ()) - .stream() - .map(t -> t.makeClockwise().xyz(baseEle + data.getBuildingPart().levelStructure.level(level).relativeEle)) - .collect(toList()); + SimplePolygonXZ bottomPolygonXZ = new SimplePolygonXZ(bottomVertexLoop); + List bottomTriangles = TriangulationUtil. + triangulate(bottomPolygonXZ.asPolygonWithHolesXZ()) + .stream() + .map(t -> t.makeClockwise().xyz(baseEle + data.getBuildingPart().levelStructure.level(level).relativeEle)) + .collect(toList()); - List tempTopTriangles = TriangulationUtil. - triangulate(bottomPolygonXZ.asPolygonWithHolesXZ()) - .stream() - .map(t -> t.makeCounterclockwise().xyz(ceilingHeight - topOffset)) - .collect(toList()); + List tempTopTriangles = TriangulationUtil. + triangulate(bottomPolygonXZ.asPolygonWithHolesXZ()) + .stream() + .map(t -> t.makeCounterclockwise().xyz(ceilingHeight - topOffset)) + .collect(toList()); - target.drawTriangles(defaultInnerMaterial, bottomTriangles, triangleTexCoordLists(bottomTriangles, material, GLOBAL_X_Z)); - target.drawTriangles(defaultInnerMaterial, tempTopTriangles, triangleTexCoordLists(tempTopTriangles, material, GLOBAL_X_Z)); + target.drawTriangles(defaultInnerMaterial, bottomTriangles, triangleTexCoordLists(bottomTriangles, material, GLOBAL_X_Z)); + target.drawTriangles(defaultInnerMaterial, tempTopTriangles, triangleTexCoordLists(tempTopTriangles, material, GLOBAL_X_Z)); - rightSurface = new WallSurface(material, - asList(bottomPoints.get(1), backBottomPoints.get(0)), - asList(topPoints.get(0), backTopPoints.get(backBottomPoints.size() - 1))); + rightSurface = new WallSurface(material, + asList(bottomPoints.get(1), backBottomPoints.get(0)), + asList(topPoints.get(0), backTopPoints.get(backBottomPoints.size() - 1))); - leftSurface = new WallSurface(material, - asList(backBottomPoints.get(1), bottomPoints.get(0)), - asList(backTopPoints.get(0), topPoints.get(topPoints.size() - 1))); - - } catch (InvalidGeometryException e) { - } + leftSurface = new WallSurface(material, + asList(backBottomPoints.get(1), bottomPoints.get(0)), + asList(backTopPoints.get(0), topPoints.get(topPoints.size() - 1))); + } catch (InvalidGeometryException e) { } /* add windows that aren't on vertices */ @@ -939,12 +913,12 @@ private void renderTo(CommonTarget target, Boolean renderSides, Boolean attachme /* draw wall */ if (mainSurface != null && backSurface != null) { - somethingRendered = true; - mainSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, !attachmentSurfaces); - backSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, !attachmentSurfaces); + String[] attachmentTypes = new String[] {"wall" + level, "wall"}; + mainSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, attachmentTypes); + backSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, attachmentTypes); if (leftSurface != null && rightSurface != null) { - rightSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, true); - leftSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null, true); + rightSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null); + leftSurface.renderTo(target, new VectorXZ(0, -floorHeight), false, null); } } } else { @@ -953,15 +927,7 @@ private void renderTo(CommonTarget target, Boolean renderSides, Boolean attachme } } - if (attachmentSurfaces && somethingRendered) { - attachmentSurfacesList.add(builder.build()); - } - } } - public void renderTo(CommonTarget target) { - renderTo(target, true, false); - } - } diff --git a/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java b/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java index de0620a1a..bf17909da 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java +++ b/src/main/java/org/osm2world/core/world/modules/building/roof/HeightfieldRoof.java @@ -1,6 +1,5 @@ package org.osm2world.core.world.modules.building.roof; -import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static org.osm2world.core.math.GeometryUtil.distanceFromLineSegment; @@ -101,16 +100,14 @@ public Collection getAttachmentSurfaces(double baseEle, int l if (attachmentSurface == null) { - String[] types = new String[] {"roof" + level, "roof", "floor" + level}; + List types = List.of("roof" + level, "roof", "floor" + level); try { - AttachmentSurface.Builder builder = new AttachmentSurface.Builder(types); - this.renderTo(builder, baseEle); - attachmentSurface = builder.build(); + attachmentSurface = new AttachmentSurface(types, getRoofTriangles(baseEle)); } catch (Exception e) { ConversionLog.error("Suppressed exception in HeightfieldRoof attachment surface generation", e); PolygonXYZ flatPolygon = getPolygon().getOuter().xyz(baseEle); - attachmentSurface = new AttachmentSurface(asList(types), asList(new FaceXYZ(flatPolygon.vertices()))); + attachmentSurface = new AttachmentSurface(types, List.of(new FaceXYZ(flatPolygon.vertices()))); } } @@ -122,6 +119,17 @@ public Collection getAttachmentSurfaces(double baseEle, int l @Override public void renderTo(CommonTarget target, double baseEle) { + List trianglesXYZ = getRoofTriangles(baseEle); + + /* draw triangles */ + + target.drawTriangles(material, trianglesXYZ, + triangleTexCoordLists(trianglesXYZ, material, SLOPED_TRIANGLES)); + + } + + private List getRoofTriangles(double baseEle) { + /* subtract attached rooftop areas (parking, helipads, pools, etc.) from the roof polygon */ List subtractPolys = new ArrayList<>(); @@ -153,10 +161,7 @@ public void renderTo(CommonTarget target, double baseEle) { trianglesXYZ.add(tCCW.xyz(v -> v.xyz(baseEle + getRoofHeightAt(v)))); } - /* draw triangles */ - - target.drawTriangles(material, trianglesXYZ, - triangleTexCoordLists(trianglesXYZ, material, SLOPED_TRIANGLES)); + return trianglesXYZ; } diff --git a/src/main/java/org/osm2world/viewer/view/debug/AttachmentSurfaceDebugView.java b/src/main/java/org/osm2world/viewer/view/debug/AttachmentSurfaceDebugView.java index 7fb6e8df6..32a4bfcba 100644 --- a/src/main/java/org/osm2world/viewer/view/debug/AttachmentSurfaceDebugView.java +++ b/src/main/java/org/osm2world/viewer/view/debug/AttachmentSurfaceDebugView.java @@ -3,12 +3,12 @@ import static java.awt.Color.ORANGE; import static java.util.Collections.emptyList; -import java.awt.Color; +import java.awt.*; import java.util.HashMap; import java.util.Map; import java.util.Random; -import org.osm2world.core.math.PolygonXYZ; +import org.osm2world.core.math.FlatSimplePolygonShapeXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.target.common.material.ImmutableMaterial; import org.osm2world.core.target.common.material.Material; @@ -51,13 +51,13 @@ protected void fillTarget(JOGLTarget target) { String type = surface.getTypes().iterator().next(); Color color = getOrCreateColor(type); - for (PolygonXYZ face : surface.getFaces()) { + for (FlatSimplePolygonShapeXYZ face : surface.getFaces()) { Material material = new ImmutableMaterial(Interpolation.FLAT, color); target.drawConvexPolygon(material, face.vertices(), emptyList()); //draw base ele - for (int i = 0; i < face.size(); i++) { + for (int i = 0; i < face.vertices().size() - 1; i++) { VectorXYZ v1 = face.vertices().get(i); VectorXYZ v2 = face.vertices().get(i + 1); v1 = v1.y(surface.getBaseEleAt(v1.xz())); From 994930d0a2713f5882b90a1d3d82461dcf17e6d1 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 19 Nov 2024 17:48:18 +0100 Subject: [PATCH 59/85] Cache results of expensive buildMeshesAndModels calls --- .../data/CachingProceduralWorldObject.java | 42 +++++++++++++++++++ .../world/data/ProceduralWorldObject.java | 6 +-- .../core/world/modules/TreeModule.java | 2 +- .../core/world/modules/building/Building.java | 6 +-- 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java diff --git a/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java new file mode 100644 index 000000000..a6f52041c --- /dev/null +++ b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java @@ -0,0 +1,42 @@ +package org.osm2world.core.world.data; + +import java.util.Collection; +import java.util.List; + +import org.osm2world.core.target.common.mesh.Mesh; +import org.osm2world.core.target.common.model.ModelInstance; +import org.osm2world.core.world.attachment.AttachmentSurface; + +/** + * subtype of {@link ProceduralWorldObject} which caches internal results to avoid repeated calculations + */ +abstract public class CachingProceduralWorldObject implements ProceduralWorldObject { + + private ProceduralWorldObject.Target target = null; + + private void fillTargetIfNecessary() { + if (target == null) { + target = new ProceduralWorldObject.Target(); + buildMeshesAndModels(target); + } + } + + @Override + public List buildMeshes() { + fillTargetIfNecessary(); + return target.meshes; + } + + @Override + public List getSubModels() { + fillTargetIfNecessary(); + return target.subModels; + } + + @Override + public Collection getAttachmentSurfaces() { + fillTargetIfNecessary(); + return target.attachmentSurfaces; + } + +} diff --git a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java index 46f0dfdb6..f71a1031a 100644 --- a/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/ProceduralWorldObject.java @@ -25,9 +25,9 @@ public interface ProceduralWorldObject extends WorldObject { class Target implements CommonTarget { - private final List meshes = new ArrayList<>(); - private final List subModels = new ArrayList<>(); - private final List attachmentSurfaces = new ArrayList<>(); + protected final List meshes = new ArrayList<>(); + protected final List subModels = new ArrayList<>(); + protected final List attachmentSurfaces = new ArrayList<>(); private @Nullable LODRange currentLodRange = null; private List currentAttachmentTypes = List.of(); diff --git a/src/main/java/org/osm2world/core/world/modules/TreeModule.java b/src/main/java/org/osm2world/core/world/modules/TreeModule.java index 1d02ab938..f32ace7a6 100644 --- a/src/main/java/org/osm2world/core/world/modules/TreeModule.java +++ b/src/main/java/org/osm2world/core/world/modules/TreeModule.java @@ -501,7 +501,7 @@ public void buildMeshesAndModels(Target target) { } - public class Forest implements AreaWorldObject, ProceduralWorldObject { + public class Forest extends CachingProceduralWorldObject implements AreaWorldObject { private final MapArea area; private final MapData mapData; diff --git a/src/main/java/org/osm2world/core/world/modules/building/Building.java b/src/main/java/org/osm2world/core/world/modules/building/Building.java index 6d100db0c..29ce26eab 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Building.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Building.java @@ -24,13 +24,13 @@ import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; -import org.osm2world.core.world.data.ProceduralWorldObject; +import org.osm2world.core.world.data.CachingProceduralWorldObject; import org.osm2world.core.world.modules.building.indoor.IndoorWall; /** * a building. Rendering a building is implemented as rendering all of its {@link BuildingPart}s. */ -public class Building implements AreaWorldObject, ProceduralWorldObject { +public class Building extends CachingProceduralWorldObject implements AreaWorldObject { private final MapArea area; @@ -164,7 +164,7 @@ public void buildMeshesAndModels(Target target) { @Override public Collection getAttachmentSurfaces() { - List result = new ArrayList<>(ProceduralWorldObject.super.getAttachmentSurfaces()); + List result = new ArrayList<>(super.getAttachmentSurfaces()); for (BuildingPart part : parts) { result.addAll(part.getAttachmentSurfaces()); } From 6f1121263b1bd0f88d4682b8f5b5453e7337115f Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 20 Nov 2024 12:10:04 +0100 Subject: [PATCH 60/85] Ignore 0 values for measurement keys such as width --- .../creation/EleTagEleCalculator.java | 2 +- .../osm2world/core/util/ValueParseUtil.java | 100 ++++++++++-------- .../core/world/modules/RoadModule.java | 7 +- .../modules/building/LevelAndHeightData.java | 3 +- .../modules/common/WorldModuleParseUtil.java | 11 +- 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/osm2world/core/map_elevation/creation/EleTagEleCalculator.java b/src/main/java/org/osm2world/core/map_elevation/creation/EleTagEleCalculator.java index 11efd8679..43fe36516 100644 --- a/src/main/java/org/osm2world/core/map_elevation/creation/EleTagEleCalculator.java +++ b/src/main/java/org/osm2world/core/map_elevation/creation/EleTagEleCalculator.java @@ -12,7 +12,7 @@ public class EleTagEleCalculator extends TagEleCalculator { @Override protected Double getEleForTags(TagSet tags, double terrainEle) { if (tags.containsKey("ele")) { - return parseOsmDecimal(tags.getValue("ele"), true); + return parseOsmDecimal(tags.getValue("ele"), null); } else { return null; } diff --git a/src/main/java/org/osm2world/core/util/ValueParseUtil.java b/src/main/java/org/osm2world/core/util/ValueParseUtil.java index 59fc0ad1c..f7a8b5db9 100644 --- a/src/main/java/org/osm2world/core/util/ValueParseUtil.java +++ b/src/main/java/org/osm2world/core/util/ValueParseUtil.java @@ -1,20 +1,36 @@ package org.osm2world.core.util; -import org.osm2world.core.util.color.ColorNameDefinition; +import static org.osm2world.core.util.ValueParseUtil.ValueConstraint.NONNEGATIVE; +import static org.osm2world.core.util.ValueParseUtil.ValueConstraint.POSITIVE; -import javax.annotation.Nullable; import java.awt.*; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; + +import org.osm2world.core.util.color.ColorNameDefinition; + /** parses the syntax of typical OSM tag values */ public final class ValueParseUtil { /** prevents instantiation */ private ValueParseUtil() { } + public enum ValueConstraint implements Predicate { + POSITIVE, NONNEGATIVE; + @Override + public boolean test(Double value) { + return switch (this) { + case POSITIVE -> value > 0; + case NONNEGATIVE -> value >= 0; + }; + } + } + /** pattern that splits into a part before and after a decimal point */ private static final Pattern DEC_POINT_PATTERN = Pattern.compile("^(\\-?\\d+)\\.(\\d+)$"); @@ -68,7 +84,7 @@ public static final int parseInt(@Nullable String value, int defaultValue) { * * @return the parsed value as a floating point number; null if value is null or has syntax errors. */ - public static final @Nullable Double parseOsmDecimal(@Nullable String value, boolean allowNegative) { + public static final @Nullable Double parseOsmDecimal(@Nullable String value, @Nullable Predicate constraint) { if (value == null) return null; @@ -76,12 +92,12 @@ public static final int parseInt(@Nullable String value, int defaultValue) { try { - int weight = Integer.parseInt(value); - if (weight >= 0 || allowNegative) { - return (double)weight; + double result = Integer.parseInt(value); + if (constraint == null || constraint.test(result)) { + return result; } - } catch (NumberFormatException nfe) {} + } catch (NumberFormatException ignored) {} /* positive number with decimal point */ @@ -92,7 +108,7 @@ public static final int parseInt(@Nullable String value, int defaultValue) { String stringBeforePoint = matcher.group(1); String stringAfterPoint = matcher.group(2); - if (stringBeforePoint.length() > 0 || stringAfterPoint.length() > 0) { + if (!stringBeforePoint.isEmpty() || !stringAfterPoint.isEmpty()) { try { @@ -105,11 +121,11 @@ public static final int parseInt(@Nullable String value, int defaultValue) { + Math.pow(10, -stringAfterPoint.length()) * afterPoint; if (negative) { result = - result; } - if (result >= 0 || allowNegative) { + if (constraint == null || constraint.test(result)) { return result; } - } catch (NumberFormatException nfe) {} + } catch (NumberFormatException ignored) {} } } @@ -117,9 +133,9 @@ public static final int parseInt(@Nullable String value, int defaultValue) { return null; } - /** variant of {@link #parseOsmDecimal(String, boolean)} with a default value */ - public static final double parseOsmDecimal(@Nullable String value, boolean allowNegative, double defaultValue) { - Double result = parseOsmDecimal(value, allowNegative); + /** variant of {@link #parseOsmDecimal(String, Predicate)} with a default value */ + public static double parseOsmDecimal(@Nullable String value, Predicate constraint, double defaultValue) { + Double result = parseOsmDecimal(value, constraint); return result == null ? defaultValue : result; } @@ -140,7 +156,7 @@ public static final double parseOsmDecimal(@Nullable String value, boolean allow /* try numeric speed (implied km/h) */ - Double speed = parseOsmDecimal(value, false); + Double speed = parseOsmDecimal(value, POSITIVE); if (speed != null) { return speed; } @@ -190,13 +206,13 @@ public static final double parseSpeed(@Nullable String value, double defaultValu * * @return measure in m; null if value is null or has syntax errors. */ - public static final @Nullable Double parseMeasure(@Nullable String value) { + public static @Nullable Double parseMeasure(@Nullable String value) { if (value == null) return null; /* try numeric measure (implied m) */ - Double measure = parseOsmDecimal(value, false); + Double measure = parseOsmDecimal(value, POSITIVE); if (measure != null) { return measure; } @@ -206,7 +222,7 @@ public static final double parseSpeed(@Nullable String value, double defaultValu Matcher mMatcher = M_PATTERN.matcher(value); if (mMatcher.matches()) { String mString = mMatcher.group(1); - return parseOsmDecimal(mString, false); + return parseOsmDecimal(mString, POSITIVE); } /* try km measure */ @@ -214,7 +230,7 @@ public static final double parseSpeed(@Nullable String value, double defaultValu Matcher kmMatcher = KM_PATTERN.matcher(value); if (kmMatcher.matches()) { String kmString = kmMatcher.group(1); - double km = parseOsmDecimal(kmString, false); + double km = parseOsmDecimal(kmString, POSITIVE); return 1000 * km; } @@ -223,7 +239,7 @@ public static final double parseSpeed(@Nullable String value, double defaultValu Matcher miMatcher = MI_PATTERN.matcher(value); if (miMatcher.matches()) { String miString = miMatcher.group(1); - double mi = parseOsmDecimal(miString, false); + double mi = parseOsmDecimal(miString, POSITIVE); return M_PER_MI * mi; } @@ -266,7 +282,7 @@ public static final double parseMeasure(@Nullable String value, double defaultVa /* try numeric weight (implied t) */ - Double weight = parseOsmDecimal(value, false); + Double weight = parseOsmDecimal(value, POSITIVE); if (weight != null) { return weight; } @@ -276,7 +292,7 @@ public static final double parseMeasure(@Nullable String value, double defaultVa Matcher tMatcher = T_PATTERN.matcher(value); if (tMatcher.matches()) { String tString = tMatcher.group(1); - return parseOsmDecimal(tString, false); + return parseOsmDecimal(tString, POSITIVE); } /* all possibilities failed */ @@ -305,7 +321,7 @@ public static final double parseWeight(@Nullable String value, double defaultVal Matcher inclineMatcher = INCLINE_PATTERN.matcher(value); if (inclineMatcher.matches()) { String inclineString = inclineMatcher.group(1); - return parseOsmDecimal(inclineString, true); + return parseOsmDecimal(inclineString, null); } return null; @@ -329,33 +345,33 @@ public static final double parseIncline(@Nullable String value, double defaultVa /* try numeric angle */ - Double measure = parseOsmDecimal(value, false); + Double measure = parseOsmDecimal(value, NONNEGATIVE); if (measure != null) { return measure % 360; } /* try cardinal directions (represented by letters) */ - switch (value) { - case "N" : return 0.0; - case "NNE": return 22.5; - case "NE" : return 45.0; - case "ENE": return 67.5; - case "E" : return 90.0; - case "ESE": return 112.5; - case "SE" : return 135.0; - case "SSE": return 157.5; - case "S" : return 180.0; - case "SSW": return 202.5; - case "SW" : return 225.0; - case "WSW": return 247.5; - case "W" : return 270.0; - case "WNW": return 292.5; - case "NW" : return 315.0; - case "NNW": return 337.5; - } + return switch (value) { + case "N" -> 0.0; + case "NNE" -> 22.5; + case "NE" -> 45.0; + case "ENE" -> 67.5; + case "E" -> 90.0; + case "ESE" -> 112.5; + case "SE" -> 135.0; + case "SSE" -> 157.5; + case "S" -> 180.0; + case "SSW" -> 202.5; + case "SW" -> 225.0; + case "WSW" -> 247.5; + case "W" -> 270.0; + case "WNW" -> 292.5; + case "NW" -> 315.0; + case "NNW" -> 337.5; + default -> null; + }; - return null; } /** variant of {@link #parseAngle(String)} with a default value */ diff --git a/src/main/java/org/osm2world/core/world/modules/RoadModule.java b/src/main/java/org/osm2world/core/world/modules/RoadModule.java index c5bad8948..d726b8d9b 100644 --- a/src/main/java/org/osm2world/core/world/modules/RoadModule.java +++ b/src/main/java/org/osm2world/core/world/modules/RoadModule.java @@ -17,6 +17,7 @@ import static org.osm2world.core.target.common.texcoord.NamedTexCoordFunction.*; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.texCoordLists; import static org.osm2world.core.target.common.texcoord.TexCoordUtil.triangleTexCoordLists; +import static org.osm2world.core.util.ValueParseUtil.ValueConstraint.POSITIVE; import static org.osm2world.core.util.ValueParseUtil.*; import static org.osm2world.core.util.color.ColorNameDefinitions.CSS_COLORS; import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.createLineBetween; @@ -833,7 +834,7 @@ private LaneLayout buildBasicLaneLayout() { Double lanes = null; if (tags.containsKey("lanes")) { - lanes = parseOsmDecimal(tags.getValue("lanes"), false); + lanes = parseOsmDecimal(tags.getValue("lanes"), POSITIVE); } Double lanesRight = null; @@ -846,7 +847,7 @@ private LaneLayout buildBasicLaneLayout() { if (laneTagsRight != null) { lanesRight = (double)laneTagsRight.length; } else if (tags.containsKey(rightKey)) { - lanesRight = parseOsmDecimal(tags.getValue(rightKey), false); + lanesRight = parseOsmDecimal(tags.getValue(rightKey), POSITIVE); } String leftKey = rightHandTraffic ? "lanes:backward" : "lanes:forward"; @@ -854,7 +855,7 @@ private LaneLayout buildBasicLaneLayout() { if (laneTagsLeft != null) { lanesLeft = (double)laneTagsLeft.length; } else if (tags.containsKey(leftKey)) { - lanesLeft = parseOsmDecimal(tags.getValue(leftKey), false); + lanesLeft = parseOsmDecimal(tags.getValue(leftKey), POSITIVE); } int vehicleLaneCount; diff --git a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java index 5e634fd84..65db0e208 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java +++ b/src/main/java/org/osm2world/core/world/modules/building/LevelAndHeightData.java @@ -7,6 +7,7 @@ import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; +import static org.osm2world.core.util.ValueParseUtil.ValueConstraint.NONNEGATIVE; import static org.osm2world.core.util.ValueParseUtil.*; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.inheritTags; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseHeight; @@ -117,7 +118,7 @@ public LevelAndHeightData(TagSet buildingTags, TagSet buildingPartTags, Map constraint) { if(tags.containsKey(key)) { - Double value = parseOsmDecimal(tags.getValue(key), false); + Double value = parseOsmDecimal(tags.getValue(key), constraint); if (value != null) { return(int) (double) value; } From 5e412ae1e77d55a3c0a3395327deb5c5207fd464 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 20 Nov 2024 13:33:00 +0100 Subject: [PATCH 61/85] Prevent exceptions in ParamFileDirMode from blocking threads --- .../java/org/osm2world/console/ParamFileDirMode.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/console/ParamFileDirMode.java b/src/main/java/org/osm2world/console/ParamFileDirMode.java index cdd7cbdfd..2b3c88b9e 100644 --- a/src/main/java/org/osm2world/console/ParamFileDirMode.java +++ b/src/main/java/org/osm2world/console/ParamFileDirMode.java @@ -1,6 +1,7 @@ package org.osm2world.console; import static java.util.Arrays.sort; +import static org.osm2world.core.conversion.ConversionLog.LogLevel.FATAL; import java.io.File; import java.io.IOException; @@ -12,6 +13,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import org.osm2world.core.conversion.ConversionLog; + /** * implementation of the mode triggered by {@link CLIArguments#isParameterFileDir()}. * OSM2World will read, process and delete parameter files from a directory. @@ -63,7 +66,12 @@ public static void run(File paramFileDir) { executor.submit(() -> { System.out.println(tempFilePath); - OSM2World.main(new String[] {"--parameterFile", tempFilePath.toString()}); + + try { + OSM2World.main(new String[]{"--parameterFile", tempFilePath.toString()}); + } catch (Exception e) { + ConversionLog.log(FATAL, "Run failed for " + tempFilePath, e, null); + } try { Files.delete(tempFilePath); From 38ea3d5bbb0711662843ab01319268f0d588e644 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 20 Nov 2024 17:10:39 +0100 Subject: [PATCH 62/85] Write conversion log even in case of fatal errors --- .../java/org/osm2world/console/Output.java | 375 +++++++++--------- 1 file changed, 194 insertions(+), 181 deletions(-) diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index e29c2c86c..5838fe80a 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -5,6 +5,7 @@ import static java.util.Map.entry; import static java.util.stream.Collectors.toList; import static org.osm2world.core.ConversionFacade.Phase.*; +import static org.osm2world.core.conversion.ConversionLog.LogLevel.FATAL; import static org.osm2world.core.math.AxisAlignedRectangleXZ.bbox; import java.io.File; @@ -66,235 +67,247 @@ public static void output(Configuration config, ConversionLog.setConsoleLogLevels(EnumSet.of(ConversionLog.LogLevel.FATAL)); } - OSMDataReader dataReader = null; - - switch (sharedArgs.getInputMode()) { - - case FILE: - File inputFile = sharedArgs.getInput(); - dataReader = switch (CLIArgumentsUtil.getInputFileType(sharedArgs)) { - case SIMPLE_FILE -> new OSMFileReader(inputFile); - case MBTILES -> new MbtilesReader(inputFile, sharedArgs.getTile()); - case GEODESK -> new GeodeskReader(inputFile, sharedArgs.getTile().bounds()); - }; - break; - - case OVERPASS: - if (sharedArgs.isInputBoundingBox()) { - LatLonBounds bounds = LatLonBounds.ofPoints(sharedArgs.getInputBoundingBox()); - dataReader = new OverpassReader(sharedArgs.getOverpassURL(), bounds); - } else if (sharedArgs.isTile()) { - LatLonBounds bounds = sharedArgs.getTile().bounds(); - dataReader = new OverpassReader(sharedArgs.getOverpassURL(), bounds); - } else { - assert sharedArgs.isInputQuery(); // can be assumed due to input validation - String query = sharedArgs.getInputQuery(); - dataReader = new OverpassReader(sharedArgs.getOverpassURL(), query); - } - break; + try { - } + OSMDataReader dataReader = null; + switch (sharedArgs.getInputMode()) { - MapMetadata metadata = null; + case FILE: + File inputFile = sharedArgs.getInput(); + dataReader = switch (CLIArgumentsUtil.getInputFileType(sharedArgs)) { + case SIMPLE_FILE -> new OSMFileReader(inputFile); + case MBTILES -> new MbtilesReader(inputFile, sharedArgs.getTile()); + case GEODESK -> new GeodeskReader(inputFile, sharedArgs.getTile().bounds()); + }; + break; - if (sharedArgs.isMetadataFile()) { - if (sharedArgs.getMetadataFile().getName().endsWith(".mbtiles")) { - if (sharedArgs.isMetadataFile() && sharedArgs.isTile()) { - try { - metadata = MapMetadata.metadataForTile(sharedArgs.getTile(), sharedArgs.getMetadataFile()); - } catch(MBTilesReadException e){ - System.err.println("Cannot read tile metadata: " + e); + case OVERPASS: + if (sharedArgs.isInputBoundingBox()) { + LatLonBounds bounds = LatLonBounds.ofPoints(sharedArgs.getInputBoundingBox()); + dataReader = new OverpassReader(sharedArgs.getOverpassURL(), bounds); + } else if (sharedArgs.isTile()) { + LatLonBounds bounds = sharedArgs.getTile().bounds(); + dataReader = new OverpassReader(sharedArgs.getOverpassURL(), bounds); + } else { + assert sharedArgs.isInputQuery(); // can be assumed due to input validation + String query = sharedArgs.getInputQuery(); + dataReader = new OverpassReader(sharedArgs.getOverpassURL(), query); } - } - } else { - metadata = MapMetadata.metadataFromJson(sharedArgs.getMetadataFile()); - } - } + break; + } - ConversionFacade cf = new ConversionFacade(); - cf.addProgressListener(perfListener); - String interpolatorType = config.getString("terrainInterpolator"); - if ("ZeroInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(ZeroInterpolator::new); - } else if ("LeastSquaresInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(LeastSquaresInterpolator::new); - } else if ("NaturalNeighborInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(NaturalNeighborInterpolator::new); - } + MapMetadata metadata = null; - String eleCalculatorName = config.getString("eleCalculator"); - if (eleCalculatorName != null) { - switch (eleCalculatorName) { - case "NoOpEleCalculator" -> cf.setEleCalculatorFactory(NoOpEleCalculator::new); - case "EleTagEleCalculator" -> cf.setEleCalculatorFactory(EleTagEleCalculator::new); - case "BridgeTunnelEleCalculator" -> cf.setEleCalculatorFactory(BridgeTunnelEleCalculator::new); - case "ConstraintEleCalculator" -> cf.setEleCalculatorFactory(() -> new ConstraintEleCalculator(new SimpleEleConstraintEnforcer())); + if (sharedArgs.isMetadataFile()) { + if (sharedArgs.getMetadataFile().getName().endsWith(".mbtiles")) { + if (sharedArgs.isMetadataFile() && sharedArgs.isTile()) { + try { + metadata = MapMetadata.metadataForTile(sharedArgs.getTile(), sharedArgs.getMetadataFile()); + } catch (MBTilesReadException e) { + System.err.println("Cannot read tile metadata: " + e); + } + } + } else { + metadata = MapMetadata.metadataFromJson(sharedArgs.getMetadataFile()); + } } - } - Results results = cf.createRepresentations(dataReader.getData(), metadata, null, config, null); - ImageExporter exporter = null; + ConversionFacade cf = new ConversionFacade(); + cf.addProgressListener(perfListener); - for (CLIArguments args : argumentsGroup.getCLIArgumentsList()) { + String interpolatorType = config.getString("terrainInterpolator"); + if ("ZeroInterpolator".equals(interpolatorType)) { + cf.setTerrainEleInterpolatorFactory(ZeroInterpolator::new); + } else if ("LeastSquaresInterpolator".equals(interpolatorType)) { + cf.setTerrainEleInterpolatorFactory(LeastSquaresInterpolator::new); + } else if ("NaturalNeighborInterpolator".equals(interpolatorType)) { + cf.setTerrainEleInterpolatorFactory(NaturalNeighborInterpolator::new); + } - /* set camera and projection */ + String eleCalculatorName = config.getString("eleCalculator"); + if (eleCalculatorName != null) { + switch (eleCalculatorName) { + case "NoOpEleCalculator" -> cf.setEleCalculatorFactory(NoOpEleCalculator::new); + case "EleTagEleCalculator" -> cf.setEleCalculatorFactory(EleTagEleCalculator::new); + case "BridgeTunnelEleCalculator" -> cf.setEleCalculatorFactory(BridgeTunnelEleCalculator::new); + case "ConstraintEleCalculator" -> + cf.setEleCalculatorFactory(() -> new ConstraintEleCalculator(new SimpleEleConstraintEnforcer())); + } + } - Camera camera = null; - Projection projection = null; + Results results = cf.createRepresentations(dataReader.getData(), metadata, null, config, null); - if (args.isPviewPos()) { + ImageExporter exporter = null; - /* perspective projection */ + for (CLIArguments args : argumentsGroup.getCLIArgumentsList()) { - MapProjection proj = results.getMapProjection(); + /* set camera and projection */ - LatLonEle pos = args.getPviewPos(); - LatLonEle lookAt = args.getPviewLookat(); + Camera camera = null; + Projection projection = null; - camera = new Camera(); - VectorXYZ posV = proj.toXZ(pos.lat, pos.lon).xyz(pos.ele); - VectorXYZ laV = proj.toXZ(lookAt.lat, lookAt.lon).xyz(lookAt.ele); - camera.setCamera(posV.x, posV.y, posV.z, laV.x, laV.y, laV.z); + if (args.isPviewPos()) { - projection = new Projection(false, - args.isPviewAspect() ? args.getPviewAspect() : - (double)args.getResolution().getAspectRatio(), - args.getPviewFovy(), - 0, - 1, 50000); + /* perspective projection */ - } else { + MapProjection proj = results.getMapProjection(); - /* orthographic projection */ + LatLonEle pos = args.getPviewPos(); + LatLonEle lookAt = args.getPviewLookat(); - double angle = args.getOviewAngle(); - CardinalDirection from = args.getOviewFrom(); + camera = new Camera(); + VectorXYZ posV = proj.toXZ(pos.lat, pos.lon).xyz(pos.ele); + VectorXYZ laV = proj.toXZ(lookAt.lat, lookAt.lon).xyz(lookAt.ele); + camera.setCamera(posV.x, posV.y, posV.z, laV.x, laV.y, laV.z); - AxisAlignedRectangleXZ bounds; + projection = new Projection(false, + args.isPviewAspect() ? args.getPviewAspect() : + (double) args.getResolution().getAspectRatio(), + args.getPviewFovy(), + 0, + 1, 50000); - if (args.isOviewBoundingBox()) { - bounds = bbox(args.getOviewBoundingBox().stream() - .map(results.getMapProjection()::toXZ) - .collect(toList())); - } else if (args.isOviewTiles()) { - bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), args.getOviewTiles()); - } else if (args.isTile()) { - bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); } else { - bounds = results.getMapData().getBoundary(); - } - - camera = OrthoTilesUtil.cameraForBounds(bounds, angle, from); - projection = OrthoTilesUtil.projectionForBounds(bounds, angle, from); - - } - - /* perform the actual output */ - for (File outputFile : args.getOutput()) { + /* orthographic projection */ - outputFile.getAbsoluteFile().getParentFile().mkdirs(); + double angle = args.getOviewAngle(); + CardinalDirection from = args.getOviewFrom(); - OutputMode outputMode = CLIArgumentsUtil.getOutputMode(outputFile); + AxisAlignedRectangleXZ bounds; - switch (outputMode) { - - case OBJ: - Integer primitiveThresholdOBJ = - config.getInteger("primitiveThresholdOBJ", null); - if (primitiveThresholdOBJ == null) { - boolean underground = config.getBoolean("renderUnderground", true); - - ObjWriter.writeObjFile(outputFile, - results.getMapData(), results.getMapProjection(), config, - camera, projection, underground); - } else { - ObjWriter.writeObjFiles(outputFile, - results.getMapData(), results.getMapProjection(), config, - camera, projection, primitiveThresholdOBJ); - } - break; - - case GLTF, GLB, GLTF_GZ, GLB_GZ: { - AxisAlignedRectangleXZ bounds = null; - if (args.isTile()) { + if (args.isOviewBoundingBox()) { + bounds = bbox(args.getOviewBoundingBox().stream() + .map(results.getMapProjection()::toXZ) + .collect(toList())); + } else if (args.isOviewTiles()) { + bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), args.getOviewTiles()); + } else if (args.isTile()) { bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); } else { bounds = results.getMapData().getBoundary(); } - GltfTarget.GltfFlavor gltfFlavor = EnumSet.of(OutputMode.GLB, OutputMode.GLB_GZ).contains(outputMode) - ? GltfTarget.GltfFlavor.GLB : GltfTarget.GltfFlavor.GLTF; - Compression compression = EnumSet.of(OutputMode.GLTF_GZ, OutputMode.GLB_GZ).contains(outputMode) - ? Compression.GZ : Compression.NONE; - GltfTarget gltfTarget = new GltfTarget(outputFile, gltfFlavor, compression, bounds); - gltfTarget.setConfiguration(config); - boolean underground = config.getBoolean("renderUnderground", true); - TargetUtil.renderWorldObjects(gltfTarget, results.getMapData(), underground); - gltfTarget.finish(); - } break; - - case POV: - POVRayWriter.writePOVInstructionFile(outputFile, - results.getMapData(), camera, projection); - break; - case WEB_PBF, WEB_PBF_GZ: { - AxisAlignedRectangleXZ bbox = null; - if (args.isTile()) { - bbox = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); - } - Compression compression = outputMode == OutputMode.WEB_PBF_GZ ? Compression.GZ : Compression.NONE; - FrontendPbfTarget.writePbfFile( - outputFile, results.getMapData(), bbox, results.getMapProjection(), compression); - } break; - - case PNG: - case PPM: - case GD: - if (camera == null || projection == null) { - System.err.println("camera or projection missing"); - } - if (exporter == null) { - exporter = ImageExporter.create( - config, results, argumentsGroup); + camera = OrthoTilesUtil.cameraForBounds(bounds, angle, from); + projection = OrthoTilesUtil.projectionForBounds(bounds, angle, from); + + } + + /* perform the actual output */ + + for (File outputFile : args.getOutput()) { + + outputFile.getAbsoluteFile().getParentFile().mkdirs(); + + OutputMode outputMode = CLIArgumentsUtil.getOutputMode(outputFile); + + switch (outputMode) { + + case OBJ: + Integer primitiveThresholdOBJ = + config.getInteger("primitiveThresholdOBJ", null); + if (primitiveThresholdOBJ == null) { + boolean underground = config.getBoolean("renderUnderground", true); + + ObjWriter.writeObjFile(outputFile, + results.getMapData(), results.getMapProjection(), config, + camera, projection, underground); + } else { + ObjWriter.writeObjFiles(outputFile, + results.getMapData(), results.getMapProjection(), config, + camera, projection, primitiveThresholdOBJ); + } + break; + + case GLTF, GLB, GLTF_GZ, GLB_GZ: { + AxisAlignedRectangleXZ bounds = null; + if (args.isTile()) { + bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); + } else { + bounds = results.getMapData().getBoundary(); + } + GltfTarget.GltfFlavor gltfFlavor = EnumSet.of(OutputMode.GLB, OutputMode.GLB_GZ).contains(outputMode) + ? GltfTarget.GltfFlavor.GLB : GltfTarget.GltfFlavor.GLTF; + Compression compression = EnumSet.of(OutputMode.GLTF_GZ, OutputMode.GLB_GZ).contains(outputMode) + ? Compression.GZ : Compression.NONE; + GltfTarget gltfTarget = new GltfTarget(outputFile, gltfFlavor, compression, bounds); + gltfTarget.setConfiguration(config); + boolean underground = config.getBoolean("renderUnderground", true); + TargetUtil.renderWorldObjects(gltfTarget, results.getMapData(), underground); + gltfTarget.finish(); + } + break; + + case POV: + POVRayWriter.writePOVInstructionFile(outputFile, + results.getMapData(), camera, projection); + break; + + case WEB_PBF, WEB_PBF_GZ: { + AxisAlignedRectangleXZ bbox = null; + if (args.isTile()) { + bbox = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); + } + Compression compression = outputMode == OutputMode.WEB_PBF_GZ ? Compression.GZ : Compression.NONE; + FrontendPbfTarget.writePbfFile( + outputFile, results.getMapData(), bbox, results.getMapProjection(), compression); + } + break; + + case PNG: + case PPM: + case GD: + if (camera == null || projection == null) { + System.err.println("camera or projection missing"); + } + if (exporter == null) { + exporter = ImageExporter.create( + config, results, argumentsGroup); + } + exporter.writeImageFile(outputFile, outputMode, + args.getResolution().width, args.getResolution().height, + camera, projection); + break; + } - exporter.writeImageFile(outputFile, outputMode, - args.getResolution().width, args.getResolution().height, - camera, projection); - break; } } - } + if (exporter != null) { + exporter.freeResources(); + exporter = null; + } - if (exporter != null) { - exporter.freeResources(); - exporter = null; - } + } catch (Exception e) { + ConversionLog.log(FATAL, "Conversion failed", e, null); + throw e; + } finally { - if (sharedArgs.getPerformancePrint()) { - long timeSec = Duration.between(perfListener.startTime, now()).getSeconds(); - System.out.println("finished after " + timeSec + " s"); - } + if (sharedArgs.getPerformancePrint()) { + long timeSec = Duration.between(perfListener.startTime, now()).getSeconds(); + System.out.println("finished after " + timeSec + " s"); + } - if (sharedArgs.isLogDir()) { + if (sharedArgs.isLogDir()) { - File logDir = sharedArgs.getLogDir(); - logDir.mkdirs(); + File logDir = sharedArgs.getLogDir(); + logDir.mkdirs(); - String fileNameBase = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")); - if (sharedArgs.isTile()) { - fileNameBase = sharedArgs.getTile().toString("_"); - } - fileNameBase = "osm2world_log_" + fileNameBase; + String fileNameBase = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")); + if (sharedArgs.isTile()) { + fileNameBase = sharedArgs.getTile().toString("_"); + } + fileNameBase = "osm2world_log_" + fileNameBase; + + writeLogFiles(logDir, fileNameBase, perfListener); - writeLogFiles(logDir, fileNameBase, perfListener); + } } From 0ea496e82e1b79588c15e9e7b669b9b805298598 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 21 Nov 2024 12:27:37 +0100 Subject: [PATCH 63/85] MapDataDebugView: Omit artificial area for empty terrain --- .../viewer/view/debug/MapDataDebugView.java | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/osm2world/viewer/view/debug/MapDataDebugView.java b/src/main/java/org/osm2world/viewer/view/debug/MapDataDebugView.java index 3fb5985e8..9b6e965f9 100644 --- a/src/main/java/org/osm2world/viewer/view/debug/MapDataDebugView.java +++ b/src/main/java/org/osm2world/viewer/view/debug/MapDataDebugView.java @@ -1,5 +1,12 @@ package org.osm2world.viewer.view.debug; +import static java.util.Collections.emptyList; +import static org.osm2world.core.map_data.creation.EmptyTerrainBuilder.EMPTY_SURFACE_VALUE; + +import java.awt.*; +import java.util.Collection; +import java.util.function.Predicate; + import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapNode; @@ -10,19 +17,12 @@ import org.osm2world.core.map_data.data.overlaps.MapOverlapWA; import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.TriangleXZ; -import org.osm2world.core.math.Vector3D; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.algorithms.TriangulationUtil; import org.osm2world.core.target.common.material.ImmutableMaterial; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.jogl.JOGLTarget; -import java.awt.*; -import java.util.Collection; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - /** * shows the plain {@link MapData} as a network of nodes, lines and areas */ @@ -49,21 +49,18 @@ public boolean canBeUsed() { @Override public void fillTarget(JOGLTarget target) { + Predicate isEmptyTerrain = it -> it.getTags().contains("surface", EMPTY_SURFACE_VALUE); + for (MapArea area : map.getMapAreas()) { - Vector3D[] vs = new Vector3D[area.getBoundaryNodes().size()]; - for (int i=0; i < area.getBoundaryNodes().size(); i++) { - vs[i] = area.getBoundaryNodes().get(i).getPos(); - } - Collection triangles = - TriangulationUtil.triangulate(area.getPolygon()); + if (isEmptyTerrain.test(area)) continue; - for (TriangleXZ t : triangles) { - target.drawTriangles( - new ImmutableMaterial(Interpolation.FLAT, AREA_COLOR), - singletonList(t.xyz(-0.1)), - emptyList()); - } + Collection triangles = TriangulationUtil.triangulate(area.getPolygon()); + + target.drawTriangles( + new ImmutableMaterial(Interpolation.FLAT, AREA_COLOR), + triangles.stream().map(t -> t.xyz(-0.1)).toList(), + emptyList()); } @@ -74,8 +71,8 @@ public void fillTarget(JOGLTarget target) { } for (MapNode node : map.getMapNodes()) { - drawBoxAround(target, node.getPos(), - NODE_COLOR, HALF_NODE_WIDTH); + if (node.getId() < 0 && node.getAdjacentAreas().stream().allMatch(isEmptyTerrain)) continue; + drawBoxAround(target, node.getPos(), NODE_COLOR, HALF_NODE_WIDTH); } for (MapWaySegment line : map.getMapWaySegments()) { @@ -86,22 +83,22 @@ public void fillTarget(JOGLTarget target) { } for (MapArea area : map.getMapAreas()) { + if (isEmptyTerrain.test(area)) continue; for (MapOverlap overlap : area.getOverlaps()) { - if (overlap instanceof MapOverlapWA) { - for (VectorXZ pos : ((MapOverlapWA)overlap).getIntersectionPositions()) { - drawBoxAround(target, pos, - INTERSECTION_COLOR, HALF_NODE_WIDTH); + if (overlap instanceof MapOverlapWA overlapWA) { + for (VectorXZ pos : overlapWA.getIntersectionPositions()) { + drawBoxAround(target, pos, INTERSECTION_COLOR, HALF_NODE_WIDTH); } - for (LineSegmentXZ seg : ((MapOverlapWA)overlap).getSharedSegments()) { + for (LineSegmentXZ seg : overlapWA.getSharedSegments()) { target.drawLineStrip(SHARED_SEGMENT_COLOR, 3, seg.p1.xyz(0), seg.p2.xyz(0)); } - for (LineSegmentXZ seg : ((MapOverlapWA)overlap).getOverlappedSegments()) { + for (LineSegmentXZ seg : overlapWA.getOverlappedSegments()) { target.drawLineStrip(INTERSECTION_COLOR, 3, seg.p1.xyz(0), seg.p2.xyz(0)); } - } else if (overlap instanceof MapOverlapAA) { - for (VectorXZ pos : ((MapOverlapAA)overlap).getIntersectionPositions()) { - drawBoxAround(target, pos, - INTERSECTION_COLOR, HALF_NODE_WIDTH); + } else if (overlap instanceof MapOverlapAA overlapAA) { + if (isEmptyTerrain.test(overlapAA.getOther(area))) continue; + for (VectorXZ pos : overlapAA.getIntersectionPositions()) { + drawBoxAround(target, pos, INTERSECTION_COLOR, HALF_NODE_WIDTH); } } } From 2a3f29755e2c1d27f2f72a6fbbf27a0b42eb8cb3 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 21 Nov 2024 14:16:44 +0100 Subject: [PATCH 64/85] Fix HighVoltagePowerTower rendering, avoid invalid strips --- .../core/world/modules/PowerModule.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/PowerModule.java b/src/main/java/org/osm2world/core/world/modules/PowerModule.java index f7f4b9bd5..174f9dfd8 100644 --- a/src/main/java/org/osm2world/core/world/modules/PowerModule.java +++ b/src/main/java/org/osm2world/core/world/modules/PowerModule.java @@ -676,19 +676,25 @@ private void drawSegment(Target target, for (int a = 0; a < 4; a++) { - List vs = new ArrayList(); - List tex = new ArrayList(); + List vs = new ArrayList<>(); + List tex = new ArrayList<>(); List> texList = nCopies(Materials.POWER_TOWER_VERTICAL.getNumTextureLayers(), tex); - for (int i = 0; i < 2; i++) { - int idx = (a+i)%4; - vs.add(high[idx].xyz(height)); - vs.add(low[idx].xyz(base)); - tex.add(new VectorXZ(i, 1)); - tex.add(new VectorXZ(i, 0)); + vs.add(high[a].xyz(height)); + tex.add(new VectorXZ(0, 1)); + + vs.add(low[a].xyz(base)); + tex.add(new VectorXZ(0, 0)); + + if (high[a].distanceTo(high[(a + 1) % 4]) > 0.001) { + vs.add(high[(a + 1) % 4].xyz(height)); + tex.add(new VectorXZ(1, 1)); } + vs.add(low[(a + 1) % 4].xyz(base)); + tex.add(new VectorXZ(1, 0)); + target.drawTriangleStrip(Materials.POWER_TOWER_VERTICAL, vs, texList); } } From 2f7e6ed78d2e624e54075e968dc6f3530dceb4d6 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 21 Nov 2024 14:47:53 +0100 Subject: [PATCH 65/85] Prevent exceptions when attempting to log stats for failed runs --- .../java/org/osm2world/console/Output.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index 5838fe80a..30f06a715 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -316,13 +316,19 @@ public static void output(Configuration config, private static void writeLogFiles(File logDir, String fileNameBase, PerformanceListener perfListener) { double totalTime = Duration.between(perfListener.startTime, now()).toMillis() / 1000.0; - Map timePerPhase = Map.ofEntries( - entry(MAP_DATA, perfListener.getPhaseDuration(MAP_DATA).toMillis() / 1000.0), - entry(REPRESENTATION, perfListener.getPhaseDuration(REPRESENTATION).toMillis() / 1000.0), - entry(ELEVATION, perfListener.getPhaseDuration(ELEVATION).toMillis() / 1000.0), - entry(TERRAIN, perfListener.getPhaseDuration(TERRAIN).toMillis() / 1000.0), - entry(TARGET, Duration.between(perfListener.getPhaseEnd(TERRAIN), now()).toMillis() / 1000.0) - ); + + Map timePerPhase; + if (perfListener.currentPhase == FINISHED) { + timePerPhase = Map.ofEntries( + entry(MAP_DATA, perfListener.getPhaseDuration(MAP_DATA).toMillis() / 1000.0), + entry(REPRESENTATION, perfListener.getPhaseDuration(REPRESENTATION).toMillis() / 1000.0), + entry(ELEVATION, perfListener.getPhaseDuration(ELEVATION).toMillis() / 1000.0), + entry(TERRAIN, perfListener.getPhaseDuration(TERRAIN).toMillis() / 1000.0), + entry(TARGET, Duration.between(perfListener.getPhaseEnd(TERRAIN), now()).toMillis() / 1000.0) + ); + } else { + timePerPhase = Map.of(); + } /* write a json file with performance stats */ @@ -347,9 +353,11 @@ private static void writeLogFiles(File logDir, String fileNameBase, PerformanceL TargetUtil.writeFileWithCompression(outputFile, compression, outputStream -> { try (var printStream = new PrintStream(outputStream)) { printStream.println("Runtime (seconds):\nTotal: " + totalTime); - for (Phase phase : Phase.values()) { - if (timePerPhase.containsKey(phase)) { - printStream.println(phase + ": " + timePerPhase.get(phase)); + if (!timePerPhase.isEmpty()) { + for (Phase phase : Phase.values()) { + if (timePerPhase.containsKey(phase)) { + printStream.println(phase + ": " + timePerPhase.get(phase)); + } } } printStream.println(); From 68a305a94e1da6da68aa308b8c1980a6b124da8d Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 21 Nov 2024 14:50:14 +0100 Subject: [PATCH 66/85] Synchronize access to config-dependent materials and models --- .../core/target/common/material/Materials.java | 14 +++++++------- .../osm2world/core/target/common/model/Models.java | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/osm2world/core/target/common/material/Materials.java b/src/main/java/org/osm2world/core/target/common/material/Materials.java index a9574afb7..9e3cfe516 100644 --- a/src/main/java/org/osm2world/core/target/common/material/Materials.java +++ b/src/main/java/org/osm2world/core/target/common/material/Materials.java @@ -296,7 +296,7 @@ private Materials() {} } /** returns all materials defined here */ - public static final Collection getMaterials() { + synchronized public static final Collection getMaterials() { return fieldNameMap.keySet(); } @@ -305,7 +305,7 @@ public static final Collection getMaterials() { * * @param name case-insensitive name of the material */ - public static final @Nullable ConfMaterial getMaterial(@Nullable String name) { + synchronized public static final @Nullable ConfMaterial getMaterial(@Nullable String name) { if (name == null) return null; @@ -332,18 +332,18 @@ public static final Collection getMaterials() { } /** variant of {@link #getMaterial(String)} with a default value */ - public static final Material getMaterial(@Nullable String name, Material defaultValue) { + synchronized public static final Material getMaterial(@Nullable String name, Material defaultValue) { Material result = getMaterial(name); return result == null ? defaultValue : result; } /** returns a material for a surface value; null if none is found */ - public static final Material getSurfaceMaterial(String value) { + synchronized public static final Material getSurfaceMaterial(String value) { return getSurfaceMaterial(value, null); } /** same as {@link #getSurfaceMaterial(String)}, but with fallback value */ - public static final Material getSurfaceMaterial(String value, Material fallback) { + synchronized public static final Material getSurfaceMaterial(String value, Material fallback) { Material material = value == null ? null : surfaceMaterialMap.get(value); if (material != null) { return material; @@ -356,7 +356,7 @@ public static final Material getSurfaceMaterial(String value, Material fallback) * returns a human-readable, unique name for a material defined * within this class, null for all other materials. */ - public static final String getUniqueName(Material material) { + synchronized public static final String getUniqueName(Material material) { return fieldNameMap.get(material); } @@ -367,7 +367,7 @@ public static final String getUniqueName(Material material) { * configures the attributes of the materials within this class * based on external configuration settings */ - public static final void configureMaterials(Configuration config) { + synchronized public static final void configureMaterials(Configuration config) { externalMaterials.clear(); diff --git a/src/main/java/org/osm2world/core/target/common/model/Models.java b/src/main/java/org/osm2world/core/target/common/model/Models.java index 7a6ca495f..fd4353913 100644 --- a/src/main/java/org/osm2world/core/target/common/model/Models.java +++ b/src/main/java/org/osm2world/core/target/common/model/Models.java @@ -30,7 +30,7 @@ private Models() {} * * @param name case-insensitive name of the material */ - public static @Nullable Model getModel(@Nullable String name) { + synchronized public static @Nullable Model getModel(@Nullable String name) { if (name == null) return null; @@ -46,7 +46,7 @@ private Models() {} /** * variant of {@link #getModel(String)} which picks one of several available models randomly. */ - public static @Nullable Model getModel(@Nullable String name, Random random) { + synchronized public static @Nullable Model getModel(@Nullable String name, Random random) { if (name == null) return null; @@ -59,7 +59,7 @@ private Models() {} } - public static void configureModels(Configuration config) { + synchronized public static void configureModels(Configuration config) { models.clear(); From 06eb633868c9762f8ad8c1d4f16cefaac5934a24 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 25 Nov 2024 13:57:13 +0100 Subject: [PATCH 67/85] Use ConversionLog to improve some logging code --- src/main/java/org/osm2world/core/ConversionFacade.java | 2 +- src/main/java/org/osm2world/core/target/obj/ObjTarget.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/osm2world/core/ConversionFacade.java b/src/main/java/org/osm2world/core/ConversionFacade.java index 44e19e58c..f631bd67e 100644 --- a/src/main/java/org/osm2world/core/ConversionFacade.java +++ b/src/main/java/org/osm2world/core/ConversionFacade.java @@ -433,7 +433,7 @@ private void calculateElevations(MapData mapData, try { sites = eleData.getSites(mapData.getDataBoundary().pad(10)); } catch (IOException e) { - e.printStackTrace(); + ConversionLog.error("Could not read elevation data: " + e.getMessage(), e); } if (!sites.isEmpty()) { diff --git a/src/main/java/org/osm2world/core/target/obj/ObjTarget.java b/src/main/java/org/osm2world/core/target/obj/ObjTarget.java index ca8f3eaf6..f736efec6 100644 --- a/src/main/java/org/osm2world/core/target/obj/ObjTarget.java +++ b/src/main/java/org/osm2world/core/target/obj/ObjTarget.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.*; +import org.osm2world.core.conversion.ConversionLog; import org.osm2world.core.map_data.data.TagSet; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; @@ -301,8 +302,7 @@ private void writeMaterial(Material material, String name) { } } catch (IOException e) { - System.err.println("Unable to export material " + name + ": "); - e.printStackTrace(); + ConversionLog.error("Unable to export material " + name + ": ", e); } } From c2a599c40d22e25816008e88fa986d784b56672f Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 25 Nov 2024 13:58:07 +0100 Subject: [PATCH 68/85] Handle EleCalculator and interpolator options in ConversionFacade --- .../java/org/osm2world/console/Output.java | 21 ------- .../org/osm2world/core/ConversionFacade.java | 55 ++++++++++++++----- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index 30f06a715..e9182881c 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -33,7 +33,6 @@ import org.osm2world.core.map_data.creation.LatLonBounds; import org.osm2world.core.map_data.creation.MapProjection; import org.osm2world.core.map_data.data.MapMetadata; -import org.osm2world.core.map_elevation.creation.*; import org.osm2world.core.math.AxisAlignedRectangleXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.osm.creation.*; @@ -119,26 +118,6 @@ public static void output(Configuration config, ConversionFacade cf = new ConversionFacade(); cf.addProgressListener(perfListener); - String interpolatorType = config.getString("terrainInterpolator"); - if ("ZeroInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(ZeroInterpolator::new); - } else if ("LeastSquaresInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(LeastSquaresInterpolator::new); - } else if ("NaturalNeighborInterpolator".equals(interpolatorType)) { - cf.setTerrainEleInterpolatorFactory(NaturalNeighborInterpolator::new); - } - - String eleCalculatorName = config.getString("eleCalculator"); - if (eleCalculatorName != null) { - switch (eleCalculatorName) { - case "NoOpEleCalculator" -> cf.setEleCalculatorFactory(NoOpEleCalculator::new); - case "EleTagEleCalculator" -> cf.setEleCalculatorFactory(EleTagEleCalculator::new); - case "BridgeTunnelEleCalculator" -> cf.setEleCalculatorFactory(BridgeTunnelEleCalculator::new); - case "ConstraintEleCalculator" -> - cf.setEleCalculatorFactory(() -> new ConstraintEleCalculator(new SimpleEleConstraintEnforcer())); - } - } - Results results = cf.createRepresentations(dataReader.getData(), metadata, null, config, null); ImageExporter exporter = null; diff --git a/src/main/java/org/osm2world/core/ConversionFacade.java b/src/main/java/org/osm2world/core/ConversionFacade.java index f631bd67e..6bfa57c2f 100644 --- a/src/main/java/org/osm2world/core/ConversionFacade.java +++ b/src/main/java/org/osm2world/core/ConversionFacade.java @@ -131,9 +131,8 @@ static final List createDefaultModuleList(Configuration config) { private Function mapProjectionFactory = MetricMapProjection::new; - private Factory terrainEleInterpolatorFactory = ZeroInterpolator::new; - - private Factory eleCalculatorFactory = BridgeTunnelEleCalculator::new; + private @Nullable Factory terrainEleInterpolatorFactory = null; + private @Nullable Factory eleCalculatorFactory = null; /** * sets the factory that will make {@link MapProjection} @@ -145,21 +144,20 @@ public void setMapProjectionFactory(Function ma } /** - * sets the factory that will make {@link EleCalculator} - * instances during subsequent calls to + * sets the factory that will make {@link EleCalculator} instances during subsequent calls to * {@link #createRepresentations(OSMData, MapMetadata, List, Configuration, List)}. + * Can be set to null, in which case there will be an attempt to parse the configuration for this. */ - public void setEleCalculatorFactory(Factory eleCalculatorFactory) { + public void setEleCalculatorFactory(@Nullable Factory eleCalculatorFactory) { this.eleCalculatorFactory = eleCalculatorFactory; } /** - * sets the factory that will make {@link TerrainInterpolator} - * instances during subsequent calls to + * sets the factory that will make {@link TerrainInterpolator} instances during subsequent calls to * {@link #createRepresentations(OSMData, MapMetadata, List, Configuration, List)}. + * Can be set to null, in which case there will be an attempt to parse the configuration for this. */ - public void setTerrainEleInterpolatorFactory( - Factory enforcerFactory) { + public void setTerrainEleInterpolatorFactory(@Nullable Factory enforcerFactory) { this.terrainEleInterpolatorFactory = enforcerFactory; } @@ -411,14 +409,14 @@ protected static void attachConnectorIfValid(AttachmentConnector connector, Atta } /** - * uses OSM data and an terrain elevation data (usually from an external + * uses OSM data and a terrain elevation data (usually from an external * source) to calculate elevations for all {@link EleConnector}s of the * {@link WorldObject}s */ private void calculateElevations(MapData mapData, TerrainElevationData eleData, Configuration config) { - TerrainInterpolator interpolator = terrainEleInterpolatorFactory.get(); + TerrainInterpolator interpolator = createTerrainInterpolator(config); if (eleData == null) { interpolator = new ZeroInterpolator(); @@ -457,11 +455,42 @@ private void calculateElevations(MapData mapData, /* refine terrain-based elevation with information from map data */ - EleCalculator eleCalculator = eleCalculatorFactory.get(); + EleCalculator eleCalculator = createEleCalculator(config); eleCalculator.calculateElevations(mapData); } + private EleCalculator createEleCalculator(Configuration config) { + + if (eleCalculatorFactory != null) { + return eleCalculatorFactory.get(); + } else { + return switch (config.getString("eleCalculator", "")) { + case "NoOpEleCalculator" -> new NoOpEleCalculator(); + case "EleTagEleCalculator" -> new EleTagEleCalculator(); + case "ConstraintEleCalculator" -> new ConstraintEleCalculator(new SimpleEleConstraintEnforcer()); + default -> new BridgeTunnelEleCalculator(); + }; + } + + } + + private TerrainInterpolator createTerrainInterpolator(Configuration config) { + + if (terrainEleInterpolatorFactory != null) { + return terrainEleInterpolatorFactory.get(); + } else { + return switch (config.getString("terrainInterpolator", "")) { + case "LinearInterpolator" -> new LinearInterpolator(); + case "LeastSquaresInterpolator" -> new LeastSquaresInterpolator(); + case "NaturalNeighborInterpolator" -> new NaturalNeighborInterpolator(); + case "InverseDistanceWeightingInterpolator" -> new InverseDistanceWeightingInterpolator(); + default -> new ZeroInterpolator(); + }; + } + + } + public enum Phase { MAP_DATA, REPRESENTATION, From 3f286b05c6a1db4b58486a359d1f9d5a0ef4bc70 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 26 Nov 2024 10:49:03 +0100 Subject: [PATCH 69/85] Make OrthoTilesUtil.boundsForTile public and use it --- src/main/java/org/osm2world/console/Output.java | 7 +++---- .../core/target/common/rendering/OrthoTilesUtil.java | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index e9182881c..facec47fe 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -1,7 +1,6 @@ package org.osm2world.console; import static java.time.Instant.now; -import static java.util.Collections.singletonList; import static java.util.Map.entry; import static java.util.stream.Collectors.toList; import static org.osm2world.core.ConversionFacade.Phase.*; @@ -166,7 +165,7 @@ public static void output(Configuration config, } else if (args.isOviewTiles()) { bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), args.getOviewTiles()); } else if (args.isTile()) { - bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); + bounds = OrthoTilesUtil.boundsForTile(results.getMapProjection(), args.getTile()); } else { bounds = results.getMapData().getBoundary(); } @@ -205,7 +204,7 @@ public static void output(Configuration config, case GLTF, GLB, GLTF_GZ, GLB_GZ: { AxisAlignedRectangleXZ bounds = null; if (args.isTile()) { - bounds = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); + bounds = OrthoTilesUtil.boundsForTile(results.getMapProjection(), args.getTile()); } else { bounds = results.getMapData().getBoundary(); } @@ -229,7 +228,7 @@ public static void output(Configuration config, case WEB_PBF, WEB_PBF_GZ: { AxisAlignedRectangleXZ bbox = null; if (args.isTile()) { - bbox = OrthoTilesUtil.boundsForTiles(results.getMapProjection(), singletonList(args.getTile())); + bbox = OrthoTilesUtil.boundsForTile(results.getMapProjection(), args.getTile()); } Compression compression = outputMode == OutputMode.WEB_PBF_GZ ? Compression.GZ : Compression.NONE; FrontendPbfTarget.writePbfFile( diff --git a/src/main/java/org/osm2world/core/target/common/rendering/OrthoTilesUtil.java b/src/main/java/org/osm2world/core/target/common/rendering/OrthoTilesUtil.java index fc2deff58..721f9e1f0 100644 --- a/src/main/java/org/osm2world/core/target/common/rendering/OrthoTilesUtil.java +++ b/src/main/java/org/osm2world/core/target/common/rendering/OrthoTilesUtil.java @@ -2,7 +2,8 @@ import static java.lang.Math.PI; import static java.util.Arrays.asList; -import static org.osm2world.core.math.AxisAlignedRectangleXZ.*; +import static org.osm2world.core.math.AxisAlignedRectangleXZ.bbox; +import static org.osm2world.core.math.AxisAlignedRectangleXZ.union; import java.util.List; @@ -143,7 +144,7 @@ public static final Projection projectionForBounds( } - private static final AxisAlignedRectangleXZ boundsForTile(MapProjection mapProjection, TileNumber tile) { + public static final AxisAlignedRectangleXZ boundsForTile(MapProjection mapProjection, TileNumber tile) { LatLonBounds bounds = tile.bounds(); return bbox(asList(mapProjection.toXZ(bounds.getMin()), mapProjection.toXZ(bounds.getMax()))); } From d2ac04520e82f65c364b9ec3f3a7fe0bd2a4c921 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 26 Nov 2024 10:54:26 +0100 Subject: [PATCH 70/85] Make it more convenient to obtain an LOD enum from int --- .../java/org/osm2world/console/OSM2World.java | 5 +---- .../core/target/common/mesh/LevelOfDetail.java | 16 +++++++++++++++- .../target/frontend_pbf/FrontendPbfTarget.java | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/osm2world/console/OSM2World.java b/src/main/java/org/osm2world/console/OSM2World.java index 297aa4942..81398f9d1 100644 --- a/src/main/java/org/osm2world/console/OSM2World.java +++ b/src/main/java/org/osm2world/console/OSM2World.java @@ -170,10 +170,7 @@ private static void executeArgumentsGroup(CLIArgumentsGroup argumentsGroup) { CLIArguments representativeArgs = argumentsGroup.getRepresentative(); - LevelOfDetail lod = null; - if (representativeArgs.getLod() != null) { - lod = LevelOfDetail.values()[representativeArgs.getLod()]; - } + LevelOfDetail lod = LevelOfDetail.fromInt(representativeArgs.getLod()); /* load configuration file */ diff --git a/src/main/java/org/osm2world/core/target/common/mesh/LevelOfDetail.java b/src/main/java/org/osm2world/core/target/common/mesh/LevelOfDetail.java index f33d20410..9e4328512 100644 --- a/src/main/java/org/osm2world/core/target/common/mesh/LevelOfDetail.java +++ b/src/main/java/org/osm2world/core/target/common/mesh/LevelOfDetail.java @@ -1,10 +1,24 @@ package org.osm2world.core.target.common.mesh; +import javax.annotation.Nullable; + /** * level of detail, from lowest (0) to highest (4). * Describes a point on the spectrum of trade-offs between performance and quality. * OSM2World's levels do not strictly conform to any particular standard. */ public enum LevelOfDetail implements Comparable { - LOD0, LOD1, LOD2, LOD3, LOD4 + + LOD0, LOD1, LOD2, LOD3, LOD4; + + public static @Nullable LevelOfDetail fromInt(@Nullable Integer lod) { + if (lod != null) { + return switch (lod) { + case 0, 1, 2, 3, 4 -> values()[lod]; + default -> null; + }; + } + return null; + } + } diff --git a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java index 4f5a560bd..75afdd979 100644 --- a/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java +++ b/src/main/java/org/osm2world/core/target/frontend_pbf/FrontendPbfTarget.java @@ -450,8 +450,8 @@ private List buildWorldObjects(@Nullable MeshMetadata w List meshesAtLod = new ArrayList<>(); for (Mesh m : meshes) { - if (m.lodRange.min() == LevelOfDetail.values()[minLod] - && m.lodRange.max() == LevelOfDetail.values()[maxLod]) { + if (m.lodRange.min() == LevelOfDetail.fromInt(minLod) + && m.lodRange.max() == LevelOfDetail.fromInt(maxLod)) { meshesAtLod.add(m); } } From 48d14702e293e5c75118bcfb913623e53b916d61 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 11:20:02 +0100 Subject: [PATCH 71/85] Speed up window placement --- .../building/ExteriorBuildingWall.java | 6 ++- .../world/modules/building/WallSurface.java | 48 ++++++++++++++----- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java index 71a01b866..047fd9da9 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java @@ -397,6 +397,8 @@ WindowImplementation.FULL_GEOMETRY, new LODRange(LOD4) /** places the default (i.e. not explicitly mapped) windows rows onto a wall surface */ private void placeDefaultWindows(WallSurface surface, WindowImplementation implementation) { + List windows = new ArrayList<>(); + for (Level level : buildingPart.levelStructure.levels(EnumSet.of(LevelType.ABOVEGROUND))) { WindowParameters windowParams = new WindowParameters(tags, level.height); @@ -415,12 +417,14 @@ private void placeDefaultWindows(WallSurface surface, WindowImplementation imple Window window = implementation == WindowImplementation.FULL_GEOMETRY ? new GeometryWindow(pos, windowParams, false) : new TexturedWindow(pos, windowParams); - surface.addElementIfSpaceFree(window); + windows.add(window); } } + surface.addElementsIfSpaceFree(windows); + } /** places default (i.e. not explicitly mapped) doors onto garage walls */ diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java index 7067f9b9d..47da351fe 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java @@ -24,6 +24,7 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.FaceDecompositionUtil; +import org.osm2world.core.math.datastructures.IndexGrid; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; import org.osm2world.core.math.shapes.ShapeXZ; @@ -34,6 +35,8 @@ import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.world.data.ProceduralWorldObject; +import com.google.common.collect.Streams; + /** * a simplified representation of a wall as a 2D plane, with its origin in the bottom left corner. * This streamlines the placement of objects (windows, doors, and similar features) onto the wall. @@ -72,7 +75,7 @@ public WallSurface(Material material, List lowerBoundaryXYZ, if (lowerBoundaryXYZ.size() < 2) throw new IllegalArgumentException("need at least two points in the lower boundary"); if (upperBoundaryXYZ.size() < 2) - throw new IllegalArgumentException("need at least two ponts in the upper boundary"); + throw new IllegalArgumentException("need at least two points in the upper boundary"); /* TODO: check for other problems, e.g. intersecting lower and upper boundary, last points of the boundaries having different x values in wall surface coords, ... */ @@ -139,18 +142,27 @@ public Material getMaterial() { /** adds an element to the wall, unless the necessary space on the wall is already occupied */ public void addElementIfSpaceFree(WallElement element) { + addElementsIfSpaceFree(List.of(element)); + } - if (!wallOutline.contains(element.outline())) { - return; - } + /** + * performs the equivalent of {@link #addElementIfSpaceFree(WallElement)} for multiple elements. + * Assumes that the new elements do not intersect with each other. + */ + public void addElementsIfSpaceFree(Collection elements) { - boolean spaceOccupied = elements.stream().anyMatch(e -> - e.outline().intersects(element.outline()) || e.outline().contains(element.outline())); + List elementsToAdd = new ArrayList<>(elements.size()); - if (!spaceOccupied) { - elements.add(element); + for (WallElement element : elements) { + if (wallOutline.contains(element.outline()) + && this.elements.stream().noneMatch(e -> + e.outline().intersects(element.outline()) || e.outline().contains(element.outline()))) { + elementsToAdd.add(element); + } } + this.elements.addAll(elementsToAdd); + } /** @@ -190,7 +202,7 @@ public void renderTo(ProceduralWorldObject.Target target, VectorXZ textureOrigin /* decompose and triangulate the empty wall surface */ - List holes = elements.stream().map(WallElement::outline).collect(toList()); + List holes = elements.stream().map(WallElement::outline).toList(); AxisAlignedRectangleXZ bbox = wallOutline.boundingBox(); double minZ = bbox.minZ; @@ -198,7 +210,7 @@ public void renderTo(ProceduralWorldObject.Target target, VectorXZ textureOrigin List verticalLines = lowerBoundary.stream() .limit(lowerBoundary.size() - 1).skip(1) // omit first and last point .map(p -> new LineSegmentXZ(new VectorXZ(p.x, minZ - 1.0), new VectorXZ(p.x, maxZ + 1.0))) - .collect(toList()); + .toList(); List shapes = new ArrayList<>(holes); shapes.addAll(verticalLines); @@ -208,10 +220,22 @@ public void renderTo(ProceduralWorldObject.Target target, VectorXZ textureOrigin : FaceDecompositionUtil.splitPolygonIntoFaces(wallOutline, shapes); if (!holes.isEmpty()) { - faces.removeIf(f -> holes.stream().anyMatch(hole -> hole.contains(f.getPointInside()))); + + IndexGrid index = (wallOutline.getArea() > 500.0) + ? new IndexGrid<>(wallOutline.boundingBox(), 10.0, 10.0) + : null; + + if (index != null) { holes.forEach(index::insert); } + + faces.removeIf(f -> { + Iterable nearbyHoles = index == null ? holes : index.probe(f); + VectorXZ faceCenter = f.getPointInside(); + return Streams.stream(nearbyHoles).anyMatch(hole -> hole.contains(faceCenter)); + }); + } - List triangles = faces.stream().flatMap(f -> f.getTriangulation().stream()).collect(toList()); + List triangles = faces.stream().flatMap(f -> f.getTriangulation().stream()).toList(); List trianglesXYZ = triangles.stream().map(t -> convertTo3D(t)).collect(toList()); /* determine the material depending on whether a window texture should be applied */ From 797ccfe61ace98958f3cb03607bd3c78eb278d78 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 11:34:15 +0100 Subject: [PATCH 72/85] Avoid window placement calculations entirely depending on LOD --- .../world/data/CachingProceduralWorldObject.java | 15 ++++++++++++++- .../core/world/modules/building/Building.java | 11 +++++++++++ .../modules/building/ExteriorBuildingWall.java | 8 +++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java index a6f52041c..d82de4802 100644 --- a/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java @@ -3,6 +3,9 @@ import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; + +import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.ModelInstance; import org.osm2world.core.world.attachment.AttachmentSurface; @@ -13,9 +16,11 @@ abstract public class CachingProceduralWorldObject implements ProceduralWorldObject { private ProceduralWorldObject.Target target = null; + private @Nullable LevelOfDetail lod; private void fillTargetIfNecessary() { - if (target == null) { + if (target == null || (lod != null && getConfiguredLod() != null && lod != getConfiguredLod())) { + lod = getConfiguredLod(); target = new ProceduralWorldObject.Target(); buildMeshesAndModels(target); } @@ -39,4 +44,12 @@ public Collection getAttachmentSurfaces() { return target.attachmentSurfaces; } + /** + * if results depend on LOD, returns the currently configured LOD. + * Can be null if this {@link ProceduralWorldObject} always produces geometry for all LOD. + */ + protected @Nullable LevelOfDetail getConfiguredLod() { + return null; + } + } diff --git a/src/main/java/org/osm2world/core/world/modules/building/Building.java b/src/main/java/org/osm2world/core/world/modules/building/Building.java index 29ce26eab..8254c8346 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/Building.java +++ b/src/main/java/org/osm2world/core/world/modules/building/Building.java @@ -7,6 +7,8 @@ import java.util.*; +import javax.annotation.Nullable; + import org.apache.commons.configuration.Configuration; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapElement; @@ -21,6 +23,8 @@ import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; +import org.osm2world.core.target.common.mesh.LevelOfDetail; +import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.util.FaultTolerantIterationUtil; import org.osm2world.core.world.attachment.AttachmentSurface; import org.osm2world.core.world.data.AreaWorldObject; @@ -33,6 +37,7 @@ public class Building extends CachingProceduralWorldObject implements AreaWorldObject { private final MapArea area; + private final Configuration config; private final List parts = new ArrayList<>(); @@ -43,6 +48,7 @@ public class Building extends CachingProceduralWorldObject implements AreaWorldO public Building(MapArea area, Configuration config) { this.area = area; + this.config = config; Optional buildingRelation = area.getMemberships().stream() .filter(it -> "outline".equals(it.getRole())) @@ -156,6 +162,11 @@ public double getGroundLevelEle() { } + @Override + protected @Nullable LevelOfDetail getConfiguredLod() { + return ConfigUtil.readLOD(config); + } + @Override public void buildMeshesAndModels(Target target) { FaultTolerantIterationUtil.forEach(parts, part -> part.buildMeshesAndModels(target)); diff --git a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java index 047fd9da9..cb1205e60 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java +++ b/src/main/java/org/osm2world/core/world/modules/building/ExteriorBuildingWall.java @@ -10,6 +10,7 @@ import static org.osm2world.core.math.VectorXZ.NULL_VECTOR; import static org.osm2world.core.math.VectorXZ.listXYZ; import static org.osm2world.core.target.common.mesh.LevelOfDetail.*; +import static org.osm2world.core.util.ConfigUtil.readLOD; import static org.osm2world.core.util.ValueParseUtil.parseLevels; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.inheritTags; @@ -29,7 +30,6 @@ import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.target.common.mesh.LODRange; -import org.osm2world.core.util.ConfigUtil; import org.osm2world.core.world.data.ProceduralWorldObject; import org.osm2world.core.world.data.WorldObject; import org.osm2world.core.world.modules.building.LevelAndHeightData.Level; @@ -192,6 +192,8 @@ public void renderTo(ProceduralWorldObject.Target target) { for (WindowImplementation windowImplementation : windowImplementations.keySet()) { LODRange lodRange = windowImplementations.get(windowImplementation); + if (buildingPart.config.containsKey("lod") && + !lodRange.contains(readLOD(buildingPart.config))) continue; target.setCurrentLodRange(lodRange); /* construct the surface(s) */ @@ -262,7 +264,7 @@ public void renderTo(ProceduralWorldObject.Target target) { DoorParameters params = DoorParameters.fromTags(node.getTags(), this.tags); if (lodRange.max().ordinal() < 3 - || ConfigUtil.readLOD(buildingPart.config).ordinal() < 3) { + || readLOD(buildingPart.config).ordinal() < 3) { params = params.withInset(0.0); } mainSurface.addElementIfSpaceFree(new Door(pos, params)); @@ -433,7 +435,7 @@ private void placeDefaultGarageDoors(WallSurface surface) { TagSet doorTags = TagSet.of("door", "overhead"); DoorParameters params = DoorParameters.fromTags(doorTags, this.tags); - if (ConfigUtil.readLOD(buildingPart.config).ordinal() < 3) { + if (readLOD(buildingPart.config).ordinal() < 3) { params = params.withInset(0.0); } From ec32e0bcc1c2a30d93d7f9516e5c28787efbcfb1 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 13:21:22 +0100 Subject: [PATCH 73/85] Handle holes in FaceDecompositionUtil --- .../algorithms/FaceDecompositionUtil.java | 45 +++++++++++++------ .../modules/building/GeometryWindow.java | 2 +- .../world/modules/building/WallSurface.java | 27 +---------- .../algorithms/FaceDecompositionUtilTest.java | 27 ++++++----- 4 files changed, 48 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java index 4b64ce65f..2374dd471 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java @@ -2,29 +2,25 @@ import static java.util.Collections.min; import static java.util.Comparator.comparingDouble; -import static java.util.stream.Collectors.*; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.osm2world.core.math.AxisAlignedRectangleXZ.bboxUnion; + +import java.util.*; import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.PolygonWithHolesXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.Intersection; +import org.osm2world.core.math.datastructures.IndexGrid; +import org.osm2world.core.math.datastructures.SpatialIndex; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.ShapeXZ; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import com.google.common.collect.Streams; /** utilities for finding the faces in a graph of line segments */ public final class FaceDecompositionUtil { @@ -35,18 +31,41 @@ public final class FaceDecompositionUtil { private FaceDecompositionUtil() {} public static final Collection splitPolygonIntoFaces(PolygonShapeXZ polygon, - Iterable otherShapes) { + Collection holes, Collection otherShapes) { List segments = new ArrayList<>(); polygon.getRings().forEach(r -> segments.addAll(r.getSegments())); + holes.forEach(s -> segments.addAll(s.getSegments())); otherShapes.forEach(s -> segments.addAll(s.getSegments())); Collection result = facesFromGraph(segments); result.removeIf(p -> !polygon.contains(p.getPointInside())); + removeFacesInHoles(result, holes); return result; } + private static void removeFacesInHoles(Collection faces, + Collection holes) { + + if (!holes.isEmpty()) { + + SpatialIndex index = (holes.size() > 40) + ? new IndexGrid<>(bboxUnion(holes), 20.0, 20.0) + : null; + + if (index != null) { holes.forEach(index::insert); } + + faces.removeIf(f -> { + Iterable nearbyHoles = index == null ? holes : index.probe(f); + VectorXZ faceCenter = f.getPointInside(); + return Streams.stream(nearbyHoles).anyMatch(hole -> hole.contains(faceCenter)); + }); + + } + + } + public static final Collection facesFromGraph(List segments) { //TODO consider applying a global precision model to VectorXZ instead of these piecemeal solutions diff --git a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java index afee9db5e..c7234b366 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java +++ b/src/main/java/org/osm2world/core/world/modules/building/GeometryWindow.java @@ -345,7 +345,7 @@ framePathXYZ, nCopies(framePathXYZ.size(), windowNormal), outline.getCentroid().add(0, -outline.getDiameter()), outline.getCentroid().add(0, +outline.getDiameter())); - Collection outlineParts = splitPolygonIntoFaces(asSimplePolygon(outline), asList(splitLine)); + Collection outlineParts = splitPolygonIntoFaces(asSimplePolygon(outline), List.of(), List.of(splitLine)); double hingeSpace = 0.03; diff --git a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java index 47da351fe..d7722a976 100644 --- a/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java +++ b/src/main/java/org/osm2world/core/world/modules/building/WallSurface.java @@ -24,10 +24,8 @@ import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.FaceDecompositionUtil; -import org.osm2world.core.math.datastructures.IndexGrid; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.PolylineXZ; -import org.osm2world.core.math.shapes.ShapeXZ; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.TextureDataDimensions; import org.osm2world.core.target.common.material.TextureLayer; @@ -35,8 +33,6 @@ import org.osm2world.core.target.common.texcoord.TexCoordFunction; import org.osm2world.core.world.data.ProceduralWorldObject; -import com.google.common.collect.Streams; - /** * a simplified representation of a wall as a 2D plane, with its origin in the bottom left corner. * This streamlines the placement of objects (windows, doors, and similar features) onto the wall. @@ -212,28 +208,9 @@ public void renderTo(ProceduralWorldObject.Target target, VectorXZ textureOrigin .map(p -> new LineSegmentXZ(new VectorXZ(p.x, minZ - 1.0), new VectorXZ(p.x, maxZ + 1.0))) .toList(); - List shapes = new ArrayList<>(holes); - shapes.addAll(verticalLines); - - Collection faces = shapes.isEmpty() + Collection faces = holes.isEmpty() && verticalLines.isEmpty() ? singletonList(wallOutline) - : FaceDecompositionUtil.splitPolygonIntoFaces(wallOutline, shapes); - - if (!holes.isEmpty()) { - - IndexGrid index = (wallOutline.getArea() > 500.0) - ? new IndexGrid<>(wallOutline.boundingBox(), 10.0, 10.0) - : null; - - if (index != null) { holes.forEach(index::insert); } - - faces.removeIf(f -> { - Iterable nearbyHoles = index == null ? holes : index.probe(f); - VectorXZ faceCenter = f.getPointInside(); - return Streams.stream(nearbyHoles).anyMatch(hole -> hole.contains(faceCenter)); - }); - - } + : FaceDecompositionUtil.splitPolygonIntoFaces(wallOutline, holes, verticalLines); List triangles = faces.stream().flatMap(f -> f.getTriangulation().stream()).toList(); List trianglesXYZ = triangles.stream().map(t -> convertTo3D(t)).collect(toList()); diff --git a/src/test/java/org/osm2world/core/math/algorithms/FaceDecompositionUtilTest.java b/src/test/java/org/osm2world/core/math/algorithms/FaceDecompositionUtilTest.java index c24497bf8..38741a846 100644 --- a/src/test/java/org/osm2world/core/math/algorithms/FaceDecompositionUtilTest.java +++ b/src/test/java/org/osm2world/core/math/algorithms/FaceDecompositionUtilTest.java @@ -1,23 +1,22 @@ package org.osm2world.core.math.algorithms; import static java.util.Arrays.asList; -import static java.util.Collections.*; -import static org.junit.Assert.*; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.osm2world.core.math.GeometryUtil.closeLoop; import static org.osm2world.core.math.SimplePolygonXZ.asSimplePolygon; import static org.osm2world.core.math.VectorXZ.NULL_VECTOR; -import static org.osm2world.core.math.algorithms.FaceDecompositionUtil.*; +import static org.osm2world.core.math.algorithms.FaceDecompositionUtil.facesFromGraph; +import static org.osm2world.core.math.algorithms.FaceDecompositionUtil.splitPolygonIntoFaces; import static org.osm2world.core.test.TestUtil.assertSameCyclicOrder; import java.util.Collection; import java.util.List; import org.junit.Test; -import org.osm2world.core.math.AxisAlignedRectangleXZ; -import org.osm2world.core.math.LineSegmentXZ; -import org.osm2world.core.math.PolygonWithHolesXZ; -import org.osm2world.core.math.SimplePolygonXZ; -import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.math.*; import org.osm2world.core.math.shapes.CircleXZ; import org.osm2world.core.math.shapes.PolygonShapeXZ; import org.osm2world.core.math.shapes.ShapeXZ; @@ -37,7 +36,7 @@ public class FaceDecompositionUtilTest { /** a "donut" shape consisting of an outer and inner square ring, standing on edge. Used in multiple tests. */ private final PolygonShapeXZ diamondDonut = new PolygonWithHolesXZ( new SimplePolygonXZ(closeLoop(topOuter, leftOuter, bottomOuter, rightOuter)), - asList(new SimplePolygonXZ(closeLoop(topInner, leftInner, bottomInner, rightInner)))); + List.of(new SimplePolygonXZ(closeLoop(topInner, leftInner, bottomInner, rightInner)))); @Test public void testFacesFromGraph() { @@ -72,7 +71,7 @@ public void testWithoutOtherShapes() { for (PolygonShapeXZ polygon : testInput) { - Collection result = splitPolygonIntoFaces(polygon, emptyList()); + Collection result = splitPolygonIntoFaces(polygon, emptyList(), emptyList()); assertEquals("shape: " + polygon, 1, result.size()); PolygonShapeXZ resultPoly = result.iterator().next(); @@ -98,7 +97,7 @@ public void testQuartering() { LineSegmentXZ horizontalLine = new LineSegmentXZ(center.add(-200, 0), center.add(+200, 0)); LineSegmentXZ verticalLine = new LineSegmentXZ(center.add(0, -200), center.add(0, +200)); - Collection result = splitPolygonIntoFaces(polygon, asList(horizontalLine, verticalLine)); + Collection result = splitPolygonIntoFaces(polygon, List.of(), List.of(horizontalLine, verticalLine)); assertEquals(4, result.size()); @@ -117,7 +116,7 @@ public void testShapeWithHole() { new LineSegmentXZ(bottomInner, leftInner), // duplicate of an existing edge new LineSegmentXZ(rightOuter, leftInner)); // intentionally "too long" - Collection result = splitPolygonIntoFaces(diamondDonut, otherShapes); + Collection result = splitPolygonIntoFaces(diamondDonut, List.of(), otherShapes); assertEquals(4, result.size()); @@ -147,13 +146,13 @@ public void testAccuracy() { // with this data, the area for the otherShape is v new VectorXZ(2.0, 3.0), new VectorXZ(0.0, 3.0))); - Iterable otherShapes = singletonList(new SimplePolygonXZ(closeLoop( + Collection otherShapes = singletonList(new SimplePolygonXZ(closeLoop( new VectorXZ(0.1, 0.75), new VectorXZ(1.167323103354597, 0.75), new VectorXZ(1.167323103354597, 2.0000000298023224), new VectorXZ(0.1, 2.0000000298023224)))); - Collection result = splitPolygonIntoFaces(polygon, otherShapes); + Collection result = splitPolygonIntoFaces(polygon, List.of(), otherShapes); assertEquals(2, result.size()); From 4d50af738cd7777529fbdad8b996b0531b92657f Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 14:44:55 +0100 Subject: [PATCH 74/85] Fix handling of polygons with holes during face decomposition --- .../core/math/algorithms/FaceDecompositionUtil.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java index 2374dd471..86baa16b5 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java @@ -35,8 +35,12 @@ public static final Collection splitPolygonIntoFaces(Polygon List segments = new ArrayList<>(); polygon.getRings().forEach(r -> segments.addAll(r.getSegments())); - holes.forEach(s -> segments.addAll(s.getSegments())); - otherShapes.forEach(s -> segments.addAll(s.getSegments())); + holes.forEach(it -> it.getRings().forEach(r -> segments.addAll(r.getSegments()))); + otherShapes.forEach(s -> { if (s instanceof PolygonShapeXZ p) { + p.getRings().forEach(r -> segments.addAll(r.getSegments())); + } else { + segments.addAll(s.getSegments()); + }}); Collection result = facesFromGraph(segments); result.removeIf(p -> !polygon.contains(p.getPointInside())); From 2e716351baa5b5a75e9d33d7923d5da5940f58cd Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 14:45:30 +0100 Subject: [PATCH 75/85] Replace JTS-based subtractPolygons with a faster implementation --- .../core/math/algorithms/CAGUtil.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/algorithms/CAGUtil.java b/src/main/java/org/osm2world/core/math/algorithms/CAGUtil.java index 8de34755d..ffcf5c78f 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/CAGUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/CAGUtil.java @@ -2,6 +2,7 @@ import static org.osm2world.core.math.JTSConversionUtil.polygonsFromJTS; import static org.osm2world.core.math.JTSConversionUtil.toJTS; +import static org.osm2world.core.math.algorithms.FaceDecompositionUtil.splitPolygonIntoFaces; import java.util.ArrayList; import java.util.Collection; @@ -21,19 +22,25 @@ */ public final class CAGUtil { - private CAGUtil() { } + private CAGUtil() { + } /** * takes a polygon outline, "subtracts" a collection of other polygons (which may themselves have holes), * and returns a collection of polygons that covers the difference area. - * + *

* The result polygons should cover the area that was within the original polygon, * but not within a subtracted polygon. * - * @return polygons without self-intersections, but maybe with holes + * @return polygons without self-intersections, but maybe with holes */ public static final Collection subtractPolygons( PolygonShapeXZ basePolygon, List subtractPolygons) { + return splitPolygonIntoFaces(basePolygon, subtractPolygons, List.of()); + } + + private static final Collection subtractPolygonsWithJTS( + PolygonShapeXZ basePolygon, List subtractPolygons) { List remainingGeometry = Collections.singletonList( (Geometry)toJTS(basePolygon)); @@ -44,15 +51,15 @@ public static final Collection subtractPolygons( if (!jtsSubtractPolygon.isValid()) continue; - List newRemainingGeometry = new ArrayList(1); + List newRemainingGeometry = new ArrayList<>(1); for (Geometry g : remainingGeometry) { Geometry newG = g.difference(jtsSubtractPolygon); if (newG instanceof GeometryCollection) { - for (int i = 0; i < ((GeometryCollection)newG).getNumGeometries(); i++) { - newRemainingGeometry.add(((GeometryCollection)newG).getGeometryN(i)); + for (int i = 0; i < newG.getNumGeometries(); i++) { + newRemainingGeometry.add(newG.getGeometryN(i)); } } else { newRemainingGeometry.add(newG); @@ -64,8 +71,7 @@ public static final Collection subtractPolygons( } - Collection result = - new ArrayList(); + Collection result = new ArrayList<>(); for (Geometry g : remainingGeometry) { result.addAll(polygonsFromJTS(g)); From adc3f96aa59641f119c2438abe5d4d23093e1521 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 15:29:22 +0100 Subject: [PATCH 76/85] Catch flawed polygons early --- .../java/org/osm2world/core/math/PolygonWithHolesXZ.java | 8 +------- .../java/org/osm2world/core/math/SimplePolygonXZ.java | 9 +++++---- .../core/math/algorithms/FaceDecompositionUtil.java | 9 ++++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java b/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java index b5d9f47f3..2d4441d1f 100644 --- a/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java +++ b/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java @@ -19,13 +19,7 @@ public PolygonWithHolesXZ(SimplePolygonXZ outerPolygon, List holes) { this.outerPolygon = outerPolygon; this.holes = holes; - /** - * forces the early computation of the area to avoid the creation of an invalid one. This is a temporary kludge as - * calling a method that can be overridden in a constructor is a bad practice. Moreover, the late call of the method - * calculateArea() was originally intended to avoid unnecessary computations but as it is necessary for intersection - * tests even before any rendering attempts, it no longer makes sense. A better solution would consist in running the - * computation of the area as soon as possible - * */ + // calculate the area in the constructor to catch degenerate polygons early this.getArea(); } diff --git a/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java b/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java index 779801879..fecc7a06d 100644 --- a/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java +++ b/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java @@ -46,6 +46,7 @@ public SimplePolygonXZ(List vertexLoop) { assertLoopLength(vertexLoop); assertNotSelfIntersecting(vertexLoop); assertNoDuplicates(vertexLoop); + assertNontrivialArea(); } @@ -131,8 +132,6 @@ private void calculateArea() { this.signedArea = calculateSignedArea(vertexLoop); this.area = Math.abs(signedArea); this.clockwise = signedArea < 0; - - assertNonzeroArea(); } @Override @@ -698,13 +697,15 @@ private static void assertNoDuplicates(List vertexLoop) { /** * @throws InvalidGeometryException if area is 0 */ - private void assertNonzeroArea() { - if (area == 0) { + private void assertNontrivialArea() { + if (getArea() == 0) { throw new InvalidGeometryException( "a polygon's area must be positive, but it's " + area + " for this polygon.\nThis problem can be caused " + "by broken polygon data or imprecise calculations" + "\nPolygon vertices: " + vertexLoop); + } else if (getArea() < 1e-6) { + throw new InvalidGeometryException("Very small polygon area: " + area); } } diff --git a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java index 86baa16b5..d7f0ed863 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java @@ -8,10 +8,7 @@ import java.util.*; -import org.osm2world.core.math.LineSegmentXZ; -import org.osm2world.core.math.PolygonWithHolesXZ; -import org.osm2world.core.math.SimplePolygonXZ; -import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.Intersection; import org.osm2world.core.math.datastructures.IndexGrid; import org.osm2world.core.math.datastructures.SpatialIndex; @@ -195,7 +192,9 @@ private static final Collection facesFromFullyNodedGraph(Col remainingEdges.removeAll(currentPath); List vertexLoop = currentPath.stream().map(e -> e.p1).collect(toList()); - faces.add(new SimplePolygonXZ(vertexLoop)); + try { + faces.add(new SimplePolygonXZ(vertexLoop)); + } catch (InvalidGeometryException ignored) { /* skip tiny or degenerate face */ } } From 5196ff654ebf6afbcea64b60aa07e77c9c4e0279 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 15:32:44 +0100 Subject: [PATCH 77/85] Shortcut triangulation for triangles represented as polygons --- .../java/org/osm2world/core/math/PolygonWithHolesXZ.java | 6 +++++- src/main/java/org/osm2world/core/math/SimplePolygonXZ.java | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java b/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java index 2d4441d1f..07c43280c 100644 --- a/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java +++ b/src/main/java/org/osm2world/core/math/PolygonWithHolesXZ.java @@ -55,7 +55,11 @@ public TriangleXZ asTriangleXZ() { @Override public List getTriangulation() { - return TriangulationUtil.triangulate(this); + if (holes.isEmpty()) { + return outerPolygon.getTriangulation(); + } else { + return TriangulationUtil.triangulate(this); + } } @Override diff --git a/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java b/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java index fecc7a06d..2b9572e58 100644 --- a/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java +++ b/src/main/java/org/osm2world/core/math/SimplePolygonXZ.java @@ -398,6 +398,10 @@ public SimplePolygonXZ getSimplifiedPolygon() { @Override public List getTriangulation() { + if (vertexLoop.size() == 4) { + return List.of(new TriangleXZ(vertexLoop.get(0), vertexLoop.get(1), vertexLoop.get(2))); + } + List result = TriangulationUtil.triangulate(this); //ensure that the triangles have the same winding as this shape From bb560b80d3ef2ef2e68ce2f96336f8e8b5555ae8 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Thu, 28 Nov 2024 16:05:41 +0100 Subject: [PATCH 78/85] Avoid caching geometry during attachment surface creation --- .../core/world/data/CachingProceduralWorldObject.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java index d82de4802..34b620359 100644 --- a/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java +++ b/src/main/java/org/osm2world/core/world/data/CachingProceduralWorldObject.java @@ -1,6 +1,5 @@ package org.osm2world.core.world.data; -import java.util.Collection; import java.util.List; import javax.annotation.Nullable; @@ -8,7 +7,6 @@ import org.osm2world.core.target.common.mesh.LevelOfDetail; import org.osm2world.core.target.common.mesh.Mesh; import org.osm2world.core.target.common.model.ModelInstance; -import org.osm2world.core.world.attachment.AttachmentSurface; /** * subtype of {@link ProceduralWorldObject} which caches internal results to avoid repeated calculations @@ -38,12 +36,6 @@ public List getSubModels() { return target.subModels; } - @Override - public Collection getAttachmentSurfaces() { - fillTargetIfNecessary(); - return target.attachmentSurfaces; - } - /** * if results depend on LOD, returns the currently configured LOD. * Can be null if this {@link ProceduralWorldObject} always produces geometry for all LOD. From ec5425742766d8c1f2ac6ac181a994c2ac602960 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 4 Apr 2023 14:22:00 +0200 Subject: [PATCH 79/85] Clean up LineSegmentIntersectionFinder code --- .../algorithms/FaceDecompositionUtil.java | 10 +-- .../LineSegmentIntersectionFinder.java | 77 ++----------------- .../LineSegmentIntersectionFinderTest.java | 32 ++++---- ...mpleLineSegmentIntersectionFinderTest.java | 8 +- 4 files changed, 33 insertions(+), 94 deletions(-) diff --git a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java index d7f0ed863..b61049cca 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java @@ -85,10 +85,10 @@ public static final Collection facesFromGraph(List> newIntersections = new ArrayList<>(); for (Iterator> iterator = intersections.iterator(); iterator.hasNext();) { Intersection intersection = iterator.next(); - VectorXZ closestKnownPoint = knownPoints.stream().min(comparingDouble(intersection.pos::distanceTo)).get(); - if (closestKnownPoint.distanceTo(intersection.pos) < SNAP_DISTANCE) { + VectorXZ closestKnownPoint = knownPoints.stream().min(comparingDouble(intersection.pos()::distanceTo)).get(); + if (closestKnownPoint.distanceTo(intersection.pos()) < SNAP_DISTANCE) { iterator.remove(); - newIntersections.add(new Intersection<>(closestKnownPoint, intersection.segmentA, intersection.segmentB)); + newIntersections.add(new Intersection<>(closestKnownPoint, intersection.segmentA(), intersection.segmentB())); } } intersections.addAll(newIntersections); @@ -97,8 +97,8 @@ public static final Collection facesFromGraph(List intersectionPointsPerSegment = HashMultimap.create(); //deduplicates values for (Intersection intersection : intersections) { - intersectionPointsPerSegment.put(intersection.segmentA, intersection.pos); - intersectionPointsPerSegment.put(intersection.segmentB, intersection.pos); + intersectionPointsPerSegment.put(intersection.segmentA(), intersection.pos()); + intersectionPointsPerSegment.put(intersection.segmentB(), intersection.pos()); } for (LineSegmentXZ segment : segments) { intersectionPointsPerSegment.putAll(segment, segment.vertices()); diff --git a/src/main/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinder.java b/src/main/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinder.java index 0559e0d2d..9195f2f21 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinder.java +++ b/src/main/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinder.java @@ -3,13 +3,7 @@ import static java.lang.Double.NaN; import static org.osm2world.core.math.GeometryUtil.getTrueLineSegmentIntersection; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.PriorityQueue; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import java.util.function.Function; import javax.annotation.Nullable; @@ -30,60 +24,11 @@ public final class LineSegmentIntersectionFinder { private LineSegmentIntersectionFinder() {} /** an intersection detected by the finder */ - public static class Intersection { - - public final VectorXZ pos; - public final E segmentA; - public final E segmentB; - - public Intersection(VectorXZ pos, E segmentA, E segmentB) { - this.pos = pos; - this.segmentA = segmentA; - this.segmentB = segmentB; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((pos == null) ? 0 : pos.hashCode()); - result = prime * result + ((segmentA == null) ? 0 : segmentA.hashCode()); - result = prime * result + ((segmentB == null) ? 0 : segmentB.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Intersection other = (Intersection) obj; - if (pos == null) { - if (other.pos != null) - return false; - } else if (!pos.equals(other.pos)) - return false; - if (segmentA == null) { - if (other.segmentA != null) - return false; - } else if (!segmentA.equals(other.segmentA)) - return false; - if (segmentB == null) { - if (other.segmentB != null) - return false; - } else if (!segmentB.equals(other.segmentB)) - return false; - return true; - } - + public record Intersection(VectorXZ pos, E segmentA, E segmentB) { @Override public String toString() { return pos.toString(); } - } /** @@ -120,7 +65,7 @@ public static final List> findAllIntersections(Iterable intersection : rawResult) { for (S segmentA : originalSegmentsMap.get(intersection.segmentA)) { for (S segmentB : originalSegmentsMap.get(intersection.segmentB)) { - result.add(new Intersection(intersection.pos, segmentA, segmentB)); + result.add(new Intersection<>(intersection.pos, segmentA, segmentB)); } } } @@ -147,8 +92,7 @@ private static final List> findAllIntersections_imp /* set up the event queue */ - PriorityQueue eventQueue = new PriorityQueue<>(); - eventQueue.addAll(beginEndEvents); + PriorityQueue eventQueue = new PriorityQueue<>(beginEndEvents); /* run the sweep */ @@ -232,7 +176,7 @@ private static final void insertIntersectionIfAny(PriorityQueue eventQueu if (pos != null && pos.x >= currentSweeplineX) { //avoid re-inserting intersections that are "old news" - eventQueue.add(new IntersectionEvent(new Intersection(pos, segmentA, segmentB))); + eventQueue.add(new IntersectionEvent(new Intersection<>(pos, segmentA, segmentB))); } } @@ -334,13 +278,8 @@ public String toString() { } - private static class IntersectionEvent implements Event { - - public final Intersection intersection; - - public IntersectionEvent(Intersection intersection) { - this.intersection = intersection; - } + private record IntersectionEvent( + Intersection intersection) implements Event { @Override public double getXPosition() { @@ -383,7 +322,7 @@ public double getCurrentX() { * It's ok for the comparator to be mutable: only changes at intersections, and there will be an event for each. */ private final Comparator comparator = (OrderedSegment s1, OrderedSegment s2) -> { - if (currentX == NaN) throw new IllegalStateException("sweeplineX unset"); + if (Double.isNaN(currentX)) throw new IllegalStateException("sweeplineX unset"); double x = currentX + 1e-5; //small offset ensures a sensible order if we're exactly at an intersection VectorXZ p1 = new VectorXZ(x, s1.evaluateAtX(x)); VectorXZ p2 = new VectorXZ(x, s2.evaluateAtX(x)); diff --git a/src/test/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinderTest.java b/src/test/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinderTest.java index 885ad0813..710e53121 100644 --- a/src/test/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinderTest.java +++ b/src/test/java/org/osm2world/core/math/algorithms/LineSegmentIntersectionFinderTest.java @@ -1,22 +1,22 @@ package org.osm2world.core.math.algorithms; -import static com.google.common.collect.Sets.newHashSet; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.findAllIntersections; -import static org.osm2world.core.test.TestUtil.assertAlmostEquals; +import org.junit.Ignore; +import org.junit.Test; +import org.osm2world.core.math.LineSegmentXZ; +import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.Intersection; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.junit.Ignore; -import org.junit.Test; -import org.osm2world.core.math.LineSegmentXZ; -import org.osm2world.core.math.VectorXZ; -import org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.Intersection; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.findAllIntersections; +import static org.osm2world.core.test.TestUtil.assertAlmostEquals; public class LineSegmentIntersectionFinderTest { @@ -29,7 +29,7 @@ public void testFindAllIntersections1() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(5, 0)), result); @@ -49,7 +49,7 @@ public void testFindAllIntersections2() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(0, 0)), result); @@ -65,7 +65,7 @@ public void testFindAllIntersections3() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(0, 0), new VectorXZ(0.5, -0.5)), result); @@ -88,7 +88,7 @@ public void testFindAllIntersections_duplicates() { assertEquals(3, result.size()); Set resultSet = new HashSet<>(); - result.forEach(it -> resultSet.add(it.pos)); + result.forEach(it -> resultSet.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(5, 0)), resultSet); } @@ -106,7 +106,7 @@ public void testFindAllIntersections_vertical() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(4, 4)), result); diff --git a/src/test/java/org/osm2world/core/math/algorithms/SimpleLineSegmentIntersectionFinderTest.java b/src/test/java/org/osm2world/core/math/algorithms/SimpleLineSegmentIntersectionFinderTest.java index 23e7f4c63..b6f14ca3f 100644 --- a/src/test/java/org/osm2world/core/math/algorithms/SimpleLineSegmentIntersectionFinderTest.java +++ b/src/test/java/org/osm2world/core/math/algorithms/SimpleLineSegmentIntersectionFinderTest.java @@ -29,7 +29,7 @@ public void testFindAllIntersections1() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(5, 0)), result); @@ -49,7 +49,7 @@ public void testFindAllIntersections2() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(0, 0)), result); @@ -65,7 +65,7 @@ public void testFindAllIntersections3() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(0, 0), new VectorXZ(0.5, -0.5)), result); @@ -84,7 +84,7 @@ public void testFindAllIntersections_vertical() { ); Set result = new HashSet<>(); - findAllIntersections(testData).forEach(it -> result.add(it.pos)); + findAllIntersections(testData).forEach(it -> result.add(it.pos())); assertAlmostEquals(newHashSet(new VectorXZ(4, 4)), result); From 23e7dabd63dd80c9fde76d7f847011505de4b909 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Fri, 29 Nov 2024 15:32:08 +0100 Subject: [PATCH 80/85] Make MapSegment a BoundedObject --- .../core/map_data/data/MapSegment.java | 11 +++++++++- .../core/map_data/data/MapWaySegment.java | 20 ++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/osm2world/core/map_data/data/MapSegment.java b/src/main/java/org/osm2world/core/map_data/data/MapSegment.java index b11f6222d..7f7f8e210 100644 --- a/src/main/java/org/osm2world/core/map_data/data/MapSegment.java +++ b/src/main/java/org/osm2world/core/map_data/data/MapSegment.java @@ -1,7 +1,11 @@ package org.osm2world.core.map_data.data; +import static org.osm2world.core.math.AxisAlignedRectangleXZ.bbox; + import java.util.List; +import org.osm2world.core.math.AxisAlignedRectangleXZ; +import org.osm2world.core.math.BoundedObject; import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.VectorXZ; @@ -12,7 +16,7 @@ * * @see MapData */ -public abstract class MapSegment { +public abstract class MapSegment implements BoundedObject { protected final MapNode startNode; protected final MapNode endNode; @@ -126,4 +130,9 @@ public static MapElement getElement(MapSegment s) { } } + @Override + public AxisAlignedRectangleXZ boundingBox() { + return bbox(List.of(getStartNode().getPos(), getEndNode().getPos())); + } + } diff --git a/src/main/java/org/osm2world/core/map_data/data/MapWaySegment.java b/src/main/java/org/osm2world/core/map_data/data/MapWaySegment.java index 776c93bd3..d971301bc 100644 --- a/src/main/java/org/osm2world/core/map_data/data/MapWaySegment.java +++ b/src/main/java/org/osm2world/core/map_data/data/MapWaySegment.java @@ -1,19 +1,16 @@ package org.osm2world.core.map_data.data; -import com.google.common.collect.Iterables; -import org.osm2world.core.map_data.data.MapRelation.Element; -import org.osm2world.core.map_data.data.overlaps.MapIntersectionWW; -import org.osm2world.core.map_data.data.overlaps.MapOverlap; -import org.osm2world.core.math.AxisAlignedRectangleXZ; -import org.osm2world.core.world.data.WaySegmentWorldObject; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import static java.util.Arrays.asList; -import static org.osm2world.core.math.AxisAlignedRectangleXZ.bbox; +import org.osm2world.core.map_data.data.MapRelation.Element; +import org.osm2world.core.map_data.data.overlaps.MapIntersectionWW; +import org.osm2world.core.map_data.data.overlaps.MapOverlap; +import org.osm2world.core.world.data.WaySegmentWorldObject; + +import com.google.common.collect.Iterables; /** @@ -68,11 +65,6 @@ public Iterable getIntersectionsWW() { return Iterables.filter(overlaps, MapIntersectionWW.class); } - @Override - public AxisAlignedRectangleXZ boundingBox() { - return bbox(asList(startNode.getPos(), endNode.getPos())); - } - @Override public List getRepresentations() { return representations; From 2d3ffc717ccdf4bedf7f494a3d83f61b05a5c4d3 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Mon, 2 Dec 2024 16:39:43 +0100 Subject: [PATCH 81/85] Work around infinite loops in FaceDecompositionUtil --- .../core/math/algorithms/FaceDecompositionUtil.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java index b61049cca..c55842e2e 100644 --- a/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java +++ b/src/main/java/org/osm2world/core/math/algorithms/FaceDecompositionUtil.java @@ -8,6 +8,7 @@ import java.util.*; +import org.osm2world.core.conversion.ConversionLog; import org.osm2world.core.math.*; import org.osm2world.core.math.algorithms.LineSegmentIntersectionFinder.Intersection; import org.osm2world.core.math.datastructures.IndexGrid; @@ -171,6 +172,7 @@ private static final Collection facesFromFullyNodedGraph(Col Set remainingEdges = new HashSet<>(directedEdges); List faces = new ArrayList<>(); + outer: while (!remainingEdges.isEmpty()) { LinkedList currentPath = new LinkedList<>(); @@ -187,6 +189,13 @@ private static final Collection facesFromFullyNodedGraph(Col currentPath.add(outgoingEdges.get(outgoingIndex)); + if (currentPath.size() > 10000) { + // likely an infinite loop + // FIXME: debug the causes of this kind of problem + ConversionLog.error("Path too long while attempting to build a face"); + break outer; + } + } remainingEdges.removeAll(currentPath); From 48246a14ac48d82ece3706829b697b30b0a46d92 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 3 Dec 2024 12:05:10 +0100 Subject: [PATCH 82/85] Introduce TagSet.of(Map) --- .../creation/OSMToMapDataConverter.java | 16 ++++--- .../osm2world/core/map_data/data/TagSet.java | 42 +++++++++++-------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java b/src/main/java/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java index ab699f5d9..05d375dd2 100644 --- a/src/main/java/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java +++ b/src/main/java/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java @@ -24,7 +24,6 @@ import org.osm2world.core.osm.ruleset.Ruleset; import de.topobyte.osm4j.core.model.iface.*; -import de.topobyte.osm4j.core.model.impl.Tag; import de.topobyte.osm4j.core.resolve.EntityNotFoundException; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; @@ -80,7 +79,7 @@ private void createMapElements(final OSMData osmData, MapMetadata metadata, /* create MapNode for each OSM node */ - final TLongObjectMap nodeIdMap = new TLongObjectHashMap(); + final TLongObjectMap nodeIdMap = new TLongObjectHashMap<>(); for (OsmNode node : osmData.getNodes()) { VectorXZ nodePos = mapProjection.toXZ(node.getLatitude(), node.getLongitude()); @@ -95,12 +94,11 @@ private void createMapElements(final OSMData osmData, MapMetadata metadata, /* ... based on multipolygons */ - forEach(osmData.getRelations(), (OsmRelation relation ) -> { + forEach(osmData.getRelations(), (OsmRelation relation) -> { - Map tags = getTagsAsMap(relation); + TagSet tags = TagSet.of(getTagsAsMap(relation)); - String value = tags.get(MULTIPOLYON_TAG.getKey()); - if (!MULTIPOLYON_TAG.getValue().equals(value)) { + if (!tags.contains(MULTIPOLYON_TAG)) { return; } @@ -275,10 +273,10 @@ public static TagSet tagsOfEntity(OsmEntity entity) { if (entity.getNumberOfTags() == 0) return TagSet.of(); - org.osm2world.core.map_data.data.Tag[] tags = - new org.osm2world.core.map_data.data.Tag[entity.getNumberOfTags()]; + Tag[] tags = + new Tag[entity.getNumberOfTags()]; for (int i = 0; i < entity.getNumberOfTags(); i++) { - tags[i] = new org.osm2world.core.map_data.data.Tag(entity.getTag(i).getKey(), entity.getTag(i).getValue()); + tags[i] = new Tag(entity.getTag(i).getKey(), entity.getTag(i).getValue()); } return TagSet.of(tags); diff --git a/src/main/java/org/osm2world/core/map_data/data/TagSet.java b/src/main/java/org/osm2world/core/map_data/data/TagSet.java index b859d3304..ac7f19109 100644 --- a/src/main/java/org/osm2world/core/map_data/data/TagSet.java +++ b/src/main/java/org/osm2world/core/map_data/data/TagSet.java @@ -2,10 +2,7 @@ import static java.util.Arrays.sort; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; +import java.util.*; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -48,10 +45,10 @@ public static final TagSet of() { * @throws IllegalArgumentException if keys are not unique */ public static final TagSet of(Collection tags) { - switch (tags.size()) { - case 0: return EMPTY_SET; - default: return new TagSet(tags.toArray(new Tag[0])); - } + return switch (tags.size()) { + case 0 -> EMPTY_SET; + default -> new TagSet(tags.toArray(new Tag[0])); + }; } /** @@ -59,10 +56,10 @@ public static final TagSet of(Collection tags) { * @throws IllegalArgumentException if keys are not unique */ public static final TagSet of(Tag... tags) { - switch (tags.length) { - case 0: return EMPTY_SET; - default: return new TagSet(tags.clone()); - } + return switch (tags.length) { + case 0 -> EMPTY_SET; + default -> new TagSet(tags.clone()); + }; } /** @@ -86,6 +83,19 @@ public static final TagSet of(String... keyValuePairs) { } + /** + * creates a {@link TagSet} from a {@link Map} mapping keys to values + */ + public static final TagSet of(Map keyValueMap) { + if (keyValueMap.isEmpty()) { + return EMPTY_SET; + } else { + return new TagSet(keyValueMap.entrySet().stream() + .map(e -> new Tag(e.getKey(), e.getValue())) + .toArray(Tag[]::new)); + } + } + /** returns true if this set contains any tags */ public boolean isEmpty() { return tags.length == 0; @@ -212,16 +222,12 @@ public Iterator iterator() { /** two {@link TagSet}s are equal iff they contain the same tags */ @Override public boolean equals(Object obj) { - if (obj instanceof TagSet) { - return Arrays.equals(this.tags, ((TagSet) obj).tags); - } else { - return false; - } + return obj instanceof TagSet otherSet && Arrays.equals(this.tags, otherSet.tags); } @Override public int hashCode() { - return tags.hashCode(); + return Arrays.hashCode(tags); } @Override From ebf18d7acd67a554e40c49c9c2051b33ad0ed83f Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Tue, 3 Dec 2024 13:04:32 +0100 Subject: [PATCH 83/85] Introduce a maxLogEntries setting which defaults to 100 --- .../java/org/osm2world/console/Output.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index facec47fe..05d9d1e79 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -1,5 +1,6 @@ package org.osm2world.console; +import static java.lang.Math.ceil; import static java.time.Instant.now; import static java.util.Map.entry; import static java.util.stream.Collectors.toList; @@ -17,7 +18,9 @@ import java.time.format.DateTimeFormatter; import java.util.EnumSet; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.IntStream; import javax.annotation.Nullable; @@ -283,7 +286,7 @@ public static void output(Configuration config, } fileNameBase = "osm2world_log_" + fileNameBase; - writeLogFiles(logDir, fileNameBase, perfListener); + writeLogFiles(logDir, fileNameBase, perfListener, config); } @@ -291,7 +294,8 @@ public static void output(Configuration config, } - private static void writeLogFiles(File logDir, String fileNameBase, PerformanceListener perfListener) { + private static void writeLogFiles(File logDir, String fileNameBase, PerformanceListener perfListener, + Configuration config) { double totalTime = Duration.between(perfListener.startTime, now()).toMillis() / 1000.0; @@ -330,7 +334,9 @@ private static void writeLogFiles(File logDir, String fileNameBase, PerformanceL File outputFile = logDir.toPath().resolve(fileNameBase + ".txt.gz").toFile(); TargetUtil.writeFileWithCompression(outputFile, compression, outputStream -> { try (var printStream = new PrintStream(outputStream)) { + printStream.println("Runtime (seconds):\nTotal: " + totalTime); + if (!timePerPhase.isEmpty()) { for (Phase phase : Phase.values()) { if (timePerPhase.containsKey(phase)) { @@ -338,8 +344,22 @@ private static void writeLogFiles(File logDir, String fileNameBase, PerformanceL } } } + printStream.println(); - ConversionLog.getLog().forEach(printStream::println); + + List entries = ConversionLog.getLog(); + int maxLogEntries = config.getInt("maxLogEntries", 100); + + if (entries.size() <= maxLogEntries) { + entries.forEach(printStream::println); + } else { + IntStream.range(0, maxLogEntries / 2) + .mapToObj(entries::get).forEach(printStream::println); + printStream.println("\n...\n"); + IntStream.range(entries.size() - (int)ceil(maxLogEntries / 2.0), entries.size()) + .mapToObj(entries::get).forEach(printStream::println); + } + } }); From 2d0ca0bfd115753aabc7abf5ba1534ea2e068a33 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 4 Dec 2024 10:36:45 +0100 Subject: [PATCH 84/85] Abolish the performancePrint parameter --- .../org/osm2world/console/CLIArguments.java | 3 --- .../java/org/osm2world/console/Output.java | 21 +------------------ .../console/CLIArgumentsGroupTest.java | 4 ++-- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/osm2world/console/CLIArguments.java b/src/main/java/org/osm2world/console/CLIArguments.java index 9d00d8753..b8d17cfb3 100644 --- a/src/main/java/org/osm2world/console/CLIArguments.java +++ b/src/main/java/org/osm2world/console/CLIArguments.java @@ -113,9 +113,6 @@ public interface CLIArguments { /* logging */ - @Option(description="writes execution times to the command line") - boolean getPerformancePrint(); - @Option(description="output directory for log files") File getLogDir(); boolean isLogDir(); diff --git a/src/main/java/org/osm2world/console/Output.java b/src/main/java/org/osm2world/console/Output.java index 05d9d1e79..bbd03d482 100644 --- a/src/main/java/org/osm2world/console/Output.java +++ b/src/main/java/org/osm2world/console/Output.java @@ -62,7 +62,7 @@ public static void output(Configuration config, CLIArguments sharedArgs = argumentsGroup.getRepresentative(); - var perfListener = new PerformanceListener(sharedArgs.getPerformancePrint()); + var perfListener = new PerformanceListener(); if (sharedArgs.isLogDir()) { ConversionLog.setConsoleLogLevels(EnumSet.of(ConversionLog.LogLevel.FATAL)); @@ -270,11 +270,6 @@ public static void output(Configuration config, throw e; } finally { - if (sharedArgs.getPerformancePrint()) { - long timeSec = Duration.between(perfListener.startTime, now()).getSeconds(); - System.out.println("finished after " + timeSec + " s"); - } - if (sharedArgs.isLogDir()) { File logDir = sharedArgs.getLogDir(); @@ -368,14 +363,8 @@ private static void writeLogFiles(File logDir, String fileNameBase, PerformanceL private static class PerformanceListener implements ProgressListener { public final Instant startTime = Instant.now(); - private final boolean printToSysout; - - public PerformanceListener(boolean printToSysout) { - this.printToSysout = printToSysout; - } private @Nullable Phase currentPhase = null; - private @Nullable Instant currentPhaseStart; private final Map phaseStarts = new HashMap<>(); private final Map phaseEnds = new HashMap<>(); @@ -400,18 +389,10 @@ public void updatePhase(Phase newPhase) { phaseStarts.put(newPhase, now()); if (currentPhase != null) { - phaseEnds.put(currentPhase, now()); - - if (printToSysout) { - long ms = Duration.between(currentPhaseStart, now()).toMillis(); - System.out.println("phase " + currentPhase + " finished after " + ms + " ms"); - } - } currentPhase = newPhase; - currentPhaseStart = now(); } diff --git a/src/test/java/org/osm2world/console/CLIArgumentsGroupTest.java b/src/test/java/org/osm2world/console/CLIArgumentsGroupTest.java index ab997defd..fbd14a3bf 100644 --- a/src/test/java/org/osm2world/console/CLIArgumentsGroupTest.java +++ b/src/test/java/org/osm2world/console/CLIArgumentsGroupTest.java @@ -31,9 +31,9 @@ public void testIsCompatible() throws ArgumentValidationException { /* test tileserver-style commands */ CLIArguments cliArgsA1 = CliFactory.parseArguments(CLIArguments.class, - "--config", "osm2world.config", "-i", "/o2wmaps/input/old/13_4231_2777.pbf", "-o", "/tmp/n_ogltile_4231_2777.ppm", "--resolution", "8192,4096", "--oview.tiles", "13,4231,2777", "--oview.from", "S", "--performancePrint", "--logDir", "/tmp/logs/"); + "--config", "osm2world.config", "-i", "/o2wmaps/input/old/13_4231_2777.pbf", "-o", "/tmp/n_ogltile_4231_2777.ppm", "--resolution", "8192,4096", "--oview.tiles", "13,4231,2777", "--oview.from", "S", "--logDir", "/tmp/logs/"); CLIArguments cliArgsA2 = CliFactory.parseArguments(CLIArguments.class, - "--config", "osm2world.config", "-i", "/o2wmaps/input/old/13_4231_2777.pbf", "-o", "/tmp/n_ogltile_4231_2777.ppm", "--resolution", "8192,4096", "--oview.tiles", "13,4231,2777", "--oview.from", "N", "--performancePrint", "--logDir", "/tmp/logs/"); + "--config", "osm2world.config", "-i", "/o2wmaps/input/old/13_4231_2777.pbf", "-o", "/tmp/n_ogltile_4231_2777.ppm", "--resolution", "8192,4096", "--oview.tiles", "13,4231,2777", "--oview.from", "N", "--logDir", "/tmp/logs/"); assertTrue(CLIArgumentsGroup.isCompatible(cliArgsA1, cliArgsA2)); From a1609d87d28752163b85b8486260ef541cdd0355 Mon Sep 17 00:00:00 2001 From: Tobias Knerr Date: Wed, 4 Dec 2024 11:20:40 +0100 Subject: [PATCH 85/85] Use a version of maven-javadoc-plugin suitable for older mvn --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 933f0f1ee..d980ba676 100644 --- a/pom.xml +++ b/pom.xml @@ -332,7 +332,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.10.1 + 3.6.3 all,-missing