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)));

Reply via email to