001/**
002 * GeomSSCanvas3D -- The 3D canvas used by the GeomSS application.
003 *
004 * Copyright (C) 2009-2025, by 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.app;
020
021import org.jogamp.java3d.utils.pickfast.PickCanvas;
022import org.jogamp.java3d.utils.universe.SimpleUniverse;
023import org.jogamp.java3d.utils.universe.ViewingPlatform;
024import geomss.GeomSSScene;
025import geomss.geom.*;
026import geomss.geom.Point;
027import geomss.j3d.*;
028import jahuwaldt.j3d.*;
029import jahuwaldt.js.param.Parameter;
030import jahuwaldt.js.param.ParameterVector;
031import java.awt.*;
032import java.awt.event.MouseAdapter;
033import java.awt.event.MouseEvent;
034import java.awt.image.BufferedImage;
035import java.awt.print.PageFormat;
036import java.awt.print.Printable;
037import java.awt.print.PrinterException;
038import java.util.Collections;
039import java.util.Comparator;
040import java.util.Iterator;
041import static java.util.Objects.isNull;
042import static java.util.Objects.nonNull;
043import static java.util.Objects.requireNonNull;
044import javax.measure.quantity.Length;
045import javax.measure.unit.SI;
046import org.jogamp.java3d.*;
047import javax.swing.SwingUtilities;
048import org.jogamp.vecmath.*;
049import javolution.util.FastTable;
050
051/**
052 * The 3D canvas used by this application to display the user's geometry.
053 * 
054 * <p> Modified by: Joseph A. Huwaldt </p>
055 * 
056 * @author Joseph A. Huwaldt, Date: April 14, 2009
057 * @version February 17, 2025
058 */
059@SuppressWarnings("serial")
060public class GeomSSCanvas3D extends BGFGCanvas3D implements Printable {
061
062    /**
063     * Pre-defined view angles used with <code>setView</code>.
064     */
065    public enum PDViewAngle {
066
067        RIGHT_SIDE(new Vector3d(Math.toRadians(-90), Math.toRadians(180), 0)),
068        LEFT_SIDE(new Vector3d(Math.toRadians(-90), 0, 0)),
069        TOP(new Vector3d(0, 0, 0)),
070        BOTTOM(new Vector3d(Math.toRadians(180), 0, 0)),
071        FRONT(new Vector3d(Math.toRadians(-90), Math.toRadians(90), 0)),
072        REAR(new Vector3d(Math.toRadians(-90), Math.toRadians(-90), 0)),
073        TOP_RIGHT_FRONT(new Vector3d(Math.toRadians(45), Math.toRadians(45), Math.toRadians(135))),
074        TOP_LEFT_FRONT(new Vector3d(Math.toRadians(-45), Math.toRadians(45), Math.toRadians(45))),
075        TOP_RIGHT_BACK(new Vector3d(Math.toRadians(45), Math.toRadians(-45), Math.toRadians(-135))),
076        TOP_LEFT_BACK(new Vector3d(Math.toRadians(-45), Math.toRadians(-45), Math.toRadians(-45)));
077
078        private final Matrix3d _rot = new Matrix3d();
079
080        PDViewAngle(Vector3d euler) {
081            Transform3D t3d = new Transform3D();
082            t3d.setEuler(euler);
083            t3d.get(_rot);
084        }
085
086        /**
087         * @return the rotation matrix associated with this view angle.
088         */
089        public Matrix3d getRotationMatrix() {
090            return _rot;
091        }
092    }
093
094    //  The view platform translation amount in meters.
095    private static final double VP_TRANS = 1;
096
097    //  A reference to the public interface for this class.
098    private GeomSSScene _publicInterface = new PublicInterface();
099
100    //  The TransformGroup containing the reference axes.
101    private TransformGroup _axisTG = null;
102
103    //  The TransformGroup containing our geometry.
104    private TransformGroup _geomTG = null;
105
106    //  The group containing all our geometry.
107    private BranchGroup _geomBG = null;
108
109    //  The virtual sphere/arc ball behavior used to control the view angle.
110    private final VirtualSphere _arcBall;
111
112    //  Flag indicating if the geometry is mirrored or not.
113    private boolean _isMirrored = false;
114
115    //  The current render type.
116    private RenderType _renderType = RenderType.SOLID_PLUS_WIREFRAME;
117
118    //  Temporary storage for matricies.
119    private Matrix3d _rot = new Matrix3d();
120    private Transform3D _trans = new Transform3D();
121
122    //  Parmeters used to track a mouse drag event.
123    private boolean _isDragging = false;
124    private int _dragStartX = -1, _dragStartY = -1;
125    private int _dragCurX = -1, _dragCurY = -1;
126
127    //  Used for picking geometry from the canvas.
128    private final PickCanvas _pickCanvas;
129
130    //  A list of items picked by the user by control-clicking/dragging.
131    private FastTable<GeomElement> _pickedItems = FastTable.newInstance();
132    private FastTable<Double> _pickedDistance = FastTable.newInstance();
133
134    //  Flag indicating that the user is waiting for picked output.
135    private boolean _pickFlag = false;
136
137    //  Flag indicating that rendering is in progress.
138    private boolean _isUpdating = false;
139
140    /**
141     * Constructs and initializes a new Canvas3D object that Java 3D can render into.
142     *
143     * @param geometry  The geometry to be displayed.
144     * @param width     The width of the canvas (must be &gt; 0).
145     * @param height    The height of the canvas (must be &gt; 0).
146     * @throws IllegalArgumentException if the specified GraphicsConfiguration does not
147     * support 3D rendering
148     */
149    @SuppressWarnings({"OverridableMethodCallInConstructor"})
150    public GeomSSCanvas3D(GeomElement geometry, int width, int height) {
151        super(SimpleUniverse.getPreferredConfiguration());
152        requireNonNull(geometry);
153
154        this.setSize(width, height);
155
156        //      Listen for mouse-clicks on the canvas in order to implement single point
157        //  picking and changing the rotation center.
158        this.addMouseListener(new MouseAdapter() {
159            @Override
160            public void mouseClicked(MouseEvent event) {
161                if (!SwingUtilities.isLeftMouseButton(event))
162                    return;
163
164                if (event.isControlDown()) {
165                    //  Handle control-clicks.
166                    handlePicking(event.getX(), event.getY());
167                    _pickFlag = false;
168
169                } else {
170                    //  Handle normal clicks.
171                    int clicks = event.getClickCount();
172                    if (clicks == 2) {
173                        changeRotationCenter(event);
174                    }
175                }
176            }
177        });
178
179        // Create a simple universe using our canvas.
180        SimpleUniverse simpleU = new SimpleUniverse(this);
181
182        //      Define the ViewPlatform location.
183        Transform3D vpT = new Transform3D();
184        vpT.set(new Vector3d(0, 0, VP_TRANS));                  //      Translation in meters
185
186        // This will move the ViewPlatform to the specified position.
187        ViewingPlatform vp = simpleU.getViewingPlatform();
188        vp.getViewPlatformTransform().setTransform(vpT);
189
190        // Create the scene graph for our model.
191        BranchGroup scene = createSceneGraph(geometry);
192
193        //      Set up for mouse input.
194        BoundingSphere bounds = new BoundingSphere(new Point3d(), Double.POSITIVE_INFINITY);
195        _arcBall = new VirtualSphere(this);
196        _arcBall.setTransformGroup(_geomTG);
197        _arcBall.setSchedulingBounds(bounds);
198        _geomTG.addChild(_arcBall);
199
200        //      Display visual feedback for the virtual sphere in the canvas.
201        this.addOverlay(_arcBall.getFeedbackOverlay());
202
203        //      Add a transform listener that rotates the reference axis view to match the geometry.
204        _arcBall.addTransformChangeListener(new TransformChangeListener() {
205
206            //  Handles the transform change event.
207            @Override
208            public void transformChanged(TransformChangeEvent event) {
209                //      Only handle rotate events.
210                if (event.getType().equals(TransformChangeEvent.Type.ROTATE)) {
211                    Transform3D newRot = event.getTransform();
212
213                    //  Get the rotational part of the new transform.
214                    newRot.get(_rot);
215
216                    //  Change the axis rotation to match the new rotation.
217                    _axisTG.getTransform(_trans);
218                    _trans.setRotation(_rot);
219                    _axisTG.setTransform(_trans);
220                }
221            }
222        });
223
224        //  Create a mouse picking behavior.
225        MousePickingBehavior mousePick = new MousePickingBehavior();
226        mousePick.setSchedulingBounds(bounds);
227        _geomTG.addChild(mousePick);
228
229        //  Display visual feedback for the mouse picking drag-rectangle on the canvas.
230        this.addOverlay(new DragRectangleOverlay());
231
232        //      By default, center on the origin and zoom out to show a 10 meter object.
233        setCenterAndZoom(new Point3d(0, 0, 0), 10);
234
235        //      Define the pick interface.
236        _pickCanvas = new PickCanvas(this, scene);
237        _pickCanvas.setMode(PickInfo.PICK_GEOMETRY);
238        _pickCanvas.setFlags(PickInfo.NODE | PickInfo.CLOSEST_INTERSECTION_POINT);
239        _pickCanvas.setTolerance(2.0f);
240
241        // Let Java 3D perform optimizations on this scene graph.
242        scene.compile();
243
244        //      Add the geometry scene to the universe.
245        simpleU.addBranchGraph(scene);
246
247        //      Change the front and back clip distances.
248        View view = this.getView();
249        view.setBackClipDistance(VP_TRANS * 10);
250        view.setFrontClipDistance(VP_TRANS * 0.01);
251
252        //      Turn on transparency geometry depth sorting.
253        view.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
254
255    }
256
257    /**
258     * A Java3D mouse behavior that draws a selection rectangle if the user control-drags
259     * the mouse on the 3D canvas.
260     */
261    private class MousePickingBehavior extends Behavior {
262
263        private WakeupOr mouseCriterion;
264
265        /**
266         * Initializes the behavior.
267         */
268        @Override
269        public void initialize() {
270            WakeupCriterion[] mouseEvents = new WakeupCriterion[4];
271
272            mouseEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED);
273            mouseEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED);
274            mouseEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_MOVED);
275            mouseEvents[3] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED);
276
277            mouseCriterion = new WakeupOr(mouseEvents);
278            wakeupOn(mouseCriterion);
279
280        }
281
282        /**
283         * Processes the MouseBehavior stimulus. This method is invoked if the Behavior's
284         * wakeup criteria are satisfied and an active ViewPlatform's activation volume
285         * intersects with the Behavior's scheduling region.
286         *
287         * @param criteria An enumeration of triggered wakeup criteria for this behavior.
288         */
289        @Override
290        public void processStimulus(Iterator<WakeupCriterion> criteria) {
291            while (criteria.hasNext()) {
292                WakeupCriterion wakeup = (WakeupCriterion)criteria.next();
293                if (wakeup instanceof WakeupOnAWTEvent) {
294                    AWTEvent[] events = ((WakeupOnAWTEvent)wakeup).getAWTEvent();
295                    if (events.length > 0) {
296                        MouseEvent evt = (MouseEvent)events[events.length - 1];
297                        doProcess(evt);
298                    }
299                }
300            }
301            wakeupOn(mouseCriterion);
302        }
303
304        /**
305         * Processes the mouse events doing the right thing for each.
306         */
307        private void doProcess(MouseEvent evt) {
308            //  Do nothing if we are not in pick mode.
309            if (!_pickFlag)
310                return;
311
312            //  Look for mouse moved with control key down event.
313            int id = evt.getID();
314            if (id == MouseEvent.MOUSE_PRESSED && evt.isControlDown()) {
315                //  Mouse button pressed with the control key down.
316                java.awt.Point p = evt.getPoint();
317                _dragStartX = p.x;
318                _dragStartY = p.y;
319                _dragCurX = p.x;
320                _dragCurY = p.y;
321                _isDragging = true;
322
323            } else if (_isDragging && (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_DRAGGED)) {
324                java.awt.Point p = evt.getPoint();
325                _dragCurX = p.x;
326                _dragCurY = p.y;
327
328                //      Force a re-draw to show the selection rectangle.
329                this.getView().repaint();
330
331            } else if (_isDragging && id == MouseEvent.MOUSE_RELEASED) {
332                _isDragging = false;
333
334                //  Pick points throughout the selection rectangle!
335                int width = _dragCurX - _dragStartX;
336                boolean swapX = false;
337                if (width < 0) {
338                    swapX = true;
339                    width *= -1;
340                }
341                int height = _dragCurY - _dragStartY;
342                boolean swapY = false;
343                if (height < 0) {
344                    swapY = true;
345                    height *= -1;
346                }
347                if (width == 0 || height == 0) {
348                    handlePicking(_dragStartX, _dragStartY);
349                    _pickFlag = false;
350                    return;
351                }
352
353                int x0 = _dragStartX;
354                int y0 = _dragStartY;
355                float pickRadius = _pickCanvas.getTolerance();
356                if (Math.min(width, height) < 10 * pickRadius) {
357                    //  A simple grid of pick points works best for small or narrow rectangles.
358                    int step = (int)(pickRadius * 2F);
359                    int xf = _dragCurX;
360                    if (swapX) {
361                        x0 = _dragCurX;
362                        xf = _dragStartX;
363                    }
364                    int yf = _dragCurY;
365                    if (swapY) {
366                        y0 = _dragCurY;
367                        yf = _dragStartY;
368                    }
369                    for (int x = x0; x < xf; x += step) {
370                        for (int y = y0; y < yf; y += step) {
371                            handlePicking(x, y);
372                        }
373                    }
374
375                } else {
376                    //  Large rectangles:
377                    //  Using a recursive circle inscribed in right triangle approach to
378                    //  minimize the number of pick tests to be run.
379                    circleInTrianglePick(x0, y0, width, height, !swapX, !swapY, pickRadius);
380                    x0 += (swapX ? -width : width);
381                    y0 += (swapY ? -height : height);
382                    circleInTrianglePick(x0, y0, width, height, swapX, swapY, pickRadius);
383                    _pickCanvas.setTolerance(pickRadius);
384                }
385                _pickFlag = false;
386            }
387        }
388
389        /**
390         * A recursive algorithm that inscribes a circle in a right-triangle and uses that
391         * circle to pick geometry in the rectangle. Then the white space around the
392         * inscribed circle is broken up into smaller right triangles and the process is
393         * repeated for each until the radius of the inscribed circle is less than the
394         * given tolerance.
395         *
396         * @param x0     The X location of the 90 deg corner of the right-triangle.
397         * @param y0     The Y location of the 90 deg corner of the right-triangle.
398         * @param a      The horizontal leg of the right-triangle.
399         * @param b      The vertical leg of the right-triangle.
400         * @param left   Indicates that the 90 deg corner of the triangle is to the left
401         *               (X pos to the right, Y pos down).
402         * @param bottom Indicates that the 90 deg corner is at the bottom (X pos to the
403         *               right, Y pos down).
404         * @param tol    The circle radius to use as a limit for recursion.
405         */
406        private void circleInTrianglePick(double x0, double y0, double a, double b, boolean left, boolean bottom, double tol) {
407
408            //  Create inscribed circle for this triangle.
409            double c = Math.sqrt(a * a + b * b);
410            double r = (a + b - c) / 2.0;     //  Radius of circle inscribed in a right triangle.
411
412            //  Find the center of the circle.
413            double cx = (left ? x0 + r : x0 - r);
414            double cy = (bottom ? y0 + r : y0 - r);
415
416            //  Conduct the pick using the inscribed circle.
417            double rad = r;
418            if (rad < tol)
419                rad = tol;
420            _pickCanvas.setTolerance((float)rad);
421            handlePicking((int)cx, (int)cy);
422            if (r < tol)
423                return;         //  Reached the minimum pick tolerance.
424
425            //  Sub-divide triangle and recursively work each sub-triangle.
426            //  Comment terms apply to left-bottom half of rectangle
427            //  Left-bottom of square.
428            double aa = 0.7 * r;
429            circleInTrianglePick(x0, y0, aa, aa, left, bottom, tol);
430
431            //  Right-bottom of square.
432            double rt2 = 2 * r;
433            double tr = (left ? rt2 : -rt2);
434            double x1 = x0 + tr;
435            double y1 = y0;
436            circleInTrianglePick(x1, y1, aa, aa, !left, bottom, tol);
437
438            //  Left-top of square.
439            tr = (bottom ? rt2 : -rt2);
440            double x2 = x0;
441            double y2 = y0 + tr;
442            circleInTrianglePick(x2, y2, aa, aa, left, !bottom, tol);
443
444            //  Right sub-triangle.
445            aa = a - rt2;
446            double bb = b * aa / a;
447            circleInTrianglePick(x1, y1, aa, bb, left, bottom, tol);
448
449            //  Left sub-triangle.
450            bb = b - rt2;
451            aa = a * bb / b;
452            circleInTrianglePick(x2, y2, aa, bb, left, bottom, tol);
453
454            //  Finally, place a pick circle to cover a largish gap in the above coverage
455            //  Even though it results in quite a bit of overlap.
456            rad = 0.2 * r;
457            if (rad > 2 * tol) {
458                tr = 0.95 * (left ? rt2 : -rt2);
459                cx = x0 + tr;
460                aa = a - rt2;
461                bb = b * aa / a;
462                tr = 0.95 * (bottom ? bb : -bb);
463                cy = y0 + tr;
464                _pickCanvas.setTolerance((float)rad);
465                handlePicking((int)cx, (int)cy);
466            }
467        }
468
469    }   //  end class MousePickingBehavior
470
471    /**
472     * An overlay that draws a visual representation of a selection drag rectangle on the
473     * 3D canvas.
474     */
475    private final class DragRectangleOverlay implements BGFGImage {
476
477        private BufferedImage bufImg;
478        private Graphics2D g2d;
479
480        /**
481         * Returns the overlay used to show a drag rectangle.
482         */
483        @Override
484        public synchronized BufferedImage getImage() {
485            if (!_isDragging)
486                return null;
487
488            //  Create a buffered image the size of the drag rectangle.
489            int width = Math.abs(_dragCurX - _dragStartX);
490            int height = Math.abs(_dragCurY - _dragStartY);
491            if (width == 0 || height == 0)
492                return null;
493            int max = Math.max(width, height);
494
495            //  Create a new buffered image only if the old one is to small.
496            if (isNull(bufImg) || max > bufImg.getWidth()) {
497                //  TYPE_INT_ARGB is required to get transparency.  However, the buffer only gets
498                //  rendered as a square with the given width no matter the height specified.
499                //  So, this is using the max of width & height to work around this problem
500                //  which must be a bug in Java3D(?).
501                bufImg = new BufferedImage(max, max, BufferedImage.TYPE_INT_ARGB);
502                g2d = bufImg.createGraphics();
503            }
504
505            //  Erase the buffered image to transparent.
506            clearSurface(g2d, bufImg.getWidth(), bufImg.getHeight());
507
508            //  Draw a transparent rectangle over the selection region.
509            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
510            Color selColor = SystemColor.textHighlight;
511            g2d.setPaint(selColor);
512            g2d.fillRect(0, 0, width, height);
513
514            return bufImg;
515        }
516
517        /**
518         * Clears the buffered image to be 100% transparent.
519         */
520        private void clearSurface(Graphics2D drawg2d, int width, int height) {
521            drawg2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
522            drawg2d.fillRect(0, 0, width + 1, height + 1);
523            drawg2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
524        }
525
526        /**
527         * Returns the X coordinate of the upper left corner of the drag rectangle.
528         */
529        @Override
530        public int getImageX() {
531            int diff = _dragCurX - _dragStartX;
532            if (diff >= 0)
533                return _dragStartX;
534            return _dragStartX + diff;
535        }
536
537        /**
538         * Return the Y coordinate of the upper left corner of the drag rectangle.
539         */
540        @Override
541        public int getImageY() {
542            int diff = _dragCurY - _dragStartY;
543            if (diff >= 0)
544                return _dragStartY;
545            return _dragStartY + diff;
546        }
547    }   //  end class DragRectangleOverlay
548
549    /**
550     * @return the 3D scene displayed in this canvas (for use in BeanShell primarily).
551     */
552    public GeomSSScene getScene() {
553        return _publicInterface;
554    }
555
556    /**
557     * Set the viewing angle for this canvas to one of the pre-set angles.
558     *
559     * @param viewAngle The pre-defined viewing angle for this canvas.
560     */
561    public void setView(PDViewAngle viewAngle) {
562        _arcBall.setRotation(viewAngle.getRotationMatrix());
563    }
564
565    /**
566     * Add a <code>TransformChangeListener</code> to this canvas.
567     *
568     * @param listener The transform change listener to add.
569     */
570    public void addTransformChangeListener(TransformChangeListener listener) {
571        _arcBall.addTransformChangeListener(listener);
572    }
573
574    /**
575     * Remove a <code>TransformChangeListener</code> from this canvas.
576     *
577     * @param listener The transform change listener to remove.
578     */
579    public void removeTransformChangeListener(TransformChangeListener listener) {
580        _arcBall.removeTransformChangeListener(listener);
581    }
582
583    /**
584     * Prints the 3D model image from this canvas to the specified Graphics context using
585     * the specified page format. This method is called by a PrinerJob and should not be
586     * called directly by users.
587     *
588     * @param graphics   the context into which the page is drawn
589     * @param pageFormat the size and orientation of the page being drawn
590     * @param pageIndex  the zero based index of the page to be drawn.
591     * @return PAGE_EXISTS if the page is rendered successfully or NO_SUCH_PAGE if
592     *         pageIndex specifies a non-existent page.
593     */
594    @Override
595    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
596            throws PrinterException {
597        if (pageIndex > 0)
598            return NO_SUCH_PAGE;
599
600        //  Pause a while to let the rendering finish up.
601        waitForRendering(1000);
602
603        //      Get the current 3D model image from the frame buffer.
604        BufferedImage img = getCurrentImage();
605
606        //      Get some format information.
607        double iX = pageFormat.getImageableX();
608        double iY = pageFormat.getImageableY();
609        double iWidth = pageFormat.getWidth();
610        double iHeight = pageFormat.getHeight();
611
612        //      Draw the image centered horizontally and at the top of the page.
613        double scale = 1;
614        int width = img.getWidth(this);
615        if (width > iWidth)
616            scale = iWidth / width;
617        int height = img.getHeight(this);
618        if (height * scale > iHeight)
619            scale = iHeight / height;
620
621        //      Draw the image into the supplied graphics context.
622        int x = (int)((iWidth - width * scale) / 2 + iX);
623        int y = (int)iY;
624        graphics.drawImage(img, x, y, (int)(width * scale), (int)(height * scale), this);
625
626        return PAGE_EXISTS;
627    }
628
629    /**
630     * Create scene graph branch group.
631     */
632    private BranchGroup createSceneGraph(GeomElement geometry) throws IllegalArgumentException {
633        BranchGroup contentRoot = new BranchGroup();
634
635        //      Create a transform group for the reference axes.
636        _axisTG = new TransformGroup();
637        _axisTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
638        _axisTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
639        Transform3D t1 = new Transform3D();
640        _axisTG.setTransform(t1);
641        adjustAxisTG();
642
643        // Create a reference axis so we can see what we are doing.
644        Axis scrnAxis = new Axis((float)VP_TRANS * 0.05F, 0.45F);
645        _axisTG.addChild(scrnAxis);
646        contentRoot.addChild(_axisTG);
647
648        //      Create a transform group that can be the root of the displayed geometry.
649        _geomTG = new TransformGroup();
650        _geomTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
651        _geomTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
652
653        //      Create a branch group to contain all our geometry.
654        _geomBG = new BranchGroup();
655        _geomBG.setCapability(Group.ALLOW_CHILDREN_READ);
656        _geomBG.setCapability(Group.ALLOW_CHILDREN_WRITE);
657        _geomBG.setCapability(Group.ALLOW_CHILDREN_EXTEND);
658
659        // Convert the geometry into J3D geometry.
660        J3DGeomGroup geomGroup = J3DGeomGroupFactory.newGroup(this, geometry);
661        _geomBG.addChild(geomGroup);
662
663        //      Add the geometry to the transform group.
664        _geomTG.addChild(_geomBG);
665
666        //      Add the entire scene graph to the content root.
667        contentRoot.addChild(_geomTG);
668
669        // Let there be LIGHT!
670        BoundingSphere lightBounds = new BoundingSphere(new Point3d(), Double.POSITIVE_INFINITY);
671        AmbientLight lightA = new AmbientLight();
672        lightA.setInfluencingBounds(lightBounds);
673        contentRoot.addChild(lightA);
674
675        DirectionalLight lightD1 = new DirectionalLight();
676        lightD1.setInfluencingBounds(lightBounds);
677        lightD1.setDirection(-15.F, -2.5F, -20.0F);
678        Color3f color = new Color3f();
679        color.x = 0.4f;
680        color.y = 0.4f;
681        color.z = 0.4f;
682        lightD1.setColor(color);
683        contentRoot.addChild(lightD1);
684
685        DirectionalLight lightD2 = new DirectionalLight();
686        lightD2.setInfluencingBounds(lightBounds);
687        lightD2.setDirection(15.F, -2.5F, -20.0F);
688        color.x = 0.4f;
689        color.y = 0.4f;
690        color.z = 0.4f;
691        lightD2.setColor(color);
692        contentRoot.addChild(lightD2);
693
694        // Make the background white
695        Background background = new Background(1.0f, 1.0f, 1.0f);
696        background.setApplicationBounds(lightBounds);
697        contentRoot.addChild(background);
698
699        return contentRoot;
700    }
701
702    /**
703     * Set up the transform group used by the reference axes. Uses class variables for
704     * temporary storage: _trans, _rot
705     */
706    private void adjustAxisTG() {
707
708        //      Get the current axis transform.
709        _axisTG.getTransform(_trans);
710
711        //      Get the rotational part of the transform.
712        _trans.get(_rot);
713
714        //      Create a translation for the axes.
715        if (this.getView().getProjectionPolicy() == View.PARALLEL_PROJECTION) {
716            _trans.set(new Vector3d(-0.85, -0.60, 0));
717            _trans.setScale(2.5);
718        } else
719            _trans.set(new Vector3d(-VP_TRANS * 0.35, -VP_TRANS * 0.25, 0));
720
721        //      Change the axis rotation to match the original rotation.
722        _trans.setRotation(_rot);
723
724        //      Store the updated transform for the axis.
725        _axisTG.setTransform(_trans);
726
727    }
728
729    /**
730     * This method is called whenever the user double-clicks on the 3D canvas. It
731     * identifies if there was any geometry picked. If so, then the center of rotation is
732     * moved to the picked point and the view is changed to look at the picked point.
733     */
734    private void changeRotationCenter(MouseEvent event) {
735
736        //      See if the mouse point intersects the geometry anywhere.
737        _pickCanvas.setShapeLocation(event);
738        PickInfo pickInfo = _pickCanvas.pickClosest();
739
740        if (nonNull(pickInfo)) {
741            //  Get the picked node from the scene.
742            Node pickedNode = pickInfo.getNode();
743
744            //  Get the local to virtual world transform from the picked node.
745            Transform3D locToVWorld = new Transform3D();
746            pickedNode.getLocalToVworld(locToVWorld);
747
748            //  Return the point that was picked in the scene.
749            Point3d rotationPoint = pickInfo.getClosestIntersectionPoint();
750
751            //  Transform the point to virtual world coordiantes.
752            locToVWorld.transform(rotationPoint);
753
754            //  Set this point as the rotation center.
755            _arcBall.setRotationCenter(rotationPoint);
756        }
757
758    }
759
760    /**
761     * This method is called whenever the user control-clicks on the 3D canvas. It
762     * identifies if there was any geometry picked and stores this in a list.
763     */
764    private void handlePicking(int xpos, int ypos) {
765        if (!_pickFlag)
766            return;
767
768        //      See if the mouse point intersects the geometry anywhere.
769        _pickCanvas.setShapeLocation(xpos, ypos);
770        PickInfo[] pickInfo = _pickCanvas.pickAll();
771
772        if (nonNull(pickInfo) && pickInfo.length > 0) {
773            //  We have at least one hit.
774            int size = pickInfo.length;
775            for (int i = 0; i < size; ++i) {
776                //      For each hit, make sure it is a GeomShape3D object.
777                Node pickedNode = pickInfo[i].getNode();
778                if (pickedNode != null && pickedNode instanceof GeomShape3D) {
779                    //  Extract the geometry element associated with the GeomShape3D object.
780                    GeomShape3D geomNode = (GeomShape3D)pickedNode;
781                    GeomElement geomElement = geomNode.getGeometryElement();
782
783                    //  Add the picked geometry to the output list (unless it is already there).
784                    if (!_pickedItems.contains(geomElement)) {
785                        _pickedItems.add(geomElement);
786
787                        //  Get the closest distance from the viewpane to the geometry.
788                        double dist = pickInfo[i].getClosestDistance();
789                        _pickedDistance.add(dist);
790                    }
791                }
792            }   //  Next i
793        }
794
795    }
796
797    /**
798     * Set the rotation center and zoom scale in the virtual sphere.
799     *
800     * @param rotCenter The center of rotation for the virtual sphere.
801     * @param geomWidth The maximum width of the geometry to display in meters.
802     */
803    private void setCenterAndZoom(Point3d rotCenter, double geomWidth) {
804
805        //      Get the field of view in radians.
806        double fieldOfView = this.getView().getFieldOfView();
807
808        //      Estimate the zoom distance.
809        double projFactor = 0.8;
810        if (_publicInterface.getProjectionPolicy() == ProjectionPolicy.PARALLEL_PROJECTION)
811            projFactor = 2.0;
812        double maxWidth = 2 * VP_TRANS * Math.tan(fieldOfView / 2.0) * projFactor;
813        double zoomScale = maxWidth / geomWidth;
814
815        //      Change the rotation point and zoom distance in the virtual sphere.
816        _arcBall.setRotationCenter(rotCenter, zoomScale);
817
818    }
819
820    /**
821     * Process code after we have swapped the image to the foreground.
822     */
823    @Override
824    public void postSwap() {
825        super.postSwap();
826        _isUpdating = false;
827    }
828
829    /**
830     * Causes the calling thread to wait until either the current rendering is complete,
831     * or until the specified amount of time has elapsed. If the calling thread is
832     * interrupted by any thread before or while it is waiting, then this method will
833     * simply return immediately.
834     *
835     * @param timeout the maximum time to wait in milliseconds.
836     * @throws IllegalArgumentException if the value of timeout is negative.
837     */
838    @SuppressWarnings("SleepWhileInLoop")
839    public void waitForRendering(long timeout) throws IllegalArgumentException {
840        if (timeout < 0)
841            throw new IllegalArgumentException();
842        long tstart = System.currentTimeMillis();
843        long tend = tstart + timeout;
844        try {
845            while (_isUpdating && System.currentTimeMillis() < tend) {
846                Thread.sleep(10);
847            }
848        } catch (InterruptedException e) { /* do nothing */ }
849    }
850
851    /**
852     * A class that serves as the public interface (in BeanShell primarily) for this
853     * application.
854     */
855    private class PublicInterface implements GeomSSScene {
856
857        /**
858         * Draws the specified geometry element into the 3D scene without erasing the
859         * existing geometry. This is identical to <code>draw(newGeom, false);</code>.
860         *
861         * @param newGeom The new geometry to be displayed on the 3D canvas.
862         */
863        @Override
864        public void draw(GeomElement newGeom) {
865            draw(newGeom, false);
866        }
867
868        /**
869         * Draws the specified geometry element in the 3D scene.
870         *
871         * @param newGeom The new geometry to be displayed on the 3D canvas.
872         * @param erase   Set to <code>true</code> to erase the geometry currently
873         *                displayed. Set to <code>false</code> to add the new geometry to
874         *                the existing display.
875         */
876        @Override
877        public void draw(GeomElement newGeom, boolean erase) {
878
879            //  Deal with empty lists first by just ignoring them.
880            if (newGeom instanceof GeometryList) {
881                if (!((GeometryList)newGeom).containsGeometry())
882                    return;
883            }
884
885            //  Indicate that the 3D scene is being updated.
886            _isUpdating = true;
887
888            //  Stop the renderer so that the changes in the scene appear in a single frame.
889            View view = getView();
890            boolean isRendering = view.isViewRunning();
891            if (isRendering)
892                view.stopView();
893            try {
894
895                //      Detach the old geometry if requested.
896                if (erase)
897                    erase();
898
899                //      Create an appropriate J3D Group.
900                J3DGeomGroup newGroup = J3DGeomGroupFactory.newGroup(GeomSSCanvas3D.this, newGeom);
901
902                if (nonNull(newGroup)) {
903                    //  Add the new group to the scene graph.
904                    newGroup.setMirrored(isMirrored());
905                    newGroup.setRenderType(_renderType);
906
907                    //  Add the new geometry.
908                    _geomBG.addChild(newGroup);
909                }
910
911            } finally {
912                //  Restart the renderer before leaving.
913                if (isRendering)
914                    view.startView();
915            }
916        }
917
918        /**
919         * Erases all the geometry from the 3D scene.
920         */
921        @Override
922        public void erase() {
923            int numChildren = _geomBG.numChildren();
924
925            //  Indicate that the 3D scene is being updated.
926            _isUpdating = true;
927
928            //  Stop the renderer so that the changes in the scene appear in a single frame.
929            View view = getView();
930            boolean isRendering = view.isViewRunning();
931            if (isRendering)
932                view.stopView();
933            try {
934
935                //      Loop over children from last to first, removing each in turn.
936                for (int i = numChildren - 1; i >= 0; --i) {
937                    J3DGeomGroup old = (J3DGeomGroup)_geomBG.getChild(i);
938                    if (nonNull(old)) {
939                        old.detach();
940                    }
941                }
942
943            } finally {
944                //  Restart the renderer before leaving.
945                if (isRendering)
946                    view.startView();
947            }
948        }
949
950        /**
951         * Erases the specified geometry from the 3D scene (if possible).
952         */
953        @Override
954        public void erase(GeomElement geometry) {
955            J3DGeomGroup dispGroup = (J3DGeomGroup)geometry.getUserData(J3DGeomGroup.USERDATA_KEY);
956            if (nonNull(dispGroup)) {
957                _isUpdating = true;
958                dispGroup.detach();
959
960            } else if (geometry instanceof GeometryList) {
961                //  Indicate that the 3D scene is being updated.
962                _isUpdating = true;
963
964                //  Stop the renderer so that the changes in the scene appear in a single frame.
965                View view = getView();
966                boolean isRendering = view.isViewRunning();
967                if (isRendering)
968                    view.stopView();
969                try {
970
971                    //  A list of items was input.  If any of it's contents are displayed, erase them.
972                    GeometryList<? extends GeometryList, GeomElement> lst = (GeometryList)geometry;
973                    for (GeomElement item : lst) {
974                        erase(item);    //      Recursively erase items in the list.
975                    }
976
977                } finally {
978                    //  Restart the renderer before leaving.
979                    if (isRendering)
980                        view.startView();
981                }
982            }
983        }
984
985        /**
986         * Centers the geometry in the display.
987         */
988        @Override
989        public void center() {
990            GeomList geomList = GeomList.newInstance();
991
992            //  Get the geometry from the transform group.
993            int numChildren = _geomBG.numChildren();
994            for (int i = 0; i < numChildren; ++i) {
995                J3DGeomGroup geomG = (J3DGeomGroup)_geomBG.getChild(i);
996                GeomElement geom = geomG.getGeomElement();
997                if (nonNull(geom))
998                    geomList.add(geom);
999            }
1000            if (!geomList.containsGeometry())
1001                return;
1002
1003            //  Get the bounds of the geometry.
1004            GeomPoint minPnt = geomList.getBoundsMin();
1005            GeomPoint maxPnt = geomList.getBoundsMax();
1006
1007            //  Account for the mirrored flag.
1008            if (isMirrored() && minPnt.getPhyDimension() > 1) {
1009                Parameter<Length> ymin = minPnt.get(Point.Y);
1010                Parameter<Length> ymax = maxPnt.get(Point.Y);
1011                Parameter<Length> ymino = ymin.opposite();
1012                Parameter<Length> ymaxo = ymax.opposite();
1013                Parameter<Length> yminNew = ymin.min(ymino.min(ymaxo));
1014                Parameter<Length> ymaxNew = ymax.max(ymaxo.max(ymino));
1015                if (!ymin.equals(yminNew)) {
1016                    MutablePoint p = MutablePoint.valueOf(minPnt);
1017                    p.set(Point.Y, yminNew);
1018                    minPnt = p;
1019                }
1020                if (!ymax.equals(ymaxNew)) {
1021                    MutablePoint p = MutablePoint.valueOf(maxPnt);
1022                    p.set(Point.Y, ymaxNew);
1023                    maxPnt = p;
1024                }
1025            }
1026
1027            //  Find the average point in model coordinates in meters.
1028            ParameterVector<Length> minV = minPnt.toParameterVector();
1029            ParameterVector<Length> maxV = maxPnt.toParameterVector();
1030            Point avgPnt = Point.valueOf(minV.plus(maxV).divide(2).to(SI.METER));
1031
1032            //  Indicate that the 3D scene is being updated.
1033            _isUpdating = true;
1034
1035            //  Convert the average point from model coordinates to virtual world coordinates.
1036            int dim = avgPnt.getPhyDimension();
1037            double rotCenterX = avgPnt.getValue(0);
1038            double rotCenterY = (dim > 1 ? avgPnt.getValue(1) : 0);
1039            double rotCenterZ = (dim > 2 ? avgPnt.getValue(2) : 0);
1040            Point3d rotCenter = new Point3d(rotCenterX, rotCenterY, rotCenterZ);
1041            Transform3D model2VWorld = new Transform3D();
1042            _geomTG.getTransform(model2VWorld);
1043            model2VWorld.transform(rotCenter);
1044
1045            //  Change the rotation point in the virtual sphere.
1046            _arcBall.setRotationCenter(rotCenter);
1047
1048            //  Cleanup before leaving.
1049            GeomList.recycle(geomList);
1050        }
1051
1052        /**
1053         * Centers the geometry in the display and zooms until the geometry fills the
1054         * display.
1055         */
1056        @Override
1057        public void centerAndZoom() {
1058            GeomList geomList = GeomList.newInstance();
1059
1060            //  Get the geometry from the transform group.
1061            int numChildren = _geomBG.numChildren();
1062            for (int i = 0; i < numChildren; ++i) {
1063                J3DGeomGroup geomG = (J3DGeomGroup)_geomBG.getChild(i);
1064                GeomElement geom = geomG.getGeomElement();
1065                if (nonNull(geom))
1066                    geomList.add(geom);
1067            }
1068            if (!geomList.containsGeometry())
1069                return;
1070
1071            //  Get the bounds of the geometry.
1072            GeomPoint minPnt = geomList.getBoundsMin();
1073            GeomPoint maxPnt = geomList.getBoundsMax();
1074            if (minPnt.isApproxEqual(maxPnt)) {
1075                //  If the max & min are identical, just center, don't zoom.
1076                center();
1077                return;
1078            }
1079
1080            //  Account for the mirrored flag.
1081            if (isMirrored() && minPnt.getPhyDimension() > 1) {
1082                Parameter<Length> ymin = minPnt.get(Point.Y);
1083                Parameter<Length> ymax = maxPnt.get(Point.Y);
1084                Parameter<Length> ymino = ymin.opposite();
1085                Parameter<Length> ymaxo = ymax.opposite();
1086                Parameter<Length> yminNew = ymin.min(ymino.min(ymaxo));
1087                Parameter<Length> ymaxNew = ymax.max(ymaxo.max(ymino));
1088                if (!ymin.equals(yminNew)) {
1089                    MutablePoint p = MutablePoint.valueOf(minPnt);
1090                    p.set(Point.Y, yminNew);
1091                    minPnt = p;
1092                }
1093                if (!ymax.equals(ymaxNew)) {
1094                    MutablePoint p = MutablePoint.valueOf(maxPnt);
1095                    p.set(Point.Y, ymaxNew);
1096                    maxPnt = p;
1097                }
1098            }
1099
1100            //  Find the average point in model coordinates in meters.
1101            ParameterVector<Length> minV = minPnt.toParameterVector();
1102            ParameterVector<Length> maxV = maxPnt.toParameterVector();
1103            Point avgPnt = Point.valueOf(minV.plus(maxV).divide(2).to(SI.METER));
1104
1105            //  Indicate that the 3D scene is being updated.
1106            _isUpdating = true;
1107
1108            //  Convert the average point from model coordinates to virtual world coordinates.
1109            int dim = avgPnt.getPhyDimension();
1110            double rotCenterX = avgPnt.getValue(0);
1111            double rotCenterY = (dim > 1 ? avgPnt.getValue(1) : 0);
1112            double rotCenterZ = (dim > 2 ? avgPnt.getValue(2) : 0);
1113            Point3d rotCenter = new Point3d(rotCenterX, rotCenterY, rotCenterZ);
1114            Transform3D model2VWorld = new Transform3D();
1115            _geomTG.getTransform(model2VWorld);
1116            model2VWorld.transform(rotCenter);
1117
1118            //  Center and zoom the display to show the whole geometry.
1119            double geomWidth = maxV.minus(minV).norm().getValue(SI.METER);
1120            setCenterAndZoom(rotCenter, geomWidth);
1121
1122            GeomList.recycle(geomList);
1123        }
1124
1125        /**
1126         * Pick items from the scene by control-clicking with the mouse. Returns a list of
1127         * selected items.
1128         */
1129        @Override
1130        @SuppressWarnings("SleepWhileInLoop")
1131        public GeomList pick() {
1132            //  Clear the picked items list.
1133            _pickedItems.clear();
1134            _pickedDistance.clear();
1135
1136            try {
1137                _pickFlag = true;
1138                _isDragging = false;
1139                //      Wait for the user to pick something.
1140                while (_pickFlag)
1141                    Thread.sleep(20);
1142            } catch (InterruptedException e) {
1143                _pickFlag = false;
1144            }
1145
1146            //  Return a geometry list containing the picked items sorted by distance
1147            //  from view point.
1148            GeomList output = GeomList.newInstance();
1149            output.addAll(_pickedItems);
1150            Collections.sort(output, new PickedDistComparator());
1151            _pickedItems.clear();
1152            _pickedDistance.clear();
1153
1154            return output;
1155        }
1156
1157        /**
1158         * A comparator used to sort the picked items in _pickedItems by distance from the
1159         * view point.
1160         */
1161        private class PickedDistComparator implements Comparator<GeomElement> {
1162
1163            @Override
1164            public int compare(GeomElement o1, GeomElement o2) {
1165                int idx1 = _pickedItems.indexOf(o1);
1166                int idx2 = _pickedItems.indexOf(o2);
1167                Double d1 = _pickedDistance.get(idx1);
1168                Double d2 = _pickedDistance.get(idx2);
1169                return -Double.compare(d1, d2);
1170            }
1171        }
1172
1173        /**
1174         * Sets a flag indicating that the geometry is mirrored about the XZ plane of
1175         * symmetry.
1176         */
1177        @Override
1178        public void setMirrored(boolean mirrored) {
1179            _isMirrored = mirrored;
1180            int numChildren = _geomBG.numChildren();
1181            for (int i = 0; i < numChildren; ++i) {
1182                J3DGeomGroup geomG = (J3DGeomGroup)_geomBG.getChild(i);
1183                geomG.setMirrored(mirrored);
1184            }
1185        }
1186
1187        /**
1188         * Returns a flag indicating if the geometry display is currently mirrored about
1189         * the XZ plane of symmetry or not.
1190         */
1191        @Override
1192        public boolean isMirrored() {
1193            return _isMirrored;
1194        }
1195
1196        /**
1197         * Sets {@link GeomSS.j3d.RenderType rendering type} for all the objects currently
1198         * displayed in the entire scene.
1199         */
1200        @Override
1201        public void setRenderType(RenderType type) {
1202            _renderType = requireNonNull(type);
1203            int numChildren = _geomBG.numChildren();
1204            for (int i = 0; i < numChildren; ++i) {
1205                J3DGeomGroup geomG = (J3DGeomGroup)_geomBG.getChild(i);
1206                geomG.setRenderType(type);
1207            }
1208        }
1209
1210        /**
1211         * Returns the {@link GeomSS.j3d.RenderType rendering type} for the 1st item in
1212         * the scene.
1213         */
1214        @Override
1215        public RenderType getRenderType() {
1216            return _renderType;
1217        }
1218
1219        /**
1220         * Sets the color used when rendering points.
1221         */
1222        @Override
1223        public void setPointColor(Color color) {
1224            J3DGeomGroup.setPointColor(color);
1225        }
1226
1227        /**
1228         * Returns the color used when rendering points.
1229         */
1230        @Override
1231        public Color getPointColor() {
1232            return J3DGeomGroup.getDefaultRenderingPrefs().getPointColor();
1233        }
1234
1235        /**
1236         * Set the size that Point objects are rendered in pixels.
1237         */
1238        @Override
1239        public void setPointSize(int pixels) {
1240            J3DGeomGroup.setPointSize(pixels);
1241        }
1242
1243        /**
1244         * Return the size that Point objects are rendered in pixels.
1245         */
1246        @Override
1247        public int getPointSize() {
1248            return J3DGeomGroup.getDefaultRenderingPrefs().getPointSize();
1249        }
1250
1251        /**
1252         * Sets the color used when rendering curves and lines.
1253         */
1254        @Override
1255        public void setLineColor(Color color) {
1256            J3DGeomGroup.setLineColor(color);
1257        }
1258
1259        /**
1260         * Returns the color used when rendering curves and lines.
1261         */
1262        @Override
1263        public Color getLineColor() {
1264            return J3DGeomGroup.getDefaultRenderingPrefs().getLineColor();
1265        }
1266
1267        /**
1268         * Set the width that line/curve objects are rendered in pixels.
1269         */
1270        @Override
1271        public void setLineWidth(int pixels) {
1272            J3DGeomGroup.setLineWidth(pixels);
1273        }
1274
1275        /**
1276         * Return the width that line/curve objects are rendered in pixels.
1277         */
1278        @Override
1279        public int getLineWidth() {
1280            return J3DGeomGroup.getDefaultRenderingPrefs().getLineWidth();
1281        }
1282
1283        /**
1284         * Set the tolerance used when drawing parametric objects such as curves and
1285         * surfaces. This tolerance is used when determining how to subdivide parametric
1286         * objects for rendering. If the input value is <code>null</code> or equal to
1287         * <code>0</code>, it will be silently ignored.
1288         */
1289        @Override
1290        public void setDrawTolerance(Parameter<Length> tol) {
1291            J3DGeomGroup.setDrawTolerance(tol);
1292        }
1293
1294        /**
1295         * Return the tolerance used when drawing parametric objects such as curves and
1296         * surfaces. This tolerance is used when determining how to subdivide parametric
1297         * objects for rendering.
1298         */
1299        @Override
1300        public Parameter<Length> getDrawTolerance() {
1301            return J3DGeomGroup.getDefaultRenderingPrefs().getDrawTolerance();
1302        }
1303
1304        /**
1305         * Retrieves the current projection policy for this scene.
1306         */
1307        @Override
1308        public ProjectionPolicy getProjectionPolicy() {
1309            ProjectionPolicy policy = ProjectionPolicy.PERSPECTIVE_PROJECTION;
1310            View view = GeomSSCanvas3D.this.getView();
1311            if (view.getProjectionPolicy() == View.PARALLEL_PROJECTION)
1312                policy = ProjectionPolicy.PARALLEL_PROJECTION;
1313            return policy;
1314        }
1315
1316        /**
1317         * Sets the projection policy for this scene. This specifies the type of
1318         * projection transform that will be generated. A value of PARALLEL_PROJECTION
1319         * specifies that a parallel projection transform is generated. A value of
1320         * PERSPECTIVE_PROJECTION specifies that a perspective projection transform is
1321         * generated.
1322         *
1323         * @param policy The new projection policy, one of PARALLEL_PROJECTION or
1324         *               PERSPECTIVE_PROJECTION.
1325         */
1326        @Override
1327        public void setProjectionPolicy(ProjectionPolicy policy) {
1328            requireNonNull(policy);
1329            View view = GeomSSCanvas3D.this.getView();
1330            if (policy == ProjectionPolicy.PERSPECTIVE_PROJECTION) {
1331                view.setProjectionPolicy(View.PERSPECTIVE_PROJECTION);
1332            } else {
1333                view.setProjectionPolicy(View.PARALLEL_PROJECTION);
1334            }
1335            adjustAxisTG();
1336        }
1337
1338        /**
1339         * Set the color (of the specified type) used to render surfaces and point-arrays.
1340         *
1341         * @param colorType The aspect or type of the surface color that is being set.
1342         * @param color     The color to use for the specified type of surface color. The
1343         *                  alpha is ignored.
1344         * @see #setSurfaceAlpha
1345         */
1346        @Override
1347        public void setSurfaceColor(SurfaceColorType colorType, Color color) {
1348            requireNonNull(colorType);
1349            requireNonNull(color);
1350            J3DRenderingPrefs prefs = J3DGeomGroup.getDefaultRenderingPrefs();
1351            J3DGeomGroup.setDefaultRenderingPrefs(prefs.changeSurfaceColor(colorType, color));
1352        }
1353
1354        /**
1355         * Get the color (of the specified type) used to render surfaces and point-arrays.
1356         *
1357         * @param colorType The aspect or type of the surface color that is being set. If
1358         *                  AMBIENT_AND_DIFFUSE is passed in, then only the ambient color
1359         *                  is returned!
1360         * @return The color used for the specified type of surface color. Alpha is
1361         *         ignored.
1362         * @see #getSurfaceAlpha
1363         */
1364        @Override
1365        public Color getSurfaceColor(SurfaceColorType colorType) {
1366            requireNonNull(colorType);
1367            return J3DGeomGroup.getDefaultRenderingPrefs().getSurfaceColor(colorType);
1368        }
1369
1370        /**
1371         * Set the alpha or transparency used to render surfaces and point-arrays.
1372         *
1373         * @param alpha The alpha value to use (0.0=completely transparent, 1.0=completely
1374         *              opaque).
1375         */
1376        @Override
1377        public void setSurfaceAlpha(float alpha) {
1378            J3DRenderingPrefs prefs = J3DGeomGroup.getDefaultRenderingPrefs();
1379            J3DGeomGroup.setDefaultRenderingPrefs(prefs.changeSurfaceAlpha(alpha));
1380        }
1381
1382        /**
1383         * Get the alpha or transparency used when rendering surfaces or point-arrays.
1384         *
1385         * @return The alpha value used (0.0=completely transparent, 1.0=completely
1386         *         opaque).
1387         */
1388        @Override
1389        public float getSurfaceAlpha() {
1390            return J3DGeomGroup.getDefaultRenderingPrefs().getSurfaceAlpha();
1391        }
1392
1393        /**
1394         * Set the shininess used when rendering surfaces and point-arrays.
1395         *
1396         * @param shininess The shininess to use in the range [0.0, 1.0] where 0.0 is not
1397         *                  shiny and 1.0 is very shiny. Values outside this range are
1398         *                  clamped.
1399         */
1400        @Override
1401        public void setSurfaceShininess(float shininess) {
1402            J3DRenderingPrefs prefs = J3DGeomGroup.getDefaultRenderingPrefs();
1403            J3DGeomGroup.setDefaultRenderingPrefs(prefs.changeSurfaceShininess(shininess));
1404        }
1405
1406        /**
1407         * Get the shininess used when rendering surfaces and point-arrays.
1408         *
1409         * @return The shininess to use in the range [0.0, 1.0] where 0.0 is not shiny and
1410         *         1.0 is very shiny.
1411         */
1412        @Override
1413        public float getSurfaceShininess() {
1414            return J3DGeomGroup.getDefaultRenderingPrefs().getSurfaceShininess();
1415        }
1416    }
1417
1418}