001/**
002 * J3DSurface -- A Java3D node that represents a Surface in a J3D scene graph.
003 * 
004 * Copyright (C) 2010-2023, Joseph A. Huwaldt
005 * All rights reserved.
006 * 
007 * This library is free software; you can redistribute it and/or modify it snder the terms
008 * of the GNU Lesser General Public License as published by the Free Software Fosndation;
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 Fosndation, 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.*;
024import java.util.List;
025import static java.util.Objects.requireNonNull;
026import javax.measure.quantity.Dimensionless;
027import org.jogamp.java3d.*;
028import org.jogamp.vecmath.*;
029import javolution.context.StackContext;
030import javolution.util.FastTable;
031
032/**
033 * A Java 3D node that represents a Surface in a Java 3D scene graph.
034 *
035 * <p> Modified by: Joseph A. Huwaldt </p>
036 *
037 * @author Joseph A. Huwaldt, Date: Jsne 16, 2010
038 * @version June 4, 2023
039 */
040public class J3DSurface extends J3DGeomGroup<Surface> {
041
042    //  Debug flag.
043    private static final boolean DEBUG = false;
044
045    //  The switch for main or mirrored geometry.
046    private Switch _symmSG;
047
048    //  Store the Shape3D so it could potentially be used again in the future.
049    private PointArrayShape3D _oldShape3D;
050
051    /**
052     * Construct a J3DSurface using the specified PointArray as a reference.
053     *
054     * @param canvas   The canvas that the geometry is being rendered into.
055     * @param geometry The GeomSS geometry to be turned into a Java3D node.
056     */
057    public J3DSurface(GeomSSCanvas3D canvas, Surface geometry) {
058        super(requireNonNull(canvas), requireNonNull(geometry));
059    }
060
061    /**
062     * Set the display of a mirrored copy of this geometry. This is called from
063     * "setDisplayed()" to turn on and off the display of mirrored geometry without
064     * changing the "mirrored state" of the object. This call does not affect the output
065     * of "isMirrored()".
066     */
067    @Override
068    protected void internalSetMirrored(boolean mirrored) {
069        if (mirrored)
070            _symmSG.setWhichChild(Switch.CHILD_ALL);
071        else
072            _symmSG.setWhichChild(0);
073    }
074
075    /**
076     * Create a new Java 3D <code>Group</code> that contains the geometry contained in
077     * this object. This method is called from <code>createSceneGraph</code>. Sub-classes
078     * must over-ride this method to provide geometry specific implementations.
079     *
080     * @return New Java 3D Group that contains the geometry in this object.
081     * @see #createSceneGraph
082     */
083    @Override
084    @SuppressWarnings("null")
085    protected Group createGeometry() {
086
087        J3DRenderingPrefs drawPrefs = getRenderingPrefs();
088        PointArrayShape3D surfaceShape = null;
089
090        //  Try to recycle a previous rendering of this geometry element if at all possible.
091        J3DSurface oldGroup = (J3DSurface)getOldJ3DGeomGroup();
092        if (oldGroup != null) {
093            J3DRenderingPrefs oldPrefs = oldGroup.getRenderingPrefs();
094            if (oldPrefs.getDrawTolerance().equals(drawPrefs.getDrawTolerance())) {
095                //System.out.println("Re-using surfaceShape");
096                //  Draw tolerance is unchanged, so re-use the existing Shape3D.
097                surfaceShape = (PointArrayShape3D)oldGroup._oldShape3D.cloneNode(true);
098
099                //  Has the surface appearance changed?
100                if (!oldPrefs.getSurfaceAppearance().equals(drawPrefs.getSurfaceAppearance())) {
101                    //System.out.println("Changing surface appearance");
102                    Appearance srfAppearance = drawPrefs.getSurfaceAppearance();
103                    PolygonAttributes polyAttrib = new PolygonAttributes();
104                    polyAttrib.setCullFace(PolygonAttributes.CULL_NONE);
105                    polyAttrib.setBackFaceNormalFlip(true);             //      Flip surface normals for back faces.
106                    srfAppearance.setPolygonAttributes(polyAttrib);
107                    surfaceShape.setAppearance(srfAppearance);
108                }
109            }
110        }
111
112        if (surfaceShape == null) {
113            StackContext.enter();
114            try {
115                //      Subdivide the surface into an array of points.
116                Surface thisSurface = this.getGeomElement();
117                PointArray<SubrangePoint> points = thisSurface.gridToTolerance(drawPrefs.getDrawTolerance());
118
119                //      Now find the corresponding array of surface normals to the gridded points.
120                List<List<GeomVector<Dimensionless>>> normals = FastTable.newInstance();
121                for (PointString<SubrangePoint> str : points) {
122                    FastTable<GeomVector<Dimensionless>> col = FastTable.newInstance();
123                    for (SubrangePoint pnt : str) {
124                        GeomPoint st = pnt.getParPosition();            //      (s,t)
125                        GeomVector<Dimensionless> normal = thisSurface.getNormal(st);
126                        col.add(normal);
127                    }
128                    normals.add(col);
129                }
130
131                //      Create a solid surface.
132                Appearance srfAppearance = drawPrefs.getSurfaceAppearance();
133                PolygonAttributes polyAttrib = new PolygonAttributes();
134                polyAttrib.setCullFace(PolygonAttributes.CULL_NONE);
135                polyAttrib.setBackFaceNormalFlip(true);         //      Flip surface normals for back faces.
136                srfAppearance.setPolygonAttributes(polyAttrib);
137                surfaceShape = new PointArrayShape3D(thisSurface, points, normals, srfAppearance, !DEBUG);
138
139            } finally {
140                StackContext.exit();
141            }
142        }
143        _oldShape3D = (PointArrayShape3D)surfaceShape.cloneNode(true);   //  Save off for potential re-use in the future.
144
145        if (DEBUG) {
146            //  Render the surface in wireframe so we can see how it
147            //  has been subdivided.
148
149            //  Define wireframe attributes.
150            PolygonAttributes wireFrameAttrib = new PolygonAttributes();
151            wireFrameAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE);
152            wireFrameAttrib.setCullFace(PolygonAttributes.CULL_NONE);
153            wireFrameAttrib.setBackFaceNormalFlip(true);        //      Flip surface normals for back faces.
154
155            //  Define wireframe material.
156            Material wireMaterial = new Material();
157            Color4f color = drawPrefs.getLineColorJ3D();
158            wireMaterial.setDiffuseColor(color.getX(), color.getY(), color.getZ());
159            wireMaterial.setLightingEnable(true);
160
161            Appearance wfApp = surfaceShape.getAppearance();
162            wfApp.setPolygonAttributes(wireFrameAttrib);
163            wfApp.setMaterial(wireMaterial);
164        }
165
166        //      Add the basic snmirrored geometry to the symmetry switch.
167        _symmSG = new Switch();
168        _symmSG.setCapability(Switch.ALLOW_SWITCH_READ);
169        _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
170        _symmSG.addChild(surfaceShape);
171
172        //      Clone the basic geometry and reverse normals to make the mirrored geometry.
173        PointArrayShape3D mirrored = reverseNormals((PointArrayShape3D)surfaceShape.cloneTree(true));
174
175        //      Create a mirror across the XZ plane of symmetry transform.
176        Transform3D symmT = new Transform3D();
177        symmT.setScale(new Vector3d(1, -1, 1));
178        TransformGroup symmTG = new TransformGroup(symmT);
179
180        //      Add the mirrored geometry to the symmetry transform group.
181        symmTG.addChild(mirrored);
182
183        //      Add the mirrored geometry to the switch.
184        _symmSG.addChild(symmTG);
185
186        //      By default, display only the main geometry (not the mirrored).
187        _symmSG.setWhichChild(0);
188
189        return _symmSG;
190    }
191
192    /**
193     * Reverse the surface normals in the specified PointArrayShape3D object.
194     */
195    private static PointArrayShape3D reverseNormals(PointArrayShape3D shape) {
196
197        //      Reverse the surface normals in the geometry for the mirrored shape.
198        GeometryArray array = (GeometryArray)shape.getGeometry();
199        int numVerts = array.getVertexCount();
200        Vector3f normal = new Vector3f();
201        for (int i = 0; i < numVerts; ++i) {
202            array.getNormal(i, normal);
203            normal.negate();
204            array.setNormal(i, normal);
205        }
206
207        return shape;
208    }
209
210    /**
211     * Creates a new instance of the node. This routine is called by
212     * <code>cloneTree</code> to duplicate the current node.
213     *
214     * @param forceDuplicate when set to <code>true</code>, causes the
215     *                       <code>duplicateOnCloneTree</code> flag to be ignored. When
216     *                       <code>false</code>, the value of each node's
217     *                       <code>duplicateOnCloneTree</code> variable determines whether
218     *                       NodeComponent data is duplicated or copied.
219     * @return A new instance of this Java3D node.
220     */
221    @Override
222    public Node cloneNode(boolean forceDuplicate) {
223        J3DSurface node = new J3DSurface(this.getCanvas3D(), this.getGeomElement());
224        node.duplicateNode(this, forceDuplicate);
225        return node;
226    }
227}