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