001/**
002 * J3DPointString -- A Java3D node that represents a PointString 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.GeomPoint;
023import geomss.geom.PointString;
024import static java.util.Objects.requireNonNull;
025import javax.measure.unit.SI;
026import org.jogamp.java3d.*;
027import org.jogamp.vecmath.*;
028import javolution.context.StackContext;
029
030/**
031 * A Java 3D node that represents a PointString in a Java 3D scene graph.
032 *
033 * <p> Modified by: Joseph A. Huwaldt </p>
034 *
035 * @author Joseph A. Huwaldt, Date: April 13, 2009
036 * @version June 4, 2023
037 */
038public class J3DPointString extends J3DGeomGroup<PointString> {
039
040    //  The switch for main or mirrored geometry.
041    private Switch _symmSG;
042
043    //  Store the Shape3D so it could potentially be used again in the future.
044    private Shape3D _oldLineShape;
045    private Shape3D _oldPointShape;
046
047    /**
048     * Construct a J3DPointString using the specified PointString 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 J3DPointString(GeomSSCanvas3D canvas, PointString 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    @Override
064    protected void internalSetMirrored(boolean mirrored) {
065        if (mirrored)
066            _symmSG.setWhichChild(Switch.CHILD_ALL);
067        else
068            _symmSG.setWhichChild(0);
069    }
070
071    /**
072     * Create a new Java 3D <code>Group</code> that contains the geometry contained in
073     * this object. This method is called from <code>createSceneGraph</code>. Sub-classes
074     * must over-ride this method to provide geometry specific implementations.
075     *
076     * @return New Java 3D Group that contains the geometry in this object.
077     * @see #createSceneGraph
078     */
079    @Override
080    @SuppressWarnings("null")
081    protected Group createGeometry() {
082
083        J3DRenderingPrefs drawPrefs = getRenderingPrefs();
084        Shape3D lineShape = null;
085        Shape3D pointShape = null;
086
087        //  Try to recycle a previous rendering of this geometry element if at all possible.
088        J3DPointString oldGroup = (J3DPointString)getOldJ3DGeomGroup();
089        if (oldGroup != null) {
090            J3DRenderingPrefs oldPrefs = oldGroup.getRenderingPrefs();
091            if (oldPrefs.getDrawTolerance().equals(drawPrefs.getDrawTolerance())) {
092                //System.out.println("Re-using lineShape & pointShape");
093                //  Draw tolerance is unchanged, so re-use the existing Shape3D.
094                lineShape = (Shape3D)oldGroup._oldLineShape.cloneNode(true);
095                pointShape = (Shape3D)oldGroup._oldPointShape.cloneNode(true);
096
097                //  Has the line color changed?
098                if (!oldPrefs.getLineColor().equals(drawPrefs.getLineColor())) {
099                    //System.out.println("Changing line color");
100                    Color4f lineColor = drawPrefs.getLineColorJ3D();
101                    LineArray lineA = (LineArray)lineShape.getGeometry();
102                    int size = lineA.getVertexCount();
103                    for (int i = 0; i < size; ++i) {
104                        lineA.setColor(i, lineColor);
105                    }
106                }
107
108                //  Has the point color changed?
109                if (!oldPrefs.getPointColor().equals(drawPrefs.getPointColor())) {
110                    //System.out.println("Changing point color");
111                    Color4f pointColor = drawPrefs.getPointColorJ3D();
112                    PointArray pointA = (PointArray)pointShape.getGeometry();
113                    int size = pointA.getVertexCount();
114                    for (int i = 0; i < size; ++i) {
115                        pointA.setColor(i, pointColor);
116                    }
117                }
118
119            }
120        }
121
122        //  If we couldn't recycle the lineShape & pointShape, then create new ones.
123        if (lineShape == null) {
124            StackContext.enter();
125            try {
126                //      Create a line and a point array.
127                PointString<?> thisString = this.getGeomElement();
128                int size = thisString.size();
129                int nVerts = 2 * (size - 1);
130                LineArray lineA = new LineArray(nVerts, LineArray.COORDINATES | LineArray.COLOR_4);
131                PointArray pointA = new PointArray(thisString.size(), PointArray.COORDINATES | PointArray.COLOR_4);
132
133                //      Set the coordinates of the line.
134                Color4f pointColor = drawPrefs.getPointColorJ3D();
135                Color4f lineColor = drawPrefs.getLineColorJ3D();
136                Point3d oldPoint = null;
137                int lineIndex = 0;
138                for (int i = 0; i < size; ++i) {
139                    GeomPoint point = thisString.get(i);
140                    //  Convert all geometry to meters.
141                    int dims = point.getPhyDimension();
142                    double x = point.getValue(0, SI.METER);
143                    double y = (dims > 1 ? point.getValue(1, SI.METER) : 0);
144                    double z = (dims > 2 ? point.getValue(2, SI.METER) : 0);
145                    Point3d point3d = new Point3d(x, y, z);
146
147                    if (i != 0) {
148                        lineA.setCoordinate(lineIndex, oldPoint);               //      Store start of line segment.
149                        lineA.setColor(lineIndex++, lineColor);
150                        lineA.setCoordinate(lineIndex, point3d);                //      Store end of line segment.
151                        lineA.setColor(lineIndex++, lineColor);
152                    }
153                    pointA.setCoordinate(i, point3d);
154                    pointA.setColor(i, pointColor);
155
156                    oldPoint = point3d;
157                }
158
159                //      Create a 3D shape from the line array
160                lineShape = new GeomShape3D(thisString, lineA);
161                pointShape = new GeomShape3D(thisString, pointA);
162
163            } finally {
164                StackContext.exit();
165            }
166        }
167        _oldLineShape = (Shape3D)lineShape.cloneNode(true);   //  Save off for potential re-use in the future.
168        _oldPointShape = (Shape3D)pointShape.cloneNode(true);
169
170        //      Define the appearance of the line array.
171        Appearance lineAppearance = new Appearance();
172        LineAttributes lineAttrib = new LineAttributes();
173        lineAttrib.setLineWidth(drawPrefs.getLineWidth());
174        lineAppearance.setLineAttributes(lineAttrib);
175        lineShape.setAppearance(lineAppearance);
176
177        //      Create a 3D shape from the point array
178        Appearance appearance = new Appearance();
179        pointShape.setAppearance(appearance);
180        appearance.setPointAttributes(new PointAttributes(drawPrefs.getPointSize(), true));
181
182        //      Create a switch group for the main geometry (unmirrored)
183        //      that contains each variation on the display properties of a PointString.
184        Switch renderTypeSG = new Switch(0);
185        renderTypeSG.setCapability(Switch.ALLOW_SWITCH_READ);
186        renderTypeSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
187
188        //      Add the geometry to the display switch.
189        renderTypeSG.addChild(lineShape);
190        renderTypeSG.addChild(pointShape);
191
192        //      Add the basic unmirrored geometry to the symmetry switch.
193        _symmSG = new Switch();
194        _symmSG.setCapability(Switch.ALLOW_SWITCH_READ);
195        _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
196        _symmSG.addChild(renderTypeSG);
197
198        //      Clone the basic geometry to make the mirrored geometry.
199        Node mirrored = renderTypeSG.cloneTree();
200
201        //      Create a mirror across the XZ plane of symmetry transform.
202        Transform3D symmT = new Transform3D();
203        symmT.setScale(new Vector3d(1, -1, 1));
204        TransformGroup symmTG = new TransformGroup(symmT);
205
206        //      Add the mirrored geometry to the symmetry transform group.
207        symmTG.addChild(mirrored);
208
209        //      Add the mirrored geometry to the symmetry switch.
210        _symmSG.addChild(symmTG);
211
212        //      By default, display only the main geometry (not the mirrored).
213        _symmSG.setWhichChild(0);
214
215        return _symmSG;
216    }
217
218    /**
219     * Set the render type used for this group.
220     */
221    @Override
222    public void setRenderType(RenderType type) {
223        super.setRenderType(requireNonNull(type));
224
225        //      Get the main geometry's render switch.
226        Switch mainRenderSG = (Switch)_symmSG.getChild(0);
227
228        //      Get the mirrored geometry's render switch.
229        Switch mirrRenderSG = (Switch)((Group)_symmSG.getChild(1)).getChild(0);
230
231        //      Set switches based on render code.
232        switch (type) {
233            case SOLID_PLUS_WIREFRAME:
234            case SOLID:
235            case WIREFRAME:
236            case STRINGS:
237                mainRenderSG.setWhichChild(0);
238                mirrRenderSG.setWhichChild(0);
239                break;
240
241            case POINTS:
242                mainRenderSG.setWhichChild(1);
243                mirrRenderSG.setWhichChild(1);
244                break;
245
246            default:
247                break;
248        }
249    }
250
251    /**
252     * Creates a new instance of the node. This routine is called by
253     * <code>cloneTree</code> to duplicate the current node.
254     *
255     * @param forceDuplicate when set to <code>true</code>, causes the
256     *                       <code>duplicateOnCloneTree</code> flag to be ignored. When
257     *                       <code>false</code>, the value of each node's
258     *                       <code>duplicateOnCloneTree</code> variable determines whether
259     *                       NodeComponent data is duplicated or copied.
260     * @return A new instance of this Java3D node.
261     */
262    @Override
263    public Node cloneNode(boolean forceDuplicate) {
264        J3DPointString node = new J3DPointString(this.getCanvas3D(), this.getGeomElement());
265        node.duplicateNode(this, forceDuplicate);
266        return node;
267    }
268}