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