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