This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit fc44195bc6b33a84fc22ff9720a99940274ce32b Author: jsorel <[email protected]> AuthorDate: Thu Oct 15 17:01:32 2026 +0200 feat(Geometry): implement Polyhedron toPrimitive --- .../geometries/operation/GeometryProcessor.java | 3 + .../sis/geometries/operation/ToPrimitive.java | 35 +++++++++++- .../sis/geometries/operation/ToPrimitiveTest.java | 64 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java index fce4bccc90..5e62cce01e 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java @@ -38,6 +38,7 @@ import org.apache.sis.geometries.curve.LinearRing; import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.geometries.mesh.MeshPrimitiveVisitor; import org.apache.sis.geometries.mesh.MultiMeshPrimitive; +import org.apache.sis.geometries.solid.Polyhedron; import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.geometries.surface.PolyhedralSurface; import org.apache.sis.geometries.surface.Triangle; @@ -496,6 +497,8 @@ public final class GeometryProcessor { } else if (geom instanceof PolyhedralSurface<?> cdt) { //also handles TIN, which is a polyhedral surface of triangles. return ToPrimitive.toPrimitive(cdt); + } else if (geom instanceof Polyhedron cdt) { + return ToPrimitive.toPrimitive(cdt); } else if (geom instanceof GeometryCollection<?> cdt) { final GeometryType elementType = cdt.getElementType(); switch (elementType) { diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java index d3ad3251da..39f7bb5d6a 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java @@ -33,6 +33,7 @@ import org.apache.sis.geometries.internal.shared.ArrayDataPoints; import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.geometries.mesh.MultiMeshPrimitive; import org.apache.sis.geometries.operation.triangulate.EarClipping; +import org.apache.sis.geometries.solid.Polyhedron; import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.geometries.surface.PolyhedralSurface; import org.apache.sis.maths.Array; @@ -95,13 +96,41 @@ public final class ToPrimitive { * Each patch is triangulated and all triangles are merged in a single non-indexed mesh. */ public static MeshPrimitive.Triangles toPrimitive(PolyhedralSurface<?> geometry) throws OperationException { - final CoordinateReferenceSystem crs = geometry.getCoordinateReferenceSystem(); + final List<Polygon> faces = new ArrayList<>(); + for (int i = 0, n = geometry.getNumPatches(); i < n; i++) { + faces.add(geometry.getPatchN(i)); + } + return triangles(geometry.getCoordinateReferenceSystem(), faces); + } + + /** + * Transform a Polyhedron to a Triangles primitive. + * The faces of the exterior shell and of all the interior shells are triangulated together + * in a single mesh, each face keeping its orientation. + */ + public static MeshPrimitive.Triangles toPrimitive(Polyhedron geometry) throws OperationException { + final List<Polygon> faces = new ArrayList<>(); + final List<GeometryCollection<Polygon>> shells = new ArrayList<>(); + shells.add(geometry.getExteriorShell()); + shells.addAll(geometry.getInteriorShells()); + for (final GeometryCollection<Polygon> shell : shells) { + for (int i = 0, n = shell.getNumGeometries(); i < n; i++) { + faces.add(shell.getGeometryN(i)); + } + } + return triangles(geometry.getCoordinateReferenceSystem(), faces); + } + + /** + * Triangulates the given polygons into a single mesh of triangles, without index. + */ + private static MeshPrimitive.Triangles triangles(final CoordinateReferenceSystem crs, final List<Polygon> faces) { final int dimension = crs.getCoordinateSystem().getDimension(); final EarClipping triangulator = new EarClipping(); final List<double[]> triangles = new ArrayList<>(); - for (int i = 0, n = geometry.getNumPatches(); i < n; i++) { + for (final Polygon face : faces) { //the returned list is reused by the next call, so the coordinates are copied now. - for (final Vector<?>[] triangle : triangulator.triangulate(geometry.getPatchN(i))) { + for (final Vector<?>[] triangle : triangulator.triangulate(face)) { final double[] coordinates = new double[3 * dimension]; for (int v = 0; v < 3; v++) { for (int d = 0; d < dimension; d++) { diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java index cc48e16072..dd13ecd158 100644 --- a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java +++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java @@ -16,6 +16,7 @@ */ package org.apache.sis.geometries.operation; +import java.util.List; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryFactory; @@ -26,6 +27,10 @@ import org.apache.sis.geometries.GeometryCollection; import org.apache.sis.geometries.curve.LineString; import org.apache.sis.geometries.curve.LinearRing; import org.apache.sis.geometries.surface.Polygon; +import org.apache.sis.geometries.solid.Polyhedron; +import org.apache.sis.geometries.solid.polyhedron.Hexahedron; +import org.apache.sis.geometries.solid.polyhedron.Icosahedron; +import org.apache.sis.geometries.solid.polyhedron.Tetrahedron; import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.geometries.mesh.MultiMeshPrimitive; import org.apache.sis.maths.NDArrays; @@ -270,6 +275,65 @@ public class ToPrimitiveTest { assertEquals(20, trianglesArea3D(primitive), 1E-9); } + /** + * Returns the six faces of the cube between the given corners. + */ + private static GeometryCollection<Polygon> cube(final CoordinateReferenceSystem crs, + final double x0, final double y0, final double z0, final double x1, final double y1, final double z1) + { + final GeometryFactory f = GeometryFactory.DEFAULT; + return f.createMultiPolygon(crs, + f.createPolygon(ring3D(crs, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, x0,y0,z0), null), + f.createPolygon(ring3D(crs, x1,y0,z0, x1,y1,z0, x1,y1,z1, x1,y0,z1, x1,y0,z0), null), + f.createPolygon(ring3D(crs, x0,y0,z0, x1,y0,z0, x1,y0,z1, x0,y0,z1, x0,y0,z0), null), + f.createPolygon(ring3D(crs, x0,y1,z0, x0,y1,z1, x1,y1,z1, x1,y1,z0, x0,y1,z0), null), + f.createPolygon(ring3D(crs, x0,y0,z0, x0,y1,z0, x1,y1,z0, x1,y0,z0, x0,y0,z0), null), + f.createPolygon(ring3D(crs, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, x0,y0,z1), null)); + } + + /** + * A polyhedron becomes the triangles of the faces of its shell. + */ + @Test + public void testPolyhedron() { + final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic3D(); + final Polyhedron cube = GeometryFactory.DEFAULT.createPolyhedron(cube(crs, 0,0,0, 2,2,2), List.of()); + final Geometry result = new GeometryProcessor().toPrimitive(cube, null, null); + assertTrue(result instanceof MeshPrimitive.Triangles); + final MeshPrimitive primitive = (MeshPrimitive) result; + assertEquals(crs, primitive.getCoordinateReferenceSystem()); + assertEquals(36, primitive.getPositions().getLength(), "Expected 2 triangles per face."); + assertEquals(24, trianglesArea3D(primitive), 1E-9); + } + + /** + * The faces of the interior shells of a polyhedron are triangulated as well as the exterior ones. + */ + @Test + public void testPolyhedronWithInteriorShell() { + final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic3D(); + final Polyhedron hollow = GeometryFactory.DEFAULT.createPolyhedron(cube(crs, 0,0,0, 4,4,4), List.of(cube(crs, 1,1,1, 3,3,3))); + final MeshPrimitive primitive = (MeshPrimitive) new GeometryProcessor().toPrimitive(hollow, null, null); + assertEquals(72, primitive.getPositions().getLength(), "Expected 2 triangles per face of both shells."); + assertEquals(96 + 24, trianglesArea3D(primitive), 1E-9); + } + + /** + * The regular polyhedra compute their faces from shared vertices. + */ + @Test + public void testRegularPolyhedron() { + for (final Polyhedron polyhedron : new Polyhedron[] {new Tetrahedron(), new Hexahedron(), new Icosahedron()}) { + final MeshPrimitive primitive = (MeshPrimitive) new GeometryProcessor().toPrimitive(polyhedron, null, null); + double expected = 0; + final GeometryCollection<Polygon> shell = polyhedron.getExteriorShell(); + for (int i = 0; i < shell.getNumGeometries(); i++) { + expected += shell.getGeometryN(i).getArea().getValue().doubleValue(); + } + assertEquals(expected, trianglesArea3D(primitive), 1E-9, polyhedron.getClass().getSimpleName()); + } + } + private static LinearRing ring3D(final CoordinateReferenceSystem crs, final double... coordinates) { return GeometryFactory.DEFAULT.createLinearRing( GeometryFactory.DEFAULT.createDataPoints(NDArrays.of(crs, coordinates)));
