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}