1 /* 2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene; 27 28 import javafx.application.ConditionalFeature; 29 import javafx.application.Platform; 30 import javafx.beans.NamedArg; 31 import javafx.beans.property.DoubleProperty; 32 import javafx.beans.property.DoublePropertyBase; 33 import javafx.beans.property.ObjectProperty; 34 import javafx.beans.property.ObjectPropertyBase; 35 import javafx.beans.value.WritableValue; 36 import javafx.geometry.NodeOrientation; 37 import javafx.geometry.Point3D; 38 import javafx.scene.input.PickResult; 39 import javafx.scene.paint.Paint; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 import com.sun.javafx.geom.BaseBounds; 45 import com.sun.javafx.geom.PickRay; 46 import com.sun.javafx.geom.transform.BaseTransform; 47 import com.sun.javafx.jmx.MXNodeAlgorithm; 48 import com.sun.javafx.jmx.MXNodeAlgorithmContext; 49 import com.sun.javafx.scene.CssFlags; 50 import com.sun.javafx.scene.DirtyBits; 51 import com.sun.javafx.scene.SubSceneHelper; 52 import com.sun.javafx.scene.input.PickResultChooser; 53 import com.sun.javafx.scene.traversal.TraversalEngine; 54 import com.sun.javafx.sg.prism.NGCamera; 55 import com.sun.javafx.sg.prism.NGLightBase; 56 import com.sun.javafx.sg.prism.NGNode; 57 import com.sun.javafx.sg.prism.NGSubScene; 58 import com.sun.javafx.tk.Toolkit; 59 60 import sun.util.logging.PlatformLogger; 61 62 /** 63 * The {@code SubScene} class is the container for content in a scene graph. 64 * 65 * <p> 66 * A default headlight will be added to a scene that contains {@code Shape3D} 67 * but without light source. This light source is a {@code Color.WHITE} 68 * {@code PointLight} placed at the camera position. 69 * 70 * @since JavaFX 8.0 71 */ 72 public class SubScene extends Node { 73 74 /** 75 * Creates a SubScene for a specific root Node with a specific size. 76 * 77 * @param root The root node of the scene graph 78 * @param width The width of the scene 79 * @param height The height of the scene 80 * 81 * @throws IllegalStateException if this constructor is called on a thread 82 * other than the JavaFX Application Thread. 83 * @throws NullPointerException if root is null 84 */ 85 public SubScene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height) { 86 this(root, width, height, false, SceneAntialiasing.DISABLED); 87 } 88 89 /** 90 * Constructs a SubScene consisting of a root, with a dimension of width and 91 * height, specifies whether a depth buffer is created for this scene and 92 * specifies whether scene anti-aliasing is requested. 93 * 94 * @param root The root node of the scene graph 95 * @param width The width of the scene 96 * @param height The height of the scene 97 * @param depthBuffer The depth buffer flag 98 * @param antiAliasing The sub-scene anti-aliasing attribute. A value of 99 * {@code null} is treated as DISABLED. 100 * <p> 101 * The depthBuffer and antiAliasing flags are conditional features. With the 102 * respective default values of: false and {@code SceneAntialiasing.DISABLED}. 103 * See {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 104 * for more information. 105 * 106 * @throws IllegalStateException if this constructor is called on a thread 107 * other than the JavaFX Application Thread. 108 * @throws NullPointerException if root is null 109 * 110 * @see javafx.scene.Node#setDepthTest(DepthTest) 111 */ 112 public SubScene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height, 113 @NamedArg("depthBuffer") boolean depthBuffer, @NamedArg("antiAliasing") SceneAntialiasing antiAliasing) 114 { 115 this.depthBuffer = depthBuffer; 116 this.antiAliasing = antiAliasing; 117 boolean isAntiAliasing = !(antiAliasing == null || antiAliasing == SceneAntialiasing.DISABLED); 118 setRoot(root); 119 setWidth(width); 120 setHeight(height); 121 122 if ((depthBuffer || isAntiAliasing) && !is3DSupported) { 123 String logname = SubScene.class.getName(); 124 PlatformLogger.getLogger(logname).warning("System can't support " 125 + "ConditionalFeature.SCENE3D"); 126 } 127 if (isAntiAliasing && !Toolkit.getToolkit().isAntiAliasingSupported()) { 128 String logname = SubScene.class.getName(); 129 PlatformLogger.getLogger(logname).warning("System can't support " 130 + "antiAliasing"); 131 } 132 } 133 134 private static boolean is3DSupported = 135 Platform.isSupported(ConditionalFeature.SCENE3D); 136 137 private final SceneAntialiasing antiAliasing; 138 139 /** 140 * Return the defined {@code SceneAntialiasing} for this {@code SubScene}. 141 * <p> 142 * Note: this is a conditional feature. See 143 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 144 * and {@link javafx.scene.SceneAntialiasing SceneAntialiasing} 145 * for more information. 146 * @since JavaFX 8.0 147 */ 148 public final SceneAntialiasing getAntiAliasing() { 149 return antiAliasing; 150 } 151 152 private final boolean depthBuffer; 153 154 /** 155 * Retrieves the depth buffer attribute for this SubScene. 156 * @return the depth buffer attribute. 157 */ 158 public final boolean isDepthBuffer() { 159 return depthBuffer; 160 } 161 162 private boolean isDepthBufferInternal() { 163 return is3DSupported ? depthBuffer : false; 164 } 165 166 /** 167 * Defines the root {@code Node} of the SubScene scene graph. 168 * If a {@code Group} is used as the root, the 169 * contents of the scene graph will be clipped by the SubScene's width and height. 170 * 171 * SubScene doesn't accept null root. 172 * 173 */ 174 private ObjectProperty<Parent> root; 175 176 public final void setRoot(Parent value) { 177 rootProperty().set(value); 178 } 179 180 public final Parent getRoot() { 181 return root == null ? null : root.get(); 182 } 183 184 public final ObjectProperty<Parent> rootProperty() { 185 if (root == null) { 186 root = new ObjectPropertyBase<Parent>() { 187 private Parent oldRoot; 188 189 private void forceUnbind() { 190 System.err.println("Unbinding illegal root."); 191 unbind(); 192 } 193 194 @Override 195 protected void invalidated() { 196 Parent _value = get(); 197 198 if (_value == null) { 199 if (isBound()) { forceUnbind(); } 200 throw new NullPointerException("Scene's root cannot be null"); 201 } 202 if (_value.getParent() != null) { 203 if (isBound()) { forceUnbind(); } 204 throw new IllegalArgumentException(_value + 205 "is already inside a scene-graph and cannot be set as root"); 206 } 207 if (_value.getClipParent() != null) { 208 if (isBound()) forceUnbind(); 209 throw new IllegalArgumentException(_value + 210 "is set as a clip on another node, so cannot be set as root"); 211 } 212 if ((_value.getScene() != null && 213 _value.getScene().getRoot() == _value) || 214 (_value.getSubScene() != null && 215 _value.getSubScene().getRoot() == _value && 216 _value.getSubScene() != SubScene.this)) 217 { 218 if (isBound()) { forceUnbind(); } 219 throw new IllegalArgumentException(_value + 220 "is already set as root of another scene or subScene"); 221 } 222 223 // disabled and isTreeVisible properties are inherrited 224 _value.setTreeVisible(impl_isTreeVisible()); 225 _value.setDisabled(isDisabled()); 226 227 if (oldRoot != null) { 228 oldRoot.setScenes(null, null); 229 oldRoot.setImpl_traversalEngine(null); 230 } 231 oldRoot = _value; 232 if (_value.getImpl_traversalEngine() == null) { 233 _value.setImpl_traversalEngine(new TraversalEngine(_value, true)); 234 } 235 _value.getStyleClass().add(0, "root"); 236 _value.setScenes(getScene(), SubScene.this); 237 markDirty(SubSceneDirtyBits.ROOT_SG_DIRTY); 238 _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable 239 _value.requestLayout(); 240 } 241 242 @Override 243 public Object getBean() { 244 return SubScene.this; 245 } 246 247 @Override 248 public String getName() { 249 return "root"; 250 } 251 }; 252 } 253 return root; 254 } 255 256 /** 257 * Specifies the type of camera use for rendering this {@code SubScene}. 258 * If {@code camera} is null, a parallel camera is used for rendering. 259 * It is illegal to set a camera that belongs to other {@code Scene} 260 * or {@code SubScene}. 261 * <p> 262 * Note: this is a conditional feature. See 263 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 264 * for more information. 265 * 266 * @defaultValue null 267 */ 268 private ObjectProperty<Camera> camera; 269 270 public final void setCamera(Camera value) { 271 cameraProperty().set(value); 272 } 273 274 public final Camera getCamera() { 275 return camera == null ? null : camera.get(); 276 } 277 278 public final ObjectProperty<Camera> cameraProperty() { 279 if (camera == null) { 280 camera = new ObjectPropertyBase<Camera>() { 281 Camera oldCamera = null; 282 283 @Override 284 protected void invalidated() { 285 Camera _value = get(); 286 if (_value != null) { 287 if (_value instanceof PerspectiveCamera 288 && !SubScene.is3DSupported) { 289 String logname = SubScene.class.getName(); 290 PlatformLogger.getLogger(logname).warning("System can't support " 291 + "ConditionalFeature.SCENE3D"); 292 } 293 // Illegal value if it belongs to any scene or other subscene 294 if ((_value.getScene() != null || _value.getSubScene() != null) 295 && (_value.getScene() != getScene() || _value.getSubScene() != SubScene.this)) { 296 throw new IllegalArgumentException(_value 297 + "is already part of other scene or subscene"); 298 } 299 // throws exception if the camera already has a different owner 300 _value.setOwnerSubScene(SubScene.this); 301 _value.setViewWidth(getWidth()); 302 _value.setViewHeight(getHeight()); 303 } 304 markDirty(SubSceneDirtyBits.CAMERA_DIRTY); 305 if (oldCamera != null && oldCamera != _value) { 306 oldCamera.setOwnerSubScene(null); 307 } 308 oldCamera = _value; 309 } 310 311 @Override 312 public Object getBean() { 313 return SubScene.this; 314 } 315 316 @Override 317 public String getName() { 318 return "camera"; 319 } 320 }; 321 } 322 return camera; 323 } 324 325 private Camera defaultCamera; 326 327 Camera getEffectiveCamera() { 328 final Camera cam = getCamera(); 329 if (cam == null 330 || (cam instanceof PerspectiveCamera && !is3DSupported)) { 331 if (defaultCamera == null) { 332 defaultCamera = new ParallelCamera(); 333 defaultCamera.setOwnerSubScene(this); 334 defaultCamera.setViewWidth(getWidth()); 335 defaultCamera.setViewHeight(getHeight()); 336 } 337 return defaultCamera; 338 } 339 340 return cam; 341 } 342 343 // Used by the camera 344 final void markContentDirty() { 345 markDirty(SubSceneDirtyBits.CONTENT_DIRTY); 346 } 347 348 /** 349 * Defines the width of this {@code SubScene} 350 * 351 * @defaultvalue 0.0 352 */ 353 private DoubleProperty width; 354 355 public final void setWidth(double value) { 356 widthProperty().set(value); 357 } 358 359 public final double getWidth() { 360 return width == null ? 0.0 : width.get(); 361 } 362 363 public final DoubleProperty widthProperty() { 364 if (width == null) { 365 width = new DoublePropertyBase() { 366 367 @Override 368 public void invalidated() { 369 final Parent _root = getRoot(); 370 //TODO - use a better method to update mirroring 371 if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 372 _root.impl_transformsChanged(); 373 } 374 if (_root.isResizable()) { 375 _root.resize(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight()); 376 } 377 markDirty(SubSceneDirtyBits.SIZE_DIRTY); 378 SubScene.this.impl_geomChanged(); 379 380 getEffectiveCamera().setViewWidth(get()); 381 } 382 383 @Override 384 public Object getBean() { 385 return SubScene.this; 386 } 387 388 @Override 389 public String getName() { 390 return "width"; 391 } 392 }; 393 } 394 return width; 395 } 396 397 /** 398 * Defines the height of this {@code SubScene} 399 * 400 * @defaultvalue 0.0 401 */ 402 private DoubleProperty height; 403 404 public final void setHeight(double value) { 405 heightProperty().set(value); 406 } 407 408 public final double getHeight() { 409 return height == null ? 0.0 : height.get(); 410 } 411 412 public final DoubleProperty heightProperty() { 413 if (height == null) { 414 height = new DoublePropertyBase() { 415 416 @Override 417 public void invalidated() { 418 final Parent _root = getRoot(); 419 if (_root.isResizable()) { 420 _root.resize(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY()); 421 } 422 markDirty(SubSceneDirtyBits.SIZE_DIRTY); 423 SubScene.this.impl_geomChanged(); 424 425 getEffectiveCamera().setViewHeight(get()); 426 } 427 428 @Override 429 public Object getBean() { 430 return SubScene.this; 431 } 432 433 @Override 434 public String getName() { 435 return "height"; 436 } 437 }; 438 } 439 return height; 440 } 441 442 /** 443 * Defines the background fill of this {@code SubScene}. Both a {@code null} 444 * value meaning paint no background and a {@link javafx.scene.paint.Paint} 445 * with transparency are supported. The default value is null. 446 * 447 * @defaultValue null 448 */ 449 private ObjectProperty<Paint> fill; 450 451 public final void setFill(Paint value) { 452 fillProperty().set(value); 453 } 454 455 public final Paint getFill() { 456 return fill == null ? null : fill.get(); 457 } 458 459 public final ObjectProperty<Paint> fillProperty() { 460 if (fill == null) { 461 fill = new ObjectPropertyBase<Paint>(null) { 462 463 @Override 464 protected void invalidated() { 465 markDirty(SubSceneDirtyBits.FILL_DIRTY); 466 } 467 468 @Override 469 public Object getBean() { 470 return SubScene.this; 471 } 472 473 @Override 474 public String getName() { 475 return "fill"; 476 } 477 }; 478 } 479 return fill; 480 } 481 482 /** 483 * @treatAsPrivate implementation detail 484 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 485 */ 486 @Deprecated @Override 487 public void impl_updatePeer() { 488 super.impl_updatePeer(); 489 490 // TODO deal with clip node 491 492 dirtyNodes = false; 493 if (isDirty()) { 494 NGSubScene peer = impl_getPeer(); 495 final Camera cam = getEffectiveCamera(); 496 boolean contentChanged = false; 497 if (cam.getSubScene() == null && 498 isDirty(SubSceneDirtyBits.CONTENT_DIRTY)) { 499 // When camera is not a part of the graph, then its 500 // owner(subscene) must take care of syncing it. And when a 501 // property on the camera changes it will mark subscenes 502 // CONTENT_DIRTY. 503 cam.impl_syncPeer(); 504 } 505 if (isDirty(SubSceneDirtyBits.FILL_DIRTY)) { 506 Object platformPaint = getFill() == null ? null : 507 Toolkit.getPaintAccessor().getPlatformPaint(getFill()); 508 peer.setFillPaint(platformPaint); 509 contentChanged = true; 510 } 511 if (isDirty(SubSceneDirtyBits.SIZE_DIRTY)) { 512 // Note change in size is a geom change and is handled by peer 513 peer.setWidth((float)getWidth()); 514 peer.setHeight((float)getHeight()); 515 } 516 if (isDirty(SubSceneDirtyBits.CAMERA_DIRTY)) { 517 peer.setCamera((NGCamera) cam.impl_getPeer()); 518 contentChanged = true; 519 } 520 if (isDirty(SubSceneDirtyBits.ROOT_SG_DIRTY)) { 521 peer.setRoot(getRoot().impl_getPeer()); 522 contentChanged = true; 523 } 524 contentChanged |= syncLights(); 525 if (contentChanged || isDirty(SubSceneDirtyBits.CONTENT_DIRTY)) { 526 peer.markContentDirty(); 527 } 528 529 clearDirtyBits(); 530 } 531 532 } 533 534 @Override 535 void nodeResolvedOrientationChanged() { 536 getRoot().parentResolvedOrientationInvalidated(); 537 } 538 539 /*********************************************************************** 540 * CSS * 541 **********************************************************************/ 542 /** 543 * @treatAsPrivate implementation detail 544 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 545 */ 546 @Deprecated @Override 547 protected void impl_processCSS(WritableValue<Boolean> cacheHint) { 548 // Nothing to do... 549 if (cssFlag == CssFlags.CLEAN) { return; } 550 551 if (getRoot().cssFlag == CssFlags.CLEAN) { 552 getRoot().cssFlag = cssFlag; 553 } 554 super.impl_processCSS(cacheHint); 555 getRoot().processCSS(cacheHint); 556 } 557 558 @Override 559 void processCSS(WritableValue<Boolean> cacheHint) { 560 Parent root = getRoot(); 561 if (root.impl_isDirty(DirtyBits.NODE_CSS)) { 562 root.impl_clearDirty(DirtyBits.NODE_CSS); 563 if (cssFlag == CssFlags.CLEAN) { cssFlag = CssFlags.UPDATE; } 564 } 565 super.processCSS(cacheHint); 566 } 567 568 @Override void updateBounds() { 569 super.updateBounds(); 570 getRoot().updateBounds(); 571 } 572 573 /** 574 * @treatAsPrivate implementation detail 575 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 576 */ 577 @Deprecated @Override 578 protected NGNode impl_createPeer() { 579 if (!is3DSupported) { 580 return new NGSubScene(false, false); 581 } 582 boolean aa = !(antiAliasing == null || antiAliasing == SceneAntialiasing.DISABLED); 583 return new NGSubScene(depthBuffer, aa && Toolkit.getToolkit().isAntiAliasingSupported()); 584 } 585 586 /** 587 * @treatAsPrivate implementation detail 588 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 589 */ 590 @Deprecated @Override 591 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 592 int w = (int)Math.ceil(width.get()); 593 int h = (int)Math.ceil(height.get()); 594 bounds = bounds.deriveWithNewBounds(0.0f, 0.0f, 0.0f, 595 w, h, 0.0f); 596 bounds = tx.transform(bounds, bounds); 597 return bounds; 598 } 599 600 /*********************************************************************** 601 * Dirty Bits * 602 **********************************************************************/ 603 boolean dirtyLayout = false; 604 void setDirtyLayout(Parent p) { 605 if (!dirtyLayout && p != null && p.getSubScene() == this && 606 this.getScene() != null) { 607 dirtyLayout = true; 608 markDirtyLayoutBranch(); 609 markDirty(SubSceneDirtyBits.CONTENT_DIRTY); 610 } 611 } 612 613 private boolean dirtyNodes = false; 614 void setDirty(Node n) { 615 if (!dirtyNodes && n != null && n.getSubScene() == this && 616 this.getScene() != null) { 617 dirtyNodes = true; 618 markDirty(SubSceneDirtyBits.CONTENT_DIRTY); 619 } 620 } 621 622 void layoutPass() { 623 if (dirtyLayout) { 624 Parent r = getRoot(); 625 if (r != null) { 626 r.layout(); 627 } 628 dirtyLayout = false; 629 } 630 } 631 632 private enum SubSceneDirtyBits { 633 SIZE_DIRTY, 634 FILL_DIRTY, 635 ROOT_SG_DIRTY, 636 CAMERA_DIRTY, 637 LIGHTS_DIRTY, 638 CONTENT_DIRTY; 639 640 private int mask; 641 642 private SubSceneDirtyBits() { mask = 1 << ordinal(); } 643 644 public final int getMask() { return mask; } 645 } 646 647 private int dirtyBits = ~0; 648 649 private void clearDirtyBits() { dirtyBits = 0; } 650 651 private boolean isDirty() { return dirtyBits != 0; } 652 653 // Should not be called directly, instead use markDirty 654 private void setDirty(SubSceneDirtyBits dirtyBit) { 655 this.dirtyBits |= dirtyBit.getMask(); 656 } 657 658 private boolean isDirty(SubSceneDirtyBits dirtyBit) { 659 return ((this.dirtyBits & dirtyBit.getMask()) != 0); 660 } 661 662 private void markDirty(SubSceneDirtyBits dirtyBit) { 663 if (!isDirty()) { 664 // Force SubScene to redraw 665 impl_markDirty(DirtyBits.NODE_CONTENTS); 666 } 667 setDirty(dirtyBit); 668 } 669 670 /*********************************************************************** 671 * Picking * 672 **********************************************************************/ 673 674 /** 675 * @treatAsPrivate implementation detail 676 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 677 */ 678 @Deprecated @Override 679 protected boolean impl_computeContains(double localX, double localY) { 680 if (subSceneComputeContains(localX, localY)) { 681 return true; 682 } else { 683 return getRoot().impl_computeContains(localX, localY); 684 } 685 } 686 687 /** 688 * Determines whether subScene contains the given point. 689 * It does not consider the contained nodes, only subScene's 690 * size and fills. 691 * @param localX horizontal coordinate in the local space of the subScene node 692 * @param localY vertical coordinate in the local space of the subScene node 693 * @return true if the point is inside subScene's area covered by its fill 694 */ 695 private boolean subSceneComputeContains(double localX, double localY) { 696 if (localX < 0 || localY < 0 || localX > getWidth() || localY > getHeight()) { 697 return false; 698 } 699 return getFill() != null; 700 } 701 702 /* 703 * Generates a pick ray based on local coordinates and camera. Then finds a 704 * top-most child node that intersects the pick ray. 705 */ 706 private PickResult pickRootSG(double localX, double localY) { 707 final double viewWidth = getWidth(); 708 final double viewHeight = getHeight(); 709 if (localX < 0 || localY < 0 || localX > viewWidth || localY > viewHeight) { 710 return null; 711 } 712 final PickResultChooser result = new PickResultChooser(); 713 final PickRay pickRay = getEffectiveCamera().computePickRay(localX, localY, new PickRay()); 714 pickRay.getDirectionNoClone().normalize(); 715 getRoot().impl_pickNode(pickRay, result); 716 return result.toPickResult(); 717 } 718 719 /** 720 * Finds a top-most child node that contains the given local coordinates. 721 * 722 * Returns the picked node, null if no such node was found. 723 * @treatAsPrivate implementation detail 724 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 725 */ 726 @Deprecated @Override 727 protected void impl_pickNodeLocal(PickRay localPickRay, PickResultChooser result) { 728 final double boundsDistance = impl_intersectsBounds(localPickRay); 729 if (!Double.isNaN(boundsDistance) && result.isCloser(boundsDistance)) { 730 final Point3D intersectPt = PickResultChooser.computePoint( 731 localPickRay, boundsDistance); 732 final PickResult subSceneResult = 733 pickRootSG(intersectPt.getX(), intersectPt.getY()); 734 if (subSceneResult != null) { 735 result.offerSubScenePickResult(this, subSceneResult, boundsDistance); 736 } else if (isPickOnBounds() || 737 subSceneComputeContains(intersectPt.getX(), intersectPt.getY())) { 738 result.offer(this, boundsDistance, intersectPt); 739 } 740 } 741 } 742 743 /** 744 * @treatAsPrivate implementation detail 745 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 746 */ 747 @Deprecated @Override 748 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 749 throw new UnsupportedOperationException("Not supported yet."); 750 } 751 752 753 private List<LightBase> lights = new ArrayList<>(); 754 755 // @param light must not be null 756 final void addLight(LightBase light) { 757 if (!lights.contains(light)) { 758 markDirty(SubSceneDirtyBits.LIGHTS_DIRTY); 759 lights.add(light); 760 } 761 } 762 763 final void removeLight(LightBase light) { 764 if (lights.remove(light)) { 765 markDirty(SubSceneDirtyBits.LIGHTS_DIRTY); 766 } 767 } 768 769 /** 770 * PG Light synchronizer. 771 */ 772 private boolean syncLights() { 773 boolean lightOwnerChanged = false; 774 if (!isDirty(SubSceneDirtyBits.LIGHTS_DIRTY)) { 775 return lightOwnerChanged; 776 } 777 NGSubScene pgSubScene = impl_getPeer(); 778 NGLightBase peerLights[] = pgSubScene.getLights(); 779 if (!lights.isEmpty() || (peerLights != null)) { 780 if (lights.isEmpty()) { 781 pgSubScene.setLights(null); 782 } else { 783 if (peerLights == null || peerLights.length < lights.size()) { 784 peerLights = new NGLightBase[lights.size()]; 785 } 786 int i = 0; 787 for (; i < lights.size(); i++) { 788 peerLights[i] = lights.get(i).impl_getPeer(); 789 } 790 // Clear the rest of the list 791 while (i < peerLights.length && peerLights[i] != null) { 792 peerLights[i++] = null; 793 } 794 pgSubScene.setLights(peerLights); 795 } 796 lightOwnerChanged = true; 797 } 798 return lightOwnerChanged; 799 } 800 801 static { 802 // This is used by classes in different packages to get access to 803 // private and package private methods. 804 SubSceneHelper.setSubSceneAccessor(new SubSceneHelper.SubSceneAccessor() { 805 806 @Override 807 public boolean isDepthBuffer(SubScene subScene) { 808 return subScene.isDepthBufferInternal(); 809 }; 810 811 @Override 812 public Camera getEffectiveCamera(SubScene subScene) { 813 return subScene.getEffectiveCamera(); 814 } 815 }); 816 } 817 }