001/**
002 * J3DTriangleList -- A Java3D node that represents a TriangleList in a J3D scene graph.
003 *
004 * Copyright (C) 2015-2023, 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 Lesser 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 geomss.app.GeomSSCanvas3D;
022import geomss.geom.GeomTriangle;
023import geomss.geom.TriangleList;
024import static java.util.Objects.requireNonNull;
025import org.jogamp.java3d.*;
026import org.jogamp.vecmath.*;
027import javolution.context.StackContext;
028
029/**
030 * A Java 3D node that represents a TriangleList in a Java 3D scene graph.
031 *
032 * <p> Modified by: Joseph A. Huwaldt </p>
033 *
034 * @author Joseph A. Huwaldt, Date: April 13, 2009
035 * @version June 4, 2023
036 */
037public class J3DTriangleList extends J3DGeomGroup<TriangleList> {
038
039    //  The switch for main or mirrored geometry.
040    private Switch _symmSG;
041
042    //  Store the Shape3D so it could potentially be used again in the future.
043    private TriangleListShape3D _oldSolidShape;
044    private TriangleListShape3D _oldWireframeShape;
045    private Shape3D _oldPointShape;
046
047    /**
048     * Construct a J3DTriangleList using the specified TriangleList as a reference.
049     *
050     * @param canvas   The canvas that the geometry is being rendered into.
051     * @param geometry The GeomSS geometry to be turned into a Java3D node.
052     */
053    public J3DTriangleList(GeomSSCanvas3D canvas, TriangleList geometry) {
054        super(requireNonNull(canvas), requireNonNull(geometry));
055    }
056
057    /**
058     * Set the display of a mirrored copy of this geometry. This is called from
059     * "setDisplayed()" to turn on and off the display of mirrored geometry without
060     * changing the "mirrored state" of the object. This call does not affect the output
061     * of "isMirrored()".
062     *
063     * @param mirrored Flag indicating if the mirrored geometry should be displayed or
064     *                 not.
065     */
066    @Override
067    protected void internalSetMirrored(boolean mirrored) {
068        if (mirrored)
069            _symmSG.setWhichChild(Switch.CHILD_ALL);
070        else
071            _symmSG.setWhichChild(0);
072    }
073
074    /**
075     * Create a new Java 3D <code>Group</code> that contains the geometry contained in
076     * this object. This method is called from <code>createSceneGraph</code>. Sub-classes
077     * must over-ride this method to provide geometry specific implementations.
078     *
079     * @return New Group that contains the geometry in this object.
080     * @see #createSceneGraph
081     */
082    @Override
083    @SuppressWarnings("null")
084    protected Group createGeometry() {
085        J3DRenderingPrefs drawPrefs = getRenderingPrefs();
086        TriangleList<? extends GeomTriangle> triLst = this.getGeomElement();
087        TriangleListShape3D solid = null;
088        TriangleListShape3D wireFrame = null;
089        Shape3D pointShape = null;
090
091        //  Try to recycle a previous rendering of this geometry element if at all possible.
092        J3DTriangleList oldGroup = (J3DTriangleList)getOldJ3DGeomGroup();
093        if (oldGroup != null) {
094            J3DRenderingPrefs oldPrefs = oldGroup.getRenderingPrefs();
095            if (oldPrefs.getDrawTolerance().equals(drawPrefs.getDrawTolerance())) {
096                //System.out.println("Re-using solidShape & wireframeShape");
097                //  Draw tolerance is unchanged, so re-use the existing Shape3D.
098                solid = (TriangleListShape3D)oldGroup._oldSolidShape.cloneNode(true);
099                wireFrame = (TriangleListShape3D)oldGroup._oldWireframeShape.cloneNode(true);
100                pointShape = (Shape3D)oldGroup._oldPointShape.cloneNode(true);
101
102                //  Has the point color changed?
103                if (!oldPrefs.getPointColor().equals(drawPrefs.getPointColor())) {
104                    //System.out.println("Changing point color");
105                    Color4f pointColor = drawPrefs.getPointColorJ3D();
106                    PointArray pointA = (PointArray)pointShape.getGeometry();
107                    int size = pointA.getVertexCount();
108                    for (int i = 0; i < size; ++i) {
109                        pointA.setColor(i, pointColor);
110                    }
111                }
112
113            }
114        }
115
116        //  If we couldn't recycle the solid & wireFrame shapes, then create new ones.
117        if (solid == null) {
118            StackContext.enter();
119            try {
120                solid = new TriangleListShape3D(triLst, triLst);
121            } finally {
122                StackContext.exit();
123            }
124
125            //  Wireframe is the same geometry, just differnet display attributes.
126            wireFrame = (TriangleListShape3D)solid.cloneNode(true);
127
128            //  Create a new PointArray geometry for the point shape.
129            GeometryArray geom = (GeometryArray)solid.getGeometry();
130            int numPnts = geom.getValidVertexCount();
131            PointArray pointA = new PointArray(numPnts, PointArray.COORDINATES | PointArray.COLOR_4);
132            Color4f pointColor = drawPrefs.getPointColorJ3D();
133            Point3f aPnt = new Point3f();
134            for (int i = 0; i < numPnts; ++i) {
135                geom.getCoordinate(i, aPnt);
136                pointA.setCoordinate(i, aPnt);
137                pointA.setColor(i, pointColor);
138            }
139            pointShape = new GeomShape3D(triLst, pointA);
140
141        }
142        this._oldSolidShape = (TriangleListShape3D)solid.cloneNode(true);    //  Save off for potential re-use in the future.
143        this._oldWireframeShape = (TriangleListShape3D)wireFrame.cloneNode(true);
144        this._oldPointShape = (Shape3D)pointShape.cloneNode(true);
145
146        //      Define wireframe attributes.
147        PolygonAttributes wireFrameAttrib = new PolygonAttributes();
148        wireFrameAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE);
149        wireFrameAttrib.setCullFace(PolygonAttributes.CULL_NONE);
150        wireFrameAttrib.setPolygonOffset(-2);   //      Make wireframe appear "on top" of solid version.
151
152        //      Define wireframe material.
153        Material wireMaterial = new Material();
154        Color4f color = drawPrefs.getLineColorJ3D();
155        wireMaterial.setDiffuseColor(color.x, color.y, color.z);
156        wireMaterial.setLightingEnable(true);
157
158        //      Create a default solid version.
159        Appearance srfAppearance = drawPrefs.getSurfaceAppearance();
160        PolygonAttributes polyAttrib = new PolygonAttributes();
161        polyAttrib.setCullFace(PolygonAttributes.CULL_NONE);
162        polyAttrib.setBackFaceNormalFlip(true);         //      Flip surface normals for back faces.
163        srfAppearance.setPolygonAttributes(polyAttrib);
164        LineAttributes lineAttrib = new LineAttributes();
165        lineAttrib.setLineWidth(drawPrefs.getLineWidth());
166        srfAppearance.setLineAttributes(lineAttrib);
167        solid.setAppearance(srfAppearance);
168
169        //      Create a duplicate wireframe version.
170        Appearance wfApp = (Appearance)srfAppearance.cloneNodeComponent(false);
171        wfApp.setPolygonAttributes(wireFrameAttrib);
172        wfApp.setMaterial(wireMaterial);
173        wireFrame.setAppearance(wfApp);
174
175        //      Create a sold + wireframe version.
176        BranchGroup solidWireFrame = new BranchGroup();
177        solidWireFrame.addChild(solid.cloneTree());
178        solidWireFrame.addChild(wireFrame.cloneTree());
179
180        //      Create a switch group for the main geometry (unmirrored)
181        //      that contains each variation on the display properties of an array.
182        Switch renderTypeSG = new Switch(0);
183        renderTypeSG.setCapability(Switch.ALLOW_SWITCH_READ);
184        renderTypeSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
185
186        //      Create a 3D shape from the point array
187        Appearance appearance = new Appearance();
188        pointShape.setAppearance(appearance);
189        appearance.setPointAttributes(new PointAttributes(drawPrefs.getPointSize(), true));
190
191        //      Add the geometry to the main geometry switch.
192        renderTypeSG.addChild(solidWireFrame);
193        renderTypeSG.addChild(solid);
194        renderTypeSG.addChild(wireFrame);
195        renderTypeSG.addChild(pointShape);
196
197        //      Add the basic unmirrored geometry to the switch.
198        _symmSG = new Switch();
199        _symmSG.setCapability(Switch.ALLOW_SWITCH_READ);
200        _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
201        _symmSG.addChild(renderTypeSG);
202
203        //      Clone the basic geometry to make the mirrored geometry.
204        Switch mirrored = new Switch(0);
205        mirrored.setCapability(Switch.ALLOW_SWITCH_READ);
206        mirrored.setCapability(Switch.ALLOW_SWITCH_WRITE);
207
208        //      Clone the basic geometry and reverse normals to make the mirrored geometry.
209        TriangleListShape3D solidM = reverseNormals((TriangleListShape3D)solid.cloneTree(true));
210        BranchGroup solidWireFrameM = new BranchGroup();
211        solidWireFrameM.addChild(solidM.cloneTree());
212        solidWireFrameM.addChild(wireFrame.cloneTree());
213
214        //      Add the geometry to the mirrored geometry switch.
215        mirrored.addChild(solidWireFrameM);
216        mirrored.addChild(solidM);
217        mirrored.addChild(wireFrame.cloneTree());
218        mirrored.addChild(pointShape.cloneTree());
219
220        //      Create a mirror across the XZ plane of symmetry transform.
221        Transform3D symmT = new Transform3D();
222        symmT.setScale(new Vector3d(1, -1, 1));
223        TransformGroup symmTG = new TransformGroup(symmT);
224
225        //      Add the mirrored geometry to the symmetry transform group.
226        symmTG.addChild(mirrored);
227
228        //      Add the mirrored geometry to the switch.
229        _symmSG.addChild(symmTG);
230
231        //      By default, display only the main geometry (not the mirrored).
232        _symmSG.setWhichChild(0);
233
234        return _symmSG;
235    }
236
237    /**
238     * Reverse the surface normals in the specified TriangleListShape3D object.
239     */
240    private static TriangleListShape3D reverseNormals(TriangleListShape3D shape) {
241
242        //      Reverse the surface normals in the geometry for the mirrored shape.
243        GeometryArray array = (GeometryArray)shape.getGeometry();
244        int numVerts = array.getVertexCount();
245        Vector3f normal = new Vector3f();
246        for (int i = 0; i < numVerts; ++i) {
247            array.getNormal(i, normal);
248            normal.negate();
249            array.setNormal(i, normal);
250        }
251
252        return shape;
253    }
254
255    /**
256     * Set the render type used for this group.
257     *
258     * @param type Value indicating the way that PointArray objects should be rendered.
259     */
260    @Override
261    public void setRenderType(RenderType type) {
262        super.setRenderType(requireNonNull(type));
263
264        //      Get the main geometry's render switch.
265        Switch mainRenderSG = (Switch)_symmSG.getChild(0);
266
267        //      Get the mirrored geometry's render switch.
268        Switch mirrRenderSG = (Switch)((Group)_symmSG.getChild(1)).getChild(0);
269
270        //      Set switches based on the render type code.
271        switch (type) {
272            case SOLID_PLUS_WIREFRAME:
273                mainRenderSG.setWhichChild(0);
274                mirrRenderSG.setWhichChild(0);
275                break;
276
277            case SOLID:
278                mainRenderSG.setWhichChild(1);
279                mirrRenderSG.setWhichChild(1);
280                break;
281
282            case WIREFRAME:
283            case STRINGS:
284                mainRenderSG.setWhichChild(2);
285                mirrRenderSG.setWhichChild(2);
286                break;
287
288            case POINTS:
289                mainRenderSG.setWhichChild(3);
290                mirrRenderSG.setWhichChild(3);
291                break;
292
293            default:
294                break;
295
296        }
297
298        //      Hide symmetric array geometry when POINTS or STRINGS are shown.
299        internalSetMirrored(isMirrored());
300    }
301
302    /**
303     * Creates a new instance of the node. This routine is called by
304     * <code>cloneTree</code> to duplicate the current node.
305     *
306     * @param forceDuplicate when set to <code>true</code>, causes the
307     *                       <code>duplicateOnCloneTree</code> flag to be ignored. When
308     *                       <code>false</code>, the value of each node's
309     *                       <code>duplicateOnCloneTree</code> variable determines whether
310     *                       NodeComponent data is duplicated or copied.
311     * @return A new instance of this Java3D node.
312     */
313    @Override
314    public Node cloneNode(boolean forceDuplicate) {
315        J3DTriangleList node = new J3DTriangleList(this.getCanvas3D(), this.getGeomElement());
316        node.duplicateNode(this, forceDuplicate);
317        return node;
318    }
319}