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