001/**
002 * PointArrayShape3D -- A Java 3D Shape3D representation of a PointArray.
003 *
004 * Copyright (C) 2009-2025, by Joseph A. Huwaldt
005 * All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or modify it under the terms
008 * of the GNU Lesser General Public License as published by the Free Software Foundation;
009 * either version 2.1 of the License, or (at your option) any later version.
010 *
011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along with
016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
018 */
019package geomss.j3d;
020
021import org.jogamp.java3d.utils.geometry.GeometryInfo;
022import org.jogamp.java3d.utils.geometry.NormalGenerator;
023import org.jogamp.java3d.utils.geometry.Stripifier;
024import geomss.geom.*;
025import geomss.geom.PointArray;
026import java.util.List;
027import static java.util.Objects.requireNonNull;
028import java.util.ResourceBundle;
029import javax.measure.quantity.Dimensionless;
030import javax.measure.unit.SI;
031import org.jogamp.java3d.*;
032import org.jogamp.vecmath.*;
033
034/**
035 * A Shape3D object based on a {@link geomss.geom.PointArray PointArray}. The default
036 * appearance of the geometry is solid filled grey with lighting enabled.
037 *
038 * <p> Modified by: Joseph A. Huwaldt </p>
039 *
040 * @author Joseph A. Huwaldt, Date: April 5, 2009
041 * @version February 17, 2025
042 */
043public class PointArrayShape3D extends GeomShape3D {
044
045    /**
046     * The resource bundle for this class.
047     */
048    private static final ResourceBundle RB = J3DGeomGroup.RB;
049
050    /**
051     * Construct a PointArrayShape3D using the specified GeomElement as a reference.
052     *
053     * @param parent The GeomSS geometry element that this Shape3D is associated with.
054     */
055    private PointArrayShape3D(GeomElement parent) {
056        super(parent);
057    }
058
059    /**
060     * Construct a PointArrayShape3D object from the specified {@link PointArray}.
061     *
062     * @param parent The GeomSS geometry element that this Shape3D is associated with.
063     * @param array  The PointArray to be converted into a J3D Shape3D object.
064     * @throws IllegalArgumentException if array.size() &lt; 2
065     */
066    public PointArrayShape3D(GeomElement parent, PointArray<? extends GeomPoint> array) {
067        super(parent);
068
069        //      Check inputs.
070        if (array.size() < 2)
071            throw new IllegalArgumentException(RB.getString("notEnoughStrings"));
072        int numPoints = array.get(0).size();
073        for (PointString string : array) {
074            if (string.size() < 2)
075                throw new IllegalArgumentException(RB.getString("notEnoughPoints"));
076            if (string.size() != numPoints)
077                throw new IllegalArgumentException(RB.getString("inconsistantStrings"));
078        }
079
080        setGeometry(createGeometry(array, null, false));
081    }
082
083    /**
084     * Construct a PointArrayShape3D object from the specified {@link PointArray}.
085     *
086     * @param parent     The GeomSS geometry element that this Shape3D is associated with.
087     * @param array      The PointArray to be converted into a J3D Shape3D object.
088     * @param appearance The Appearance to use when rendering the point array.
089     * @throws IllegalArgumentException if array.size() &lt; 2
090     */
091    public PointArrayShape3D(GeomElement parent, PointArray<? extends GeomPoint> array, Appearance appearance) {
092        this(parent, array);
093        setAppearance(requireNonNull(appearance));
094    }
095
096    /**
097     * Construct a PointArrayShape3D object from the specified {@link PointArray}.
098     *
099     * @param parent     The GeomSS geometry element that this Shape3D is associated with.
100     * @param array      The PointArray to be converted into a J3D Shape3D object.
101     * @param appearance The Appearance to use when rendering the point array.
102     * @param stripify   Flag indicating if the array of quadrilateral panels should be
103     *                   converted into triangle strips for rendering or not.
104     * @throws IllegalArgumentException if array.size() &lt; 2
105     */
106    public PointArrayShape3D(GeomElement parent, PointArray<? extends GeomPoint> array,
107            Appearance appearance, boolean stripify) {
108        super(parent);
109
110        //      Check inputs.
111        if (array.size() < 2)
112            throw new IllegalArgumentException(RB.getString("notEnoughStrings"));
113        int numPoints = array.get(0).size();
114        for (PointString string : array) {
115            if (string.size() < 2)
116                throw new IllegalArgumentException(RB.getString("notEnoughPoints"));
117            if (string.size() != numPoints)
118                throw new IllegalArgumentException(RB.getString("inconsistantStrings"));
119        }
120
121        setGeometry(createGeometry(array, null, stripify));
122        setAppearance(requireNonNull(appearance));
123    }
124
125    /**
126     * Construct a PointArrayShape3D object from the specified {@link PointArray}.
127     *
128     * @param parent     The GeomSS geometry element that this Shape3D is associated with.
129     * @param array      The PointArray to be converted into a J3D Shape3D object.
130     * @param normals    A list of lists of surface normals, one for each point in
131     *                   "array". If <code>null</code> is provided, the surface normals
132     *                   will be automatically estimated from the vertex data.
133     * @param appearance The Appearance to use when rendering the point array.
134     * @param stripify   Flag indicating if the array of quadrilateral panels should be
135     *                   converted into triangle strips for rendering or not.
136     * @throws IllegalArgumentException if array.size() &lt; 2
137     */
138    public PointArrayShape3D(GeomElement parent, PointArray<? extends GeomPoint> array,
139            List<List<GeomVector<Dimensionless>>> normals, Appearance appearance, boolean stripify) {
140        super(parent);
141
142        //      Check inputs.
143        if (array.size() < 2)
144            throw new IllegalArgumentException(RB.getString("notEnoughStrings"));
145        int numPoints = array.get(0).size();
146        for (PointString string : array) {
147            if (string.size() < 2)
148                throw new IllegalArgumentException(RB.getString("notEnoughPoints"));
149            if (string.size() != numPoints)
150                throw new IllegalArgumentException(RB.getString("inconsistantStrings"));
151        }
152
153        setGeometry(createGeometry(array, requireNonNull(normals), stripify));
154        setAppearance(requireNonNull(appearance));
155    }
156
157    /**
158     * Used to create a new instance of the node.
159     *
160     * @param forceDuplicate when set to <code>true</code>, causes the
161     *                       <code>duplicateOnCloneTree</code> flag to be ignored. When
162     *                       <code>false</code>, the value of each node's
163     *                       <code>duplicateOnCloneTree</code> variable determines whether
164     *                       NodeComponent data is duplicated or copied.
165     * @return A new instance of this Java3D node.
166     */
167    @Override
168    public Node cloneNode(boolean forceDuplicate) {
169        PointArrayShape3D usc = new PointArrayShape3D(this.getGeometryElement());
170        usc.duplicateNode(this, forceDuplicate);
171        return usc;
172    }
173
174    /**
175     * Convert the PointArray, and an optional array of vertex normals, into a J3D
176     * Geometry object here.
177     */
178    private Geometry createGeometry(PointArray<? extends GeomPoint> array,
179            List<List<GeomVector<Dimensionless>>> normals, boolean stripify) {
180
181        int numCols = array.size();
182        int numPanelCols = numCols - 1;
183        int numRows = array.get(0).size();
184        int numPanelRows = numRows - 1;
185
186        // Convert PointArray objects into J3D quadralaterals.
187        int vertexCount = numRows * numCols;
188        int indexCount = numPanelRows * numPanelCols * 4;
189
190        // Load in the geometry into required 1D arrays.
191        Point3f[] coordinates = new Point3f[vertexCount];
192        Vector3f[] normalsArr = null;
193        if (normals != null)
194            normalsArr = new Vector3f[vertexCount];
195
196        int vertex = 0;
197        int dim = array.getPhyDimension();
198        for (int i = 0; i < numCols; ++i) {
199            PointString<?> string = array.get(i);
200
201            for (int j = 0; j < numRows; ++j) {
202                GeomPoint point = string.get(j);
203                //      Convert all geometry to meters.
204                double x = point.getValue(0, SI.METER);
205                double y = (dim > 1 ? point.getValue(1, SI.METER) : 0);
206                double z = (dim > 2 ? point.getValue(2, SI.METER) : 0);
207                coordinates[vertex] = new Point3f((float)x, (float)y, (float)z);
208
209                if (normals != null) {
210                    //  If normal vectors supplied, create an array of normals in Java3D format.
211                    GeomVector n = normals.get(i).get(j);
212                    x = n.getValue(0);
213                    y = (dim > 1 ? n.getValue(1) : 0);
214                    z = (dim > 2 ? n.getValue(2) : 0);
215                    normalsArr[vertex] = new Vector3f((float)x, (float)y, (float)z);
216                }
217
218                ++vertex;
219            }
220        }
221
222        // Set up the quad vertex indices (panel verticies).
223        int[] coordIndices = new int[indexCount];
224        int index = 0;
225        vertex = 0;
226        for (int i = 0; i < numPanelCols; ++i) {
227            for (int j = 0; j < numPanelRows; ++j) {
228                coordIndices[index++] = vertex;
229                coordIndices[index++] = vertex + 1;
230                coordIndices[index++] = vertex + 1 + numRows;
231                coordIndices[index++] = vertex + numRows;
232                ++vertex;
233            }
234            ++vertex;
235        }
236
237        // Create a GeomInfo object for our geometry.
238        GeometryInfo geomInfo = new GeometryInfo(GeometryInfo.QUAD_ARRAY);
239        geomInfo.setCoordinates(coordinates);
240        geomInfo.setCoordinateIndices(coordIndices);
241
242        if (normals != null) {
243            //  Use the supplied vertex normals.
244            geomInfo.setNormals(normalsArr);
245            geomInfo.setNormalIndices(coordIndices);
246
247        } else {
248            // Generate vertex normals automatically.
249            NormalGenerator ng = new NormalGenerator();
250            ng.generateNormals(geomInfo);
251        }
252
253        if (stripify) {
254            // Stripify the geometry for increased performance.
255            Stripifier st = new Stripifier();
256            st.stripify(geomInfo);
257        }
258
259        return geomInfo.getGeometryArray();
260    }
261}