Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene;
027
028import java.util.ArrayList;
029import java.util.List;
030
031import com.sun.javafx.geom.BaseBounds;
032import com.sun.javafx.geom.PickRay;
033import com.sun.javafx.geom.transform.BaseTransform;
034import com.sun.javafx.jmx.MXNodeAlgorithm;
035import com.sun.javafx.jmx.MXNodeAlgorithmContext;
036import com.sun.javafx.scene.CssFlags;
037import com.sun.javafx.scene.DirtyBits;
038import com.sun.javafx.scene.SubSceneHelper;
039import com.sun.javafx.scene.input.PickResultChooser;
040import com.sun.javafx.scene.traversal.TraversalEngine;
041import com.sun.javafx.sg.PGLightBase;
042import com.sun.javafx.sg.PGNode;
043import com.sun.javafx.sg.PGSubScene;
044import com.sun.javafx.tk.Toolkit;
045import javafx.application.ConditionalFeature;
046import javafx.application.Platform;
047import javafx.beans.property.DoubleProperty;
048import javafx.beans.property.DoublePropertyBase;
049import javafx.beans.property.ObjectProperty;
050import javafx.beans.property.ObjectPropertyBase;
051import javafx.geometry.NodeOrientation;
052import javafx.geometry.Point3D;
053import javafx.scene.input.PickResult;
054import javafx.scene.paint.Paint;
055import sun.util.logging.PlatformLogger;
056
057/**
058 * The {@code SubScene} class is the container for content in a scene graph.
059 *
060 * @since JavaFX 8
061 */
062public class SubScene extends Node {
063
064    /**
065     * Creates a SubScene for a specific root Node with a specific size.
066     *
067     * @param root The root node of the scene graph
068     * @param width The width of the scene
069     * @param height The height of the scene
070     *
071     * @throws IllegalStateException if this constructor is called on a thread
072     * other than the JavaFX Application Thread.
073     * @throws NullPointerException if root is null
074     */
075    public SubScene(Parent root, double width, double height) {
076        setRoot(root);
077        setWidth(width);
078        setHeight(height);
079    }
080
081    /**
082     * Constructs a SubScene consisting of a root, with a dimension of width and
083     * height, specifies whether a depth buffer is created for this scene and
084     * specifies whether scene anti-aliasing is requested.
085     *
086     * @param root The root node of the scene graph
087     * @param width The width of the scene
088     * @param height The height of the scene
089     * @param depthBuffer The depth buffer flag
090     * @param antiAliasing The sub-scene anti-aliasing flag
091     * <p>
092     * The depthBuffer and antiAliasing flags are conditional feature and the default
093     * value for both are false. See
094     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
095     * for more information.
096     *
097     * @throws IllegalStateException if this constructor is called on a thread
098     * other than the JavaFX Application Thread.
099     * @throws NullPointerException if root is null
100     *
101     * @see javafx.scene.Node#setDepthTest(DepthTest)
102     */
103    public SubScene(Parent root, double width, double height,
104            boolean depthBuffer, boolean antiAliasing) {
105        this(root, width, height);
106        this.depthBuffer = depthBuffer;
107
108        // NOTE: this block will be removed once implement anti-aliasing
109        if (antiAliasing) {
110            String logname = SubScene.class.getName();
111            PlatformLogger.getLogger(logname).warning("3D anti-aliasing is "
112                    + "not supported yet.");
113        }
114
115        if ((depthBuffer || antiAliasing)
116                && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
117            String logname = SubScene.class.getName();
118            PlatformLogger.getLogger(logname).warning("System can't support "
119                    + "ConditionalFeature.SCENE3D");
120            // TODO: 3D - ignore depthBuffer and antiAliasing at rendering time
121        }
122        //TODO: 3D - verify that depthBuffer is working correctly
123        //TODO: 3D - complete antiAliasing
124    }
125
126    /**
127     * Return true if this {@code SubScene} is anti-aliased otherwise false.
128     */
129    public boolean isAntiAliasing() {
130        throw new UnsupportedOperationException("Unsupported --- *** isAntiAliasing method ***");
131    }
132
133    private boolean depthBuffer = false;
134
135    boolean isDepthBufferInteral() {
136        if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
137            return false;
138        }
139        return depthBuffer;
140    }
141    /**
142     * Defines the root {@code Node} of the SubScene scene graph.
143     * If a {@code Group} is used as the root, the
144     * contents of the scene graph will be clipped by the SubScene's width and height.
145     *
146     * SubScene doesn't accept null root.
147     *
148     */
149    private ObjectProperty<Parent> root;
150
151    public final void setRoot(Parent value) {
152        rootProperty().set(value);
153    }
154
155    public final Parent getRoot() {
156        return root == null ? null : root.get();
157    }
158
159    public final ObjectProperty<Parent> rootProperty() {
160        if (root == null) {
161            root = new ObjectPropertyBase<Parent>() {
162                private Parent oldRoot;
163
164                private void forceUnbind() {
165                    System.err.println("Unbinding illegal root.");
166                    unbind();
167                }
168
169                @Override
170                protected void invalidated() {
171                    Parent _value = get();
172
173                    if (_value == null) {
174                        if (isBound()) { forceUnbind(); }
175                        throw new NullPointerException("Scene's root cannot be null");
176                    }
177                    if (_value.getParent() != null) {
178                        if (isBound()) { forceUnbind(); }
179                        throw new IllegalArgumentException(_value +
180                                "is already inside a scene-graph and cannot be set as root");
181                    }
182                    if (_value.getClipParent() != null) {
183                        if (isBound()) forceUnbind();
184                        throw new IllegalArgumentException(_value +
185                                "is set as a clip on another node, so cannot be set as root");
186                    }
187                    if ((_value.getScene() != null &&
188                            _value.getScene().getRoot() == _value) ||
189                            (_value.getSubScene() != null &&
190                            _value.getSubScene().getRoot() == _value &&
191                            _value.getSubScene() != SubScene.this))
192                    {
193                        if (isBound()) { forceUnbind(); }
194                        throw new IllegalArgumentException(_value +
195                                "is already set as root of another scene or subScene");
196                    }
197
198                    // disabled and isTreeVisible properties are inherrited
199                    _value.setTreeVisible(impl_isTreeVisible());
200                    _value.setDisabled(isDisabled());
201
202                    if (oldRoot != null) {
203                        oldRoot.setScenes(null, null);
204                        oldRoot.setImpl_traversalEngine(null);
205                    }
206                    oldRoot = _value;
207                    if (_value.getImpl_traversalEngine() == null) {
208                        _value.setImpl_traversalEngine(new TraversalEngine(_value, true));
209                    }
210                    _value.getStyleClass().add(0, "root");
211                    _value.setScenes(getScene(), SubScene.this);
212                    markDirty(SubSceneDirtyBits.ROOT_SG_DIRTY);
213                    _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable
214                    _value.requestLayout();
215                }
216
217                @Override
218                public Object getBean() {
219                    return SubScene.this;
220                }
221
222                @Override
223                public String getName() {
224                    return "root";
225                }
226            };
227        }
228        return root;
229    }
230
231    /**
232     * Specifies the type of camera use for rendering this {@code SubScene}.
233     * If {@code camera} is null, a parallel camera is used for rendering.
234     * It is illegal to set a camera that belongs to other {@code Scene}
235     * or {@code SubScene}.
236     * <p>
237     * Note: this is a conditional feature. See
238     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
239     * for more information.
240     *
241     * @defaultValue null
242     */
243    private ObjectProperty<Camera> camera;
244
245    public final void setCamera(Camera value) {
246        cameraProperty().set(value);
247    }
248
249    public final Camera getCamera() {
250        return camera == null ? null : camera.get();
251    }
252
253    public final ObjectProperty<Camera> cameraProperty() {
254        if (camera == null) {
255            camera = new ObjectPropertyBase<Camera>() {
256                Camera oldCamera = null;
257
258                @Override
259                protected void invalidated() {
260                    Camera _value = get();
261                    if (_value != null) {
262                        if (_value instanceof PerspectiveCamera
263                                && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
264                            String logname = SubScene.class.getName();
265                            PlatformLogger.getLogger(logname).warning("System can't support "
266                                    + "ConditionalFeature.SCENE3D");
267                        }
268                        // Illegal value if it belongs to any scene or other subscene
269                        if ((_value.getScene() != null || _value.getSubScene() != null)
270                                && (_value.getScene() != getScene() || _value.getSubScene() != SubScene.this)) {
271                            throw new IllegalArgumentException(_value
272                                    + "is already part of other scene or subscene");
273                        }
274                        // throws exception if the camera already has a different owner
275                        _value.setOwnerSubScene(SubScene.this);
276                        _value.setViewWidth(getWidth());
277                        _value.setViewHeight(getHeight());
278                    }
279                    markDirty(SubSceneDirtyBits.CAMERA_DIRTY);
280                    if (oldCamera != null && oldCamera != _value) {
281                        oldCamera.setOwnerSubScene(null);
282                    }
283                    oldCamera = _value;
284                }
285
286                @Override
287                public Object getBean() {
288                    return SubScene.this;
289                }
290
291                @Override
292                public String getName() {
293                    return "camera";
294                }
295            };
296        }
297        return camera;
298    }
299
300    private Camera defaultCamera;
301
302    Camera getEffectiveCamera() {
303        final Camera cam = getCamera();
304        if (cam == null
305                || (cam instanceof PerspectiveCamera
306                && !Platform.isSupported(ConditionalFeature.SCENE3D))) {
307            if (defaultCamera == null) {
308                defaultCamera = new ParallelCamera();
309                defaultCamera.setOwnerSubScene(this);
310                defaultCamera.setViewWidth(getWidth());
311                defaultCamera.setViewHeight(getHeight());
312            }
313            return defaultCamera;
314        }
315
316        return cam;
317    }
318
319    // Used by the camera
320    final void markContentDirty() {
321        markDirty(SubSceneDirtyBits.CONTENT_DIRTY);
322    }
323
324    /**
325     * Defines the width of this {@code SubScene}
326     *
327     * @defaultvalue 0.0
328     */
329    private DoubleProperty width;
330
331    public final void setWidth(double value) {
332        widthProperty().set(value);
333    }
334
335    public final double getWidth() {
336        return width == null ? 0.0 : width.get();
337    }
338
339    public final DoubleProperty widthProperty() {
340        if (width == null) {
341            width = new DoublePropertyBase() {
342
343                @Override
344                public void invalidated() {
345                    final Parent _root = getRoot();
346                    //TODO - use a better method to update mirroring
347                    if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
348                        _root.impl_transformsChanged();
349                    }
350                    if (_root.isResizable()) {
351                        _root.resize(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight());
352                    }
353                    markDirty(SubSceneDirtyBits.SIZE_DIRTY);
354                    SubScene.this.impl_geomChanged();
355
356                    getEffectiveCamera().setViewWidth(get());
357                }
358
359                @Override
360                public Object getBean() {
361                    return SubScene.this;
362                }
363
364                @Override
365                public String getName() {
366                    return "width";
367                }
368            };
369        }
370        return width;
371    }
372
373    /**
374     * Defines the height of this {@code SubScene}
375     *
376     * @defaultvalue 0.0
377     */
378    private DoubleProperty height;
379
380    public final void setHeight(double value) {
381        heightProperty().set(value);
382    }
383
384    public final double getHeight() {
385        return height == null ? 0.0 : height.get();
386    }
387
388    public final DoubleProperty heightProperty() {
389        if (height == null) {
390            height = new DoublePropertyBase() {
391
392                @Override
393                public void invalidated() {
394                    final Parent _root = getRoot();
395                    if (_root.isResizable()) {
396                        _root.resize(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY());
397                    }
398                    markDirty(SubSceneDirtyBits.SIZE_DIRTY);
399                    SubScene.this.impl_geomChanged();
400
401                    getEffectiveCamera().setViewHeight(get());
402                }
403
404                @Override
405                public Object getBean() {
406                    return SubScene.this;
407                }
408
409                @Override
410                public String getName() {
411                    return "height";
412                }
413            };
414        }
415        return height;
416    }
417
418    /**
419     * Defines the background fill of this {@code SubScene}. Both a {@code null}
420     * value meaning paint no background and a {@link javafx.scene.paint.Paint}
421     * with transparency are supported. The default value is null.
422     *
423     * @defaultValue null
424     */
425    private ObjectProperty<Paint> fill;
426
427    public final void setFill(Paint value) {
428        fillProperty().set(value);
429    }
430
431    public final Paint getFill() {
432        return fill == null ? null : fill.get();
433    }
434
435    public final ObjectProperty<Paint> fillProperty() {
436        if (fill == null) {
437            fill = new ObjectPropertyBase<Paint>(null) {
438
439                @Override
440                protected void invalidated() {
441                    markDirty(SubSceneDirtyBits.FILL_DIRTY);
442                }
443
444                @Override
445                public Object getBean() {
446                    return SubScene.this;
447                }
448
449                @Override
450                public String getName() {
451                    return "fill";
452                }
453            };
454        }
455        return fill;
456    }
457
458    /**
459     * @treatAsPrivate implementation detail
460     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
461     */
462    @Deprecated @Override
463    public void impl_updatePG() {
464        super.impl_updatePG();
465
466        // TODO deal with clip node
467
468        dirtyNodes = dirtyLayout = false;
469        if (isDirty()) {
470            PGSubScene peer = (PGSubScene) impl_getPGNode();
471            final Camera cam = getEffectiveCamera();
472            boolean contentChanged = false;
473            if (cam.getSubScene() == null &&
474                    isDirty(SubSceneDirtyBits.CONTENT_DIRTY)) {
475                // When camera is not a part of the graph, then its
476                // owner(subscene) must take care of syncing it. And when a
477                // property on the camera changes it will mark subscenes
478                // CONTENT_DIRTY.
479                cam.impl_syncPGNode();
480            }
481            if (isDirty(SubSceneDirtyBits.FILL_DIRTY)) {
482                Object platformPaint = getFill() == null ? null :
483                        Toolkit.getPaintAccessor().getPlatformPaint(getFill());
484                peer.setFillPaint(platformPaint);
485                contentChanged = true;
486            }
487            peer.setDepthBuffer(isDepthBufferInteral());
488            if (isDirty(SubSceneDirtyBits.SIZE_DIRTY)) {
489                // Note change in size is a geom change and is handled by peer
490                peer.setWidth((float)getWidth());
491                peer.setHeight((float)getHeight());
492            }
493            if (isDirty(SubSceneDirtyBits.CAMERA_DIRTY)) {
494                peer.setCamera(cam.getPlatformCamera());
495                contentChanged = true;
496            }
497            if (isDirty(SubSceneDirtyBits.ROOT_SG_DIRTY)) {
498                peer.setRoot(getRoot().impl_getPGNode());
499                contentChanged = true;
500            }
501            contentChanged |= syncLights();
502            if (contentChanged || isDirty(SubSceneDirtyBits.CONTENT_DIRTY)) {
503                peer.markContentDirty();
504            }
505
506            clearDirtyBits();
507        }
508
509    }
510
511    @Override
512    void nodeResolvedOrientationChanged() {
513        getRoot().parentResolvedOrientationInvalidated();
514    }
515
516    /***********************************************************************
517     *                         CSS                                         *
518     **********************************************************************/
519    /**
520     * @treatAsPrivate implementation detail
521     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
522     */
523    @Deprecated @Override
524    protected void impl_processCSS() {
525        // Nothing to do...
526        if (cssFlag == CssFlags.CLEAN) { return; }
527
528        if (getRoot().cssFlag == CssFlags.CLEAN) {
529            getRoot().cssFlag = cssFlag;
530        }
531        super.impl_processCSS();
532        getRoot().processCSS();
533    }
534
535    @Override
536    void processCSS() {
537        Parent root = getRoot();
538        if (root.impl_isDirty(DirtyBits.NODE_CSS)) {
539            root.impl_clearDirty(DirtyBits.NODE_CSS);
540            if (cssFlag == CssFlags.CLEAN) { cssFlag = CssFlags.UPDATE; }
541        }
542        super.processCSS();
543    }
544
545    @Override void updateBounds() {
546        super.updateBounds();
547        getRoot().updateBounds();
548    }
549
550    /**
551     * @treatAsPrivate implementation detail
552     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
553     */
554    @Deprecated    @Override
555    protected PGNode impl_createPGNode() {
556        return Toolkit.getToolkit().createPGSubScene();
557    }
558
559    /**
560     * @treatAsPrivate implementation detail
561     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
562     */
563    @Deprecated    @Override
564    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
565        int w = (int)Math.ceil(width.get());
566        int h = (int)Math.ceil(height.get());
567        bounds = bounds.deriveWithNewBounds(0.0f, 0.0f, 0.0f,
568                                            w, h, 0.0f);
569        bounds = tx.transform(bounds, bounds);
570        return bounds;
571    }
572
573    /***********************************************************************
574     *                         Dirty Bits                                  *
575     **********************************************************************/
576    boolean dirtyLayout = false;
577    void setDirtyLayout(Parent p) {
578        if (!dirtyLayout && p != null && p.getSubScene() == this &&
579                this.getScene() != null) {
580            dirtyLayout = true;
581            markDirty(SubSceneDirtyBits.CONTENT_DIRTY);
582        }
583    }
584
585    private boolean dirtyNodes = false;
586    void setDirty(Node n) {
587        if (!dirtyNodes && n != null && n.getSubScene() == this &&
588                this.getScene() != null) {
589            dirtyNodes = true;
590            markDirty(SubSceneDirtyBits.CONTENT_DIRTY);
591        }
592    }
593
594    private enum SubSceneDirtyBits {
595        SIZE_DIRTY,
596        FILL_DIRTY,
597        ROOT_SG_DIRTY,
598        CAMERA_DIRTY,
599        LIGHTS_DIRTY,
600        CONTENT_DIRTY;
601
602        private int mask;
603
604        private SubSceneDirtyBits() { mask = 1 << ordinal(); }
605
606        public final int getMask() { return mask; }
607    }
608
609    private int dirtyBits = ~0;
610
611    private void clearDirtyBits() { dirtyBits = 0; }
612
613    private boolean isDirty() { return dirtyBits != 0; }
614
615    // Should not be called directly, instead use markDirty
616    private void setDirty(SubSceneDirtyBits dirtyBit) {
617        this.dirtyBits |= dirtyBit.getMask();
618    }
619
620    private boolean isDirty(SubSceneDirtyBits dirtyBit) {
621        return ((this.dirtyBits & dirtyBit.getMask()) != 0);
622    }
623
624    private void markDirty(SubSceneDirtyBits dirtyBit) {
625        if (!isDirty()) {
626            // Force SubScene to redraw
627            impl_markDirty(DirtyBits.NODE_CONTENTS);
628        }
629        setDirty(dirtyBit);
630    }
631
632    /***********************************************************************
633     *                           Picking                                   *
634     **********************************************************************/
635
636    /**
637     * @treatAsPrivate implementation detail
638     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
639     */
640    @Deprecated    @Override
641    protected boolean impl_computeContains(double localX, double localY) {
642        if (subSceneComputeContains(localX, localY)) {
643            return true;
644        } else {
645            return getRoot().impl_computeContains(localX, localY);
646        }
647    }
648
649    /**
650     * Determines whether subScene contains the given point.
651     * It does not consider the contained nodes, only subScene's
652     * size and fills.
653     * @param localX horizontal coordinate in the local space of the subScene node
654     * @param localY vertical coordinate in the local space of the subScene node
655     * @return true if the point is inside subScene's area covered by its fill
656     */
657    private boolean subSceneComputeContains(double localX, double localY) {
658        if (localX < 0 || localY < 0 || localX > getWidth() || localY > getHeight()) {
659            return false;
660        }
661        return getFill() != null;
662    }
663
664    /*
665     * Generates a pick ray based on local coordinates and camera. Then finds a
666     * top-most child node that intersects the pick ray.
667     */
668    private PickResult pickRootSG(double localX, double localY) {
669        final double viewWidth = getWidth();
670        final double viewHeight = getHeight();
671        if (localX < 0 || localY < 0 || localX > viewWidth || localY > viewHeight) {
672            return null;
673        }
674        final PickResultChooser result = new PickResultChooser();
675        final PickRay pickRay = getEffectiveCamera().computePickRay(localX, localY, new PickRay());
676        getRoot().impl_pickNode(pickRay, result);
677        return result.toPickResult();
678    }
679
680    /**
681     * Finds a top-most child node that contains the given local coordinates.
682     *
683     * Returns the picked node, null if no such node was found.
684     * @treatAsPrivate implementation detail
685     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
686     */
687    @Deprecated @Override
688    protected void impl_pickNodeLocal(PickRay localPickRay, PickResultChooser result) {
689        final double boundsDistance = impl_intersectsBounds(localPickRay);
690        if (!Double.isNaN(boundsDistance) && result.isCloser(boundsDistance)) {
691            final Point3D intersectPt = PickResultChooser.computePoint(
692                    localPickRay, boundsDistance);
693            final PickResult subSceneResult =
694                    pickRootSG(intersectPt.getX(), intersectPt.getY());
695            if (subSceneResult != null) {
696                result.offerSubScenePickResult(this, subSceneResult, boundsDistance);
697            } else if (isPickOnBounds() ||
698                    subSceneComputeContains(intersectPt.getX(), intersectPt.getY())) {
699                result.offer(this, boundsDistance, intersectPt);
700            }
701        }
702    }
703
704    /**
705     * @treatAsPrivate implementation detail
706     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
707     */
708    @Deprecated    @Override
709    public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
710        throw new UnsupportedOperationException("Not supported yet.");
711    }
712
713
714    private List<LightBase> lights = new ArrayList<>();
715
716    // @param light must not be null
717    final void addLight(LightBase light) {
718        if (!lights.contains(light)) {
719            markDirty(SubSceneDirtyBits.LIGHTS_DIRTY);
720            lights.add(light);
721        }
722    }
723
724    final void removeLight(LightBase light) {
725        if (lights.remove(light)) {
726            markDirty(SubSceneDirtyBits.LIGHTS_DIRTY);
727        }
728    }
729
730    /**
731     * PG Light synchronizer.
732     */
733    private boolean syncLights() {
734        boolean lightOwnerChanged = false;
735        if (!isDirty(SubSceneDirtyBits.LIGHTS_DIRTY)) {
736            return lightOwnerChanged;
737        }
738        PGSubScene pgSubScene = (PGSubScene) impl_getPGNode();
739        Object peerLights[] = pgSubScene.getLights();
740        if (!lights.isEmpty() || (peerLights != null)) {
741            if (lights.isEmpty()) {
742                pgSubScene.setLights(null);
743            } else {
744                if (peerLights == null || peerLights.length < lights.size()) {
745                    peerLights = new PGLightBase[lights.size()];
746                }
747                int i = 0;
748                for (; i < lights.size(); i++) {
749                    peerLights[i] = lights.get(i).impl_getPGNode();
750                }
751                // Clear the rest of the list
752                while (i < peerLights.length && peerLights[i] != null) {
753                    peerLights[i++] = null;
754                }
755                pgSubScene.setLights(peerLights);
756            }
757            lightOwnerChanged = true;
758        }
759        return lightOwnerChanged;
760    }
761
762    static {
763        // This is used by classes in different packages to get access to
764        // private and package private methods.
765        SubSceneHelper.setSubSceneAccessor(new SubSceneHelper.SubSceneAccessor() {
766
767            @Override
768            public boolean isDepthBuffer(SubScene subScene) {
769                return subScene.isDepthBufferInteral();
770            };
771
772            @Override
773            public Camera getEffectiveCamera(SubScene subScene) {
774                return subScene.getEffectiveCamera();
775            }
776        });
777    }
778}