Skip to content

Commit

Permalink
Approximate extruded circles with a max error based on LOD
Browse files Browse the repository at this point in the history
  • Loading branch information
tordanik committed Nov 15, 2024
1 parent 73c61ad commit 274824e
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 12 deletions.
4 changes: 3 additions & 1 deletion src/main/java/org/osm2world/core/target/Target.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/org/osm2world/core/target/common/MeshTarget.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 */

Expand Down Expand Up @@ -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
Expand All @@ -202,14 +209,14 @@ public TriangleGeometry asTriangles() {

Collection<ShapeXZ> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ private void writeJson(OutputStream outputStream) throws IOException {

List<MeshProcessingStep> 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),
Expand Down

0 comments on commit 274824e

Please sign in to comment.