001/**
002 * J3DGeomGroup -- A Java3D node that represents a GeomElement 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.GeomElement;
023import jahuwaldt.js.param.Parameter;
024import java.awt.Color;
025import static java.util.Objects.requireNonNull;
026import java.util.ResourceBundle;
027import javax.measure.quantity.Length;
028import org.jogamp.java3d.BranchGroup;
029import org.jogamp.java3d.Group;
030import org.jogamp.java3d.Switch;
031import javax.swing.event.ChangeEvent;
032import javax.swing.event.ChangeListener;
033import javolution.lang.Immutable;
034
035/**
036 * A Java 3D node that represents a GeomElement in a Java 3D scene graph.
037 *
038 * <p> Modified by: Joseph A. Huwaldt </p>
039 *
040 * @author Joseph A. Huwaldt, Date: April 13, 2009
041 * @version June 4, 2023
042 *
043 * @param <T> The type of GeomElement represented by this object.
044 */
045public abstract class J3DGeomGroup<T extends GeomElement> extends BranchGroup {
046
047    /**
048     * The resource bundle for this class and it's descendants.
049     */
050    static final ResourceBundle RB
051            = ResourceBundle.getBundle("geomss.j3d.J3DResources", java.util.Locale.getDefault());
052
053    /**
054     * The key used to store this instance in the user data of the supplied GeomElement
055     * object.
056     */
057    public static final String USERDATA_KEY = "J3DGeomGroup";
058
059    //  Store the globally active rendering preferences.
060    private static J3DRenderingPrefs _defDrawPrefs = new J3DRenderingPrefs();
061
062    //  Store the rendering preferences used to render this object.
063    private final J3DRenderingPrefs _drawPrefs;
064
065    //  Reference to the geometry for this geometry group.
066    private T _geometry;
067
068    //  The display group containing the geometry.
069    private Switch _dispG;
070
071    //  Indicates of the mirrored version of this geometry is displayed.
072    private boolean _mirrored = false;
073
074    //  The RenderType used for this geometry.
075    private RenderType _renderType = RenderType.SOLID_PLUS_WIREFRAME;
076
077    //  Store the old group so it can potentially be used again in the future.
078    private final J3DGeomGroup<T> _oldJ3DGroup;
079
080    //  A reference to the Canvas3D that this geometry list is being rendered into.
081    private final GeomSSCanvas3D _canvas;
082
083    /**
084     * Construct a J3DGeomGroup using the specified GeomElement as a reference.
085     *
086     * @param canvas   The canvas that the geometry is being rendered into.
087     * @param geometry The GeomSS geometry to be turned into a Java3D node.
088     */
089    @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
090    public J3DGeomGroup(GeomSSCanvas3D canvas, T geometry) {
091        _geometry = requireNonNull(geometry);
092        _canvas = requireNonNull(canvas);
093        _drawPrefs = _defDrawPrefs;
094
095        if (!(geometry instanceof Immutable)) {
096            //  Add a change listener to the geometry and remove this group from the geometry
097            //  if the geometry changes.
098            _geometry.addChangeListener(new ChangeListener() {
099                @Override
100                public void stateChanged(ChangeEvent e) {
101                    _geometry.removeUserData(USERDATA_KEY);
102                    _geometry.removeChangeListener(this);
103                }
104            });
105        }
106
107        this.setCapability(BranchGroup.ALLOW_DETACH);
108        this.setCapability(Group.ALLOW_CHILDREN_READ);
109
110        //      Get any existing J3DGeomGroups in this geometry.
111        _oldJ3DGroup = (J3DGeomGroup)geometry.getUserData(USERDATA_KEY);
112
113        //      Create the scene graph for this group.
114        createSceneGraph();
115
116        //      Make sure this group has the same properties as the old one (if it exists).
117        if (_oldJ3DGroup != null) {
118            this.setMirrored(_oldJ3DGroup.isMirrored());
119            this.setDisplayed(_oldJ3DGroup.isDisplayed());
120            this.setRenderType(_oldJ3DGroup.getRenderType());
121        }
122        
123                //      Store a reference to this group in the geometry element's user data
124        //      (replacing any old data).
125        geometry.putUserData(USERDATA_KEY, this);
126    }
127
128    /**
129     * @return The canvas that this geometry is being rendered into.
130     */
131    protected GeomSSCanvas3D getCanvas3D() {
132        return _canvas;
133    }
134    
135    /**
136     * @return the J3DGeomGroup that was used to render this geometry element previously
137     *         (if any). If this geometry has not been rendered previously,
138     *         <code>null</code> will be returned.
139     */
140    protected J3DGeomGroup<T> getOldJ3DGeomGroup() {
141        return _oldJ3DGroup;
142    }
143
144    /**
145     * @return the {@link GeomElement} that this group represents in the scene graph.
146     */
147    public T getGeomElement() {
148        return _geometry;
149    }
150
151    /**
152     * @return the geometry group containing the scene graph for this GeomGroup. This
153     *         returns the same object that was created by "createSceneGraph()".
154     * @see #createSceneGraph()
155     */
156    protected Group getSceneGraph() {
157        return (Group)_dispG.getChild(0);
158    }
159
160    /**
161     * Create a 3D scene group to represent the geometry.
162     *
163     * @see #getSceneGraph()
164     */
165    private void createSceneGraph() {
166
167        //      Create a switch group so that sub-elements can be turned on or off.
168        _dispG = new Switch(Switch.CHILD_ALL);
169        _dispG.setCapability(Switch.ALLOW_SWITCH_READ);
170        _dispG.setCapability(Switch.ALLOW_SWITCH_WRITE);
171
172        //      Create the geometry itself.
173        Group geomG = createGeometry();
174        _dispG.addChild(requireNonNull(geomG));
175
176        //      Add the root to this group.
177        this.addChild(_dispG);
178    }
179
180    /**
181     * Returns the display setting of this geometry group.
182     *
183     * @return true if the geometry is being displayed and false if it is not displayed.
184     */
185    public boolean isDisplayed() {
186        return _dispG.getWhichChild() != Switch.CHILD_NONE;
187    }
188
189    /**
190     * Sets the display of this geometry group to either displayed (true) or not displayed
191     * (false). Subclasses that override this method should call "super.setDisplayed()" to
192     * maintain proper state and object display.
193     *
194     * @param visible Flag indicating if the geometry is displayed or not.
195     */
196    public void setDisplayed(boolean visible) {
197        if (visible) {
198            _dispG.setWhichChild(Switch.CHILD_ALL);
199            if (_mirrored)
200                internalSetMirrored(true);
201        } else {
202            _dispG.setWhichChild(Switch.CHILD_NONE);
203            if (_mirrored)
204                internalSetMirrored(false);
205        }
206    }
207
208    /**
209     * Set the display of a copy of this geometry mirrored across the XZ plane to either
210     * DISPLAYED (true) or NOT_DISPLAYED (false). Subclasses that override this method
211     * should call "super.setMirrored(mirrored)" to maintain proper state information.
212     *
213     * @param mirrored Flag indicating if the mirrored geometry should be displayed or
214     *                 not.
215     * @see #isMirrored()
216     * @see #internalSetMirrored(boolean)
217     */
218    public void setMirrored(boolean mirrored) {
219        _mirrored = mirrored;
220        internalSetMirrored(mirrored);
221    }
222
223    /**
224     * Returns a flag indicating if the geometry display is currently mirrored about the
225     * XZ plane of symmetry or not.
226     *
227     * @return true if mirroring across the XZ plane is being DISPLAYED and false if it is
228     *         NOT_DISPLAYED.
229     * @see #setMirrored(boolean)
230     */
231    public boolean isMirrored() {
232        return _mirrored;
233    }
234
235    /**
236     * Set the render type used for this group. Sub-classes must provide specific
237     * implementations that depend on the implementation of the geometry and must call
238     * "super.setRenderType()" to properly maintain state.
239     *
240     * @param type Value indicating the way that some objects should be rendered.
241     * @see #getRenderType()
242     */
243    public void setRenderType(RenderType type) {
244        _renderType = requireNonNull(type);
245    }
246
247    /**
248     * Return the render type used for this group. Sub-classes must provide specific
249     * implementations that depend on the the geometry being rendered.
250     *
251     * @return The render type used for this Group.
252     * @see #setRenderType(geomss.j3d.RenderType)
253     */
254    public RenderType getRenderType() {
255        return _renderType;
256    }
257
258    /**
259     * @return the currently active rendering preferences that will be used to render
260     *         future objects.
261     */
262    public static J3DRenderingPrefs getDefaultRenderingPrefs() {
263        return _defDrawPrefs;
264    }
265
266    /**
267     * Set the currently active rendering preferences that will be used to render future
268     * objects.
269     *
270     * @param prefs The rendering preferences to make current.
271     */
272    public static void setDefaultRenderingPrefs(J3DRenderingPrefs prefs) {
273        _defDrawPrefs = requireNonNull(prefs);
274    }
275
276    /**
277     * @return the rendering preferences that were used, in the past, to render this
278     *         object.
279     */
280    public J3DRenderingPrefs getRenderingPrefs() {
281        return _drawPrefs;
282    }
283
284    /**
285     * Set the color to use when rendering points. This is a convenience method for
286     * <code>J3DGeomGroup.setDefaultRenderingPrefs(getDefaultRenderingPrefs().changePointColor(color))</code>.
287     *
288     * @param color The Color to use for rending Point objects in the future.
289     */
290    public static void setPointColor(Color color) {
291        _defDrawPrefs = _defDrawPrefs.changePointColor(requireNonNull(color));
292    }
293
294    /**
295     * Set the color to use when rendering curves and lines. This is a convenience method
296     * for
297     * <code>J3DGeomGroup.setDefaultRenderingPrefs(getDefaultRenderingPrefs().changeLineColor(color))</code>.
298     *
299     * @param color The Color to use for rending lines and curves objects in the future.
300     */
301    public static void setLineColor(Color color) {
302        _defDrawPrefs = _defDrawPrefs.changeLineColor(requireNonNull(color));
303    }
304
305    /**
306     * Set the size that Point objects are rendered in pixels. This is a convenience
307     * method for
308     * <code>J3DGeomGroup.setDefaultRenderingPrefs(getDefaultRenderingPrefs().changePointSize(pixels))</code>.
309     *
310     * @param pixels The size, in pixels, to use when rendering points in the future.
311     * @throws IllegalArgumentException if the point size provided is &lt; 1 pixel.
312     */
313    public static void setPointSize(int pixels) {
314        _defDrawPrefs = _defDrawPrefs.changePointSize(pixels);
315    }
316
317    /**
318     * Set the width that line/curve objects are rendered in pixels. This is a convenience
319     * method for
320     * <code>J3DGeomGroup.setDefaultRenderingPrefs(getDefaultRenderingPrefs().changeLineWidth(pixels))</code>.
321     *
322     * @param pixels The width, in pixels, to use when rendering lines/curves in the
323     *               future.
324     * @throws IllegalArgumentException if the line size provided is &lt; 1 pixel.
325     */
326    public static void setLineWidth(int pixels) {
327        _defDrawPrefs = _defDrawPrefs.changeLineWidth(pixels);
328    }
329
330    /**
331     * Set the tolerance used when drawing parametric objects such as curves and surfaces.
332     * This tolerance is used when determining how to subdivide parametric objects for
333     * rendering. If the input value is <code>null</code> or equal to <code>0</code>, it
334     * will be silently ignored. This is a convenience method for
335     * <code>J3DGeomGroup.setDefaultRenderingPrefs(getDefaultRenderingPrefs().changeDrawTolerance(tol))</code>.
336     *
337     * @param tol The geometric tolerance to use when rendering parametric objects.
338     */
339    public static void setDrawTolerance(Parameter<Length> tol) {
340        _defDrawPrefs = _defDrawPrefs.changeDrawTolerance(requireNonNull(tol));
341    }
342
343    /**
344     * Set the display of a mirrored copy of this geometry. This is called from
345     * "setDisplayed()" to turn on and off the display of mirrored geometry without
346     * changing the "mirrored state" of the object. This call should not affect the output
347     * of "isMirrored()".
348     * <p>
349     * Subclasses should override this to turn on or off a mirrored geometry in response
350     * to the main geometry being set to displayed or not-displayed. The default
351     * implementation does nothing.
352     * </p>
353     *
354     * @param mirrored Flag indicating if the mirrored geometry should be displayed or
355     *                 not.
356     * @see #setMirrored(boolean)
357     * @see #isMirrored() 
358     */
359    protected void internalSetMirrored(boolean mirrored) {
360    }
361
362    /**
363     * Create a new Java 3D <code>Group</code> that contains the geometry contained in
364     * this object. This method is called from <code>createSceneGraph</code>. Sub-classes
365     * must over-ride this method to provide geometry specific implementations.
366     *
367     * @return New Java 3D Group that contains the geometry in this object.
368     * @see #createSceneGraph
369     */
370    protected abstract Group createGeometry();
371
372}