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 > 0). 145 * @param height The height of the canvas (must be > 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}