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 f427b9b0e8684927e3fefee055760acfb9a7f96c
Author: jsorel <[email protected]>
AuthorDate: Thu Aug 6 15:26:23 2026 +0200

    feat(Geometry): add polyhedrons faces and getFace method
---
 .../geometries/polyhedron/AbstractPolyhedron.java  | 129 +++++++++++++++++-
 .../sis/geometries/polyhedron/Dodecahedron.java    |  64 +++++++++
 .../sis/geometries/polyhedron/Hexahedron.java      |  42 ++++++
 .../sis/geometries/polyhedron/Icosahedron.java     |  64 +++++++++
 .../sis/geometries/polyhedron/Octahedron.java      |  42 ++++++
 .../polyhedron/RhombicTriacontahedron.java         | 101 ++++++++++++++
 .../sis/geometries/polyhedron/Tetrahedron.java     |  36 +++++
 .../polyhedron/TruncatedIcosahedron.java           | 150 +++++++++++++++++++++
 8 files changed, 627 insertions(+), 1 deletion(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/AbstractPolyhedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/AbstractPolyhedron.java
index 96ca8aa219..9697bcc55c 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/AbstractPolyhedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/AbstractPolyhedron.java
@@ -17,9 +17,18 @@
 package org.apache.sis.geometries.polyhedron;
 
 import java.util.List;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.LinearRing;
 import org.apache.sis.geometries.MultiPolygon;
+import org.apache.sis.geometries.Polygon;
 import org.apache.sis.geometries.Polyhedron;
+import org.apache.sis.geometries.Sphere;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.NDArrays;
+import org.apache.sis.geometries.math.ReadOnly;
+import org.apache.sis.geometries.math.Vector3D;
+import org.apache.sis.geometries.spherical.SphericalConvexPolygon;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
@@ -27,7 +36,40 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
  *
  * @author Johann Sorel (Geomatys)
  */
-public class AbstractPolyhedron extends AbstractGeometry implements Polyhedron{
+public abstract class AbstractPolyhedron extends AbstractGeometry implements 
Polyhedron{
+
+    /**
+     * Golden ratio, used to build the latitude constants of the icosahedral-
+     * symmetry solids (Icosahedron, Dodecahedron, RhombicTriacontahedron,
+     * TruncatedIcosahedron).
+     */
+    protected static final double PHI = (1 + Math.sqrt(5)) / 2;
+
+    /**
+     * Latitude of a cube corner (atan(1/sqrt(2)), equivalently 
asin(1/sqrt(3))),
+     * used by Tetrahedron, Hexahedron and Dodecahedron.
+     */
+    protected static final double CUBE_LAT = Math.atan(1 / Math.sqrt(2));
+
+    /**
+     * atan(phi), used by Icosahedron and RhombicTriacontahedron.
+     */
+    protected static final double ATAN_PHI = Math.atan(PHI);
+
+    /**
+     * atan(1/phi) = PI/2 - atan(phi), used by Icosahedron and 
RhombicTriacontahedron.
+     */
+    protected static final double ATAN_INV_PHI = Math.PI/2 - ATAN_PHI;
+
+    /**
+     * atan(phi^2), used by Dodecahedron and RhombicTriacontahedron.
+     */
+    protected static final double ATAN_PHI2 = Math.atan(PHI*PHI);
+
+    /**
+     * atan(1/phi^2) = PI/2 - atan(phi^2), used by Dodecahedron and 
RhombicTriacontahedron.
+     */
+    protected static final double ATAN_INV_PHI2 = Math.PI/2 - ATAN_PHI2;
 
     @Override
     public CoordinateReferenceSystem getCoordinateReferenceSystem() {
@@ -59,4 +101,89 @@ public class AbstractPolyhedron extends AbstractGeometry 
implements Polyhedron{
         throw new UnsupportedOperationException("Not supported yet."); // 
Generated from 
nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody
     }
 
+    /**
+     * @return number of faces on the polyhedron
+     */
+    public abstract int getFaceCount();
+
+    /**
+     * @return face polygon
+     */
+    public abstract Polygon getFace(int index);
+
+    /**
+     * Get the face index intersecting the given unit vector.
+     *
+     * @param unitVector a unit vector from the center of the polyhedron
+     * @return the face index intersecting the vector
+     */
+    public abstract int getFace(ReadOnly.Vector<?> unitVector);
+
+
+    /**
+     * Build a unit direction vector from a latitude/longitude, in radians.
+     *
+     * @param latRad latitude in radians, in range [-PI/2 .. PI/2]
+     * @param lonRad longitude in radians, in range [-PI .. PI]
+     * @return unit direction vector
+     */
+    protected static ReadOnly.Vector<?> fromLatLon(double latRad, double 
lonRad) {
+        return new Vector3D.Double().setFromLatLon(latRad, lonRad);
+    }
+
+    /**
+     * Build a direction vector from a latitude/longitude, in radians, scaled
+     * by the given radius.
+     *
+     * @param latRad latitude in radians, in range [-PI/2 .. PI/2]
+     * @param lonRad longitude in radians, in range [-PI .. PI]
+     * @param radius scale applied to the unit direction vector
+     * @return direction vector
+     */
+    protected static ReadOnly.Vector<?> fromLatLon(double latRad, double 
lonRad, double radius) {
+        return new Vector3D.Double().setFromLatLon(latRad, 
lonRad).scale(radius);
+    }
+
+    /**
+     * Find which face's solid-angle cone (as seen from the polyhedron center)
+     * contains the given unit vector.
+     * <p>
+     * Faces must be convex, with vertices in CCW order viewed from outside
+     * the polyhedron. For each face, the vector lies in its cone if it is on
+     * the inner side of every edge plane, following the same triple-product
+     * test as {@link 
org.apache.sis.geometries.spherical.SphericalTriangle#contains }
+     * generalized to polygons with any number of vertices. This holds for any
+     * convex polyhedron centered on the origin : each ray from the center
+     * crosses exactly one face.
+     *
+     * @param vertices polyhedron vertices
+     * @param faces polyhedron faces, as CCW vertex indices into {@code 
vertices}
+     * @param unitVector vector to test
+     * @return matching face index, or -1 if none matched
+     */
+    protected static int nearestFace(Array vertices, int[][] faces, 
ReadOnly.Vector<?> unitVector) {
+        final Sphere sphere = new Sphere(3);
+        for (int f = 0; f < faces.length; f++) {
+            final Array polyArray = NDArrays.subset(vertices, faces[f]);
+            final SphericalConvexPolygon face = new 
SphericalConvexPolygon(sphere, polyArray);
+            if (face.contains(unitVector)) {
+                return f;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Build the polygon of one face.
+     *
+     * @param vertices polyhedron vertices
+     * @param face face vertex indices into {@code vertices}, in CCW order 
viewed from outside
+     * @return face polygon
+     */
+    protected static Polygon toPolygon(Array vertices, int[] face) {
+        final Array positions = NDArrays.subset(vertices, face);
+        final LinearRing ring = 
GeometryFactory.createLinearRing(GeometryFactory.createSequence(positions));
+        return GeometryFactory.createPolygon(ring, null);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Dodecahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Dodecahedron.java
index 388b64da6c..ff7b851787 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Dodecahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Dodecahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/RegularDodecahedron.html
@@ -23,4 +29,62 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class Dodecahedron extends AbstractPolyhedron{
 
+    /**
+     * Vertices are the dual of {@link Icosahedron} (each icosahedron face
+     * centroid, normalized), so both solids share a consistent orientation.
+     */
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon(     ATAN_PHI2,                         0),
+        fromLatLon(     ATAN_PHI2,                   Math.PI),
+        fromLatLon(      CUBE_LAT,                 Math.PI/4),
+        fromLatLon(      CUBE_LAT,               3*Math.PI/4),
+        fromLatLon( ATAN_INV_PHI2,                 Math.PI/2),
+        fromLatLon(    -ATAN_PHI2,                         0),
+        fromLatLon(    -ATAN_PHI2,                   Math.PI),
+        fromLatLon(     -CUBE_LAT,                 Math.PI/4),
+        fromLatLon(     -CUBE_LAT,               3*Math.PI/4),
+        fromLatLon(-ATAN_INV_PHI2,                 Math.PI/2),
+        fromLatLon(      CUBE_LAT,                -Math.PI/4),
+        fromLatLon(      CUBE_LAT,              -3*Math.PI/4),
+        fromLatLon( ATAN_INV_PHI2,                -Math.PI/2),
+        fromLatLon(     -CUBE_LAT,                -Math.PI/4),
+        fromLatLon(     -CUBE_LAT,              -3*Math.PI/4),
+        fromLatLon(-ATAN_INV_PHI2,                -Math.PI/2),
+        fromLatLon(             0,             ATAN_INV_PHI2),
+        fromLatLon(             0,            -ATAN_INV_PHI2),
+        fromLatLon(             0,   Math.PI - ATAN_INV_PHI2),
+        fromLatLon(             0, -(Math.PI - ATAN_INV_PHI2))
+    }, true).build();
+
+    private static final int[][] FACES = {
+        { 3,  1,  0,  2,  4,  3},
+        {12, 10,  0,  1, 11, 12},
+        {16,  2,  0, 10, 17, 16},
+        {19, 11,  1,  3, 18, 19},
+        { 9,  4,  2, 16,  7,  9},
+        { 8, 18,  3,  4,  9,  8},
+        { 9,  7,  5,  6,  8,  9},
+        {14,  6,  5, 13, 15, 14},
+        {17, 13,  5,  7, 16, 17},
+        {18,  8,  6, 14, 19, 18},
+        {13, 17, 10, 12, 15, 13},
+        {15, 12, 11, 19, 14, 15}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Hexahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Hexahedron.java
index e970cd38d0..58f303a468 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Hexahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Hexahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/Cube.html
@@ -23,4 +29,40 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class Hexahedron extends AbstractPolyhedron{
 
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon( CUBE_LAT,    Math.PI/4),
+        fromLatLon(-CUBE_LAT,    Math.PI/4),
+        fromLatLon( CUBE_LAT,   -Math.PI/4),
+        fromLatLon(-CUBE_LAT,   -Math.PI/4),
+        fromLatLon( CUBE_LAT,  3*Math.PI/4),
+        fromLatLon(-CUBE_LAT,  3*Math.PI/4),
+        fromLatLon( CUBE_LAT, -3*Math.PI/4),
+        fromLatLon(-CUBE_LAT, -3*Math.PI/4)
+    }, true).build();
+
+    private static final int[][] FACES = {
+        {1, 0, 2, 3, 1},
+        {4, 0, 1, 5, 4},
+        {2, 0, 4, 6, 2},
+        {5, 1, 3, 7, 5},
+        {3, 2, 6, 7, 3},
+        {6, 4, 5, 7, 6}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Icosahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Icosahedron.java
index a2fca45320..3f874e9c2e 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Icosahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Icosahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/RegularIcosahedron.html
@@ -23,4 +29,62 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class Icosahedron extends AbstractPolyhedron{
 
+    /**
+     * Vertices are all cyclic permutations of (0, +-1, +-phi), normalized,
+     * where phi is the golden ratio.
+     */
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon(     ATAN_PHI,            Math.PI/2),
+        fromLatLon(    -ATAN_PHI,            Math.PI/2),
+        fromLatLon(     ATAN_PHI,           -Math.PI/2),
+        fromLatLon(    -ATAN_PHI,           -Math.PI/2),
+        fromLatLon( ATAN_INV_PHI,                    0),
+        fromLatLon( ATAN_INV_PHI,              Math.PI),
+        fromLatLon(-ATAN_INV_PHI,                    0),
+        fromLatLon(-ATAN_INV_PHI,              Math.PI),
+        fromLatLon(            0,             ATAN_PHI),
+        fromLatLon(            0,            -ATAN_PHI),
+        fromLatLon(            0,   Math.PI - ATAN_PHI),
+        fromLatLon(            0, -(Math.PI - ATAN_PHI))
+    }, true).build();
+
+    private static final int[][] FACES = {
+        { 4,  0,  2,  4},
+        { 2,  0,  5,  2},
+        { 8,  0,  4,  8},
+        { 5,  0, 10,  5},
+        {10,  0,  8, 10},
+        { 3,  1,  6,  3},
+        { 7,  1,  3,  7},
+        { 6,  1,  8,  6},
+        {10,  1,  7, 10},
+        { 8,  1, 10,  8},
+        { 4,  2,  9,  4},
+        {11,  2,  5, 11},
+        { 9,  2, 11,  9},
+        { 9,  3,  6,  9},
+        { 7,  3, 11,  7},
+        {11,  3,  9, 11},
+        { 8,  4,  6,  8},
+        { 6,  4,  9,  6},
+        { 7,  5, 10,  7},
+        {11,  5,  7, 11}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Octahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Octahedron.java
index b097e0c12b..60cf48bfa9 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Octahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Octahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/RegularOctahedron.html
@@ -23,4 +29,40 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class Octahedron extends AbstractPolyhedron{
 
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon(         0,          0),
+        fromLatLon(         0,    Math.PI),
+        fromLatLon(         0,  Math.PI/2),
+        fromLatLon(         0, -Math.PI/2),
+        fromLatLon( Math.PI/2,          0),
+        fromLatLon(-Math.PI/2,          0)
+    }, true).build();
+
+    private static final int[][] FACES = {
+        {4, 0, 2, 4},
+        {2, 0, 5, 2},
+        {3, 0, 4, 3},
+        {5, 0, 3, 5},
+        {2, 1, 4, 2},
+        {5, 1, 2, 5},
+        {4, 1, 3, 4},
+        {3, 1, 5, 3}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/RhombicTriacontahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/RhombicTriacontahedron.java
index a5c12fdc02..1903669c58 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/RhombicTriacontahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/RhombicTriacontahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/RhombicTriacontahedron.html
@@ -23,4 +29,99 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class RhombicTriacontahedron extends AbstractPolyhedron{
 
+    /**
+     * Scale applied to the 20 dodecahedron-direction vertices, so every face
+     * is a planar golden rhombus.
+     */
+    private static final double INNER_RADIUS = 0.9105929973100293;
+
+    /**
+     * The first 12 vertices are the {@link Icosahedron} vertices (degree 5),
+     * the last 20 are the {@link Dodecahedron} vertices (degree 3) scaled so
+     * every face is a planar golden rhombus.
+     */
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon(      ATAN_PHI,                 Math.PI/2),
+        fromLatLon(     -ATAN_PHI,                 Math.PI/2),
+        fromLatLon(      ATAN_PHI,                -Math.PI/2),
+        fromLatLon(     -ATAN_PHI,                -Math.PI/2),
+        fromLatLon(  ATAN_INV_PHI,                         0),
+        fromLatLon(  ATAN_INV_PHI,                   Math.PI),
+        fromLatLon( -ATAN_INV_PHI,                         0),
+        fromLatLon( -ATAN_INV_PHI,                   Math.PI),
+        fromLatLon(             0,                  ATAN_PHI),
+        fromLatLon(             0,                 -ATAN_PHI),
+        fromLatLon(             0,        Math.PI - ATAN_PHI),
+        fromLatLon(             0,     -(Math.PI - ATAN_PHI)),
+        fromLatLon(     ATAN_PHI2,                          0, INNER_RADIUS),
+        fromLatLon(     ATAN_PHI2,                    Math.PI, INNER_RADIUS),
+        fromLatLon(      CUBE_LAT,                  Math.PI/4, INNER_RADIUS),
+        fromLatLon(      CUBE_LAT,                3*Math.PI/4, INNER_RADIUS),
+        fromLatLon( ATAN_INV_PHI2,                  Math.PI/2, INNER_RADIUS),
+        fromLatLon(    -ATAN_PHI2,                          0, INNER_RADIUS),
+        fromLatLon(    -ATAN_PHI2,                    Math.PI, INNER_RADIUS),
+        fromLatLon(     -CUBE_LAT,                  Math.PI/4, INNER_RADIUS),
+        fromLatLon(     -CUBE_LAT,                3*Math.PI/4, INNER_RADIUS),
+        fromLatLon(-ATAN_INV_PHI2,                  Math.PI/2, INNER_RADIUS),
+        fromLatLon(      CUBE_LAT,                 -Math.PI/4, INNER_RADIUS),
+        fromLatLon(      CUBE_LAT,               -3*Math.PI/4, INNER_RADIUS),
+        fromLatLon( ATAN_INV_PHI2,                 -Math.PI/2, INNER_RADIUS),
+        fromLatLon(     -CUBE_LAT,                 -Math.PI/4, INNER_RADIUS),
+        fromLatLon(     -CUBE_LAT,               -3*Math.PI/4, INNER_RADIUS),
+        fromLatLon(-ATAN_INV_PHI2,                 -Math.PI/2, INNER_RADIUS),
+        fromLatLon(             0,              ATAN_INV_PHI2, INNER_RADIUS),
+        fromLatLon(             0,             -ATAN_INV_PHI2, INNER_RADIUS),
+        fromLatLon(             0,    Math.PI - ATAN_INV_PHI2, INNER_RADIUS),
+        fromLatLon(             0, -(Math.PI - ATAN_INV_PHI2), INNER_RADIUS)
+    }, true).build();
+
+    private static final int[][] FACES = {
+        {13,  2, 12,  0, 13},
+        { 0, 12,  4, 14,  0},
+        {15,  5, 13,  0, 15},
+        { 0, 14,  8, 16,  0},
+        {16, 10, 15,  0, 16},
+        { 1, 17,  3, 18,  1},
+        {19,  6, 17,  1, 19},
+        { 1, 18,  7, 20,  1},
+        {21,  8, 19,  1, 21},
+        { 1, 20, 10, 21,  1},
+        {22,  4, 12,  2, 22},
+        { 2, 13,  5, 23,  2},
+        {24,  9, 22,  2, 24},
+        { 2, 23, 11, 24,  2},
+        { 3, 17,  6, 25,  3},
+        {26,  7, 18,  3, 26},
+        { 3, 25,  9, 27,  3},
+        {27, 11, 26,  3, 27},
+        {29,  6, 28,  4, 29},
+        {28,  8, 14,  4, 28},
+        { 4, 22,  9, 29,  4},
+        { 5, 30,  7, 31,  5},
+        { 5, 15, 10, 30,  5},
+        {31, 11, 23,  5, 31},
+        { 6, 19,  8, 28,  6},
+        {29,  9, 25,  6, 29},
+        {30, 10, 20,  7, 30},
+        { 7, 26, 11, 31,  7},
+        {21, 10, 16,  8, 21},
+        { 9, 24, 11, 27,  9}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Tetrahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Tetrahedron.java
index 7c978a66ca..1e3d7d93c1 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Tetrahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/Tetrahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/Tetrahedron.html
@@ -23,4 +29,34 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class Tetrahedron extends AbstractPolyhedron{
 
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon( CUBE_LAT,    Math.PI/4),
+        fromLatLon(-CUBE_LAT,   -Math.PI/4),
+        fromLatLon(-CUBE_LAT,  3*Math.PI/4),
+        fromLatLon( CUBE_LAT, -3*Math.PI/4)
+    }, true).build();
+
+    private static final int[][] FACES = {
+        {2, 0, 1, 2},
+        {1, 0, 3, 1},
+        {3, 0, 2, 3},
+        {2, 1, 3, 2}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/TruncatedIcosahedron.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/TruncatedIcosahedron.java
index aebe4cfe7a..0d5aa46141 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/TruncatedIcosahedron.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/polyhedron/TruncatedIcosahedron.java
@@ -16,6 +16,12 @@
  */
 package org.apache.sis.geometries.polyhedron;
 
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.ArrayFactoryJava;
+import org.apache.sis.geometries.math.DataType;
+import org.apache.sis.geometries.math.ReadOnly;
+
 /**
  *
  * @see https://mathworld.wolfram.com/TruncatedIcosahedron.html
@@ -23,4 +29,148 @@ package org.apache.sis.geometries.polyhedron;
  */
 public final class TruncatedIcosahedron extends AbstractPolyhedron{
 
+    /**
+     * atan(1/(3*phi)) : one of the two latitude values of the vertices
+     * shared between a pentagon and two hexagons.
+     */
+    private static final double ATAN_INV_3PHI = Math.atan(1 / (3*PHI));
+
+    /**
+     * Remaining distinct latitude/longitude magnitudes appearing in the
+     * vertices below. Unlike the other solids, a truncated icosahedron
+     * vertex is a 1/3-interpolation between two {@link Icosahedron}
+     * vertices, normalized back onto the unit sphere ; this does not reduce
+     * to a simple expression of PI or phi, so these are plain precise
+     * radian constants rather than derived formulas.
+     */
+    private static final double LAT_1 = 1.3676273807451667;
+    private static final double LAT_2 = 1.0250282040502057;
+    private static final double LAT_3 = 0.81835937075291465;
+    private static final double LAT_4 = 0.71147906437076591;
+    private static final double LAT_5 = 0.33257431379403996;
+    private static final double LAT_6 = 0.41539154898382641;
+    private static final double LON_1 = 0.89058134991397542;
+    private static final double LON_2 = 0.29970859976855624;
+    private static final double LON_3 = 1.3011353287662717;
+    private static final double LON_4 = 1.1296873864711223;
+
+    /**
+     * Vertices are obtained by truncating each {@link Icosahedron} edge at
+     * its 1/3 and 2/3 points (the uniform truncation ratio that keeps every
+     * new edge, pentagon and hexagon, the same length), then normalized.
+     */
+    private static final Array VERTICES = ArrayFactoryJava.INSTANCE.builder()
+        .dataType(DataType.DOUBLE).values(new ReadOnly.Vector<?>[]{
+        fromLatLon(         LAT_1,                    Math.PI/2),
+        fromLatLon(         LAT_1,                   -Math.PI/2),
+        fromLatLon(         LAT_2,                        LON_1),
+        fromLatLon(         LAT_3,                        LON_2),
+        fromLatLon(         LAT_2,              Math.PI - LON_1),
+        fromLatLon(         LAT_3,              Math.PI - LON_2),
+        fromLatLon(         LAT_4,                        LON_3),
+        fromLatLon(         LAT_5,                        LON_4),
+        fromLatLon(         LAT_4,              Math.PI - LON_3),
+        fromLatLon(         LAT_5,              Math.PI - LON_4),
+        fromLatLon(        -LAT_1,                    Math.PI/2),
+        fromLatLon(        -LAT_1,                   -Math.PI/2),
+        fromLatLon(        -LAT_2,                        LON_1),
+        fromLatLon(        -LAT_3,                        LON_2),
+        fromLatLon(        -LAT_2,              Math.PI - LON_1),
+        fromLatLon(        -LAT_3,              Math.PI - LON_2),
+        fromLatLon(        -LAT_4,                        LON_3),
+        fromLatLon(        -LAT_5,                        LON_4),
+        fromLatLon(        -LAT_4,              Math.PI - LON_3),
+        fromLatLon(        -LAT_5,              Math.PI - LON_4),
+        fromLatLon(         LAT_2,                       -LON_1),
+        fromLatLon(         LAT_3,                       -LON_2),
+        fromLatLon(         LAT_2,           -(Math.PI - LON_1)),
+        fromLatLon(         LAT_3,           -(Math.PI - LON_2)),
+        fromLatLon(         LAT_4,                       -LON_3),
+        fromLatLon(         LAT_5,                       -LON_4),
+        fromLatLon(         LAT_4,           -(Math.PI - LON_3)),
+        fromLatLon(         LAT_5,           -(Math.PI - LON_4)),
+        fromLatLon(        -LAT_2,                       -LON_1),
+        fromLatLon(        -LAT_3,                       -LON_2),
+        fromLatLon(        -LAT_2,           -(Math.PI - LON_1)),
+        fromLatLon(        -LAT_3,           -(Math.PI - LON_2)),
+        fromLatLon(        -LAT_4,                       -LON_3),
+        fromLatLon(        -LAT_5,                       -LON_4),
+        fromLatLon(        -LAT_4,           -(Math.PI - LON_3)),
+        fromLatLon(        -LAT_5,           -(Math.PI - LON_4)),
+        fromLatLon( ATAN_INV_3PHI,                            0),
+        fromLatLon(-ATAN_INV_3PHI,                            0),
+        fromLatLon(         LAT_6,                ATAN_INV_PHI2),
+        fromLatLon( ATAN_INV_3PHI,              2*ATAN_INV_PHI2),
+        fromLatLon(         LAT_6,               -ATAN_INV_PHI2),
+        fromLatLon( ATAN_INV_3PHI,             -2*ATAN_INV_PHI2),
+        fromLatLon( ATAN_INV_3PHI,                      Math.PI),
+        fromLatLon(-ATAN_INV_3PHI,                      Math.PI),
+        fromLatLon(         LAT_6,      Math.PI - ATAN_INV_PHI2),
+        fromLatLon( ATAN_INV_3PHI,    Math.PI - 2*ATAN_INV_PHI2),
+        fromLatLon(         LAT_6,   -(Math.PI - ATAN_INV_PHI2)),
+        fromLatLon( ATAN_INV_3PHI, -(Math.PI - 2*ATAN_INV_PHI2)),
+        fromLatLon(        -LAT_6,                ATAN_INV_PHI2),
+        fromLatLon(-ATAN_INV_3PHI,              2*ATAN_INV_PHI2),
+        fromLatLon(        -LAT_6,               -ATAN_INV_PHI2),
+        fromLatLon(-ATAN_INV_3PHI,             -2*ATAN_INV_PHI2),
+        fromLatLon(        -LAT_6,      Math.PI - ATAN_INV_PHI2),
+        fromLatLon(-ATAN_INV_3PHI,    Math.PI - 2*ATAN_INV_PHI2),
+        fromLatLon(        -LAT_6,   -(Math.PI - ATAN_INV_PHI2)),
+        fromLatLon(-ATAN_INV_3PHI, -(Math.PI - 2*ATAN_INV_PHI2)),
+        fromLatLon(             0,                       LAT_1),
+        fromLatLon(             0,             Math.PI - LAT_1),
+        fromLatLon(             0,                      -LAT_1),
+        fromLatLon(             0,           -(Math.PI - LAT_1))
+    }, true).build();
+
+    private static final int[][] FACES = {
+        { 8,  4,  0,  2,  6,  8},
+        {16, 12, 10, 14, 18, 16},
+        {24, 20,  1, 22, 26, 24},
+        {34, 30, 11, 28, 32, 34},
+        {36, 38,  3, 21, 40, 36},
+        {46, 23,  5, 44, 42, 46},
+        {50, 29, 13, 48, 37, 50},
+        {43, 52, 15, 31, 54, 43},
+        {17, 56,  7, 39, 49, 17},
+        {51, 41, 25, 58, 33, 51},
+        {53, 45,  9, 57, 19, 53},
+        {35, 59, 27, 47, 55, 35},
+        { 3,  2,  0,  1, 20, 21,  3},
+        { 1,  0,  4,  5, 23, 22,  1},
+        { 7,  6,  2,  3, 38, 39,  7},
+        { 5,  4,  8,  9, 45, 44,  5},
+        { 9,  8,  6,  7, 56, 57,  9},
+        {11, 10, 12, 13, 29, 28, 11},
+        {15, 14, 10, 11, 30, 31, 15},
+        {13, 12, 16, 17, 49, 48, 13},
+        {19, 18, 14, 15, 52, 53, 19},
+        {17, 16, 18, 19, 57, 56, 17},
+        {21, 20, 24, 25, 41, 40, 21},
+        {27, 26, 22, 23, 46, 47, 27},
+        {25, 24, 26, 27, 59, 58, 25},
+        {33, 32, 28, 29, 50, 51, 33},
+        {31, 30, 34, 35, 55, 54, 31},
+        {35, 34, 32, 33, 58, 59, 35},
+        {39, 38, 36, 37, 48, 49, 39},
+        {37, 36, 40, 41, 51, 50, 37},
+        {43, 42, 44, 45, 53, 52, 43},
+        {47, 46, 42, 43, 54, 55, 47}
+    };
+
+    @Override
+    public int getFaceCount() {
+        return FACES.length;
+    }
+
+    @Override
+    public Polygon getFace(int index) {
+        return toPolygon(VERTICES, FACES[index]);
+    }
+
+    @Override
+    public int getFace(ReadOnly.Vector<?> unitVector) {
+        return nearestFace(VERTICES, FACES, unitVector);
+    }
+
 }

Reply via email to