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