1 /*
   2  * Copyright (c) 2013, 2016, 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 com.sun.javafx.collections.TrackableObservableList;
  29 import com.sun.javafx.geom.BaseBounds;
  30 import com.sun.javafx.geom.BoxBounds;
  31 import com.sun.javafx.geom.transform.Affine3D;
  32 import com.sun.javafx.geom.transform.BaseTransform;
  33 import com.sun.javafx.jmx.MXNodeAlgorithm;
  34 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  35 import com.sun.javafx.scene.DirtyBits;
  36 import com.sun.javafx.scene.LightBaseHelper;
  37 import com.sun.javafx.scene.NodeHelper;
  38 import com.sun.javafx.scene.transform.TransformHelper;
  39 import com.sun.javafx.sg.prism.NGLightBase;
  40 import com.sun.javafx.tk.Toolkit;
  41 import javafx.application.ConditionalFeature;
  42 import javafx.application.Platform;
  43 import javafx.beans.InvalidationListener;
  44 import javafx.beans.Observable;
  45 import javafx.beans.property.BooleanProperty;
  46 import javafx.beans.property.ObjectProperty;
  47 import javafx.beans.property.SimpleBooleanProperty;
  48 import javafx.beans.property.SimpleObjectProperty;
  49 import javafx.collections.ListChangeListener.Change;
  50 import javafx.collections.ObservableList;
  51 import javafx.scene.paint.Color;
  52 import javafx.scene.shape.Shape3D;
  53 import sun.util.logging.PlatformLogger;
  54 
  55 /**
  56  * The {@code LightBase} class provides definitions of common properties for
  57  * objects that represent a form of Light source.  These properties
  58  * include:
  59  * <ul>
  60  * <li>The color that defines color of the light source.
  61  * </ul>
  62  *
  63  * Note that this is a conditional feature. See
  64  * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
  65  * for more information.
  66  *
  67  * @since JavaFX 8.0
  68  */
  69 public abstract class LightBase extends Node {
  70     static {
  71          // This is used by classes in different packages to get access to
  72          // private and package private methods.
  73         LightBaseHelper.setLightBaseAccessor(new LightBaseHelper.LightBaseAccessor() {
  74             @Override
  75             public void doMarkDirty(Node node, DirtyBits dirtyBit) {
  76                 ((LightBase) node).doMarkDirty(dirtyBit);
  77             }
  78 
  79             @Override
  80             public void doUpdatePeer(Node node) {
  81                 ((LightBase) node).doUpdatePeer();
  82             }
  83         });
  84     }
  85 
  86     private Affine3D localToSceneTx = new Affine3D();
  87 
  88     {
  89         // To initialize the class helper at the begining each constructor of this class
  90         LightBaseHelper.initHelper(this);
  91     }
  92 
  93     /**
  94      * Creates a new instance of {@code LightBase} class with a default Color.WHITE light source.
  95      */
  96     protected LightBase() {
  97         this(Color.WHITE);
  98     }
  99 
 100     /**
 101      * Creates a new instance of {@code LightBase} class using the specified color.
 102      *
 103      * @param color the color of the light source
 104      */
 105     protected LightBase(Color color) {
 106         if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
 107             String logname = LightBase.class.getName();
 108             PlatformLogger.getLogger(logname).warning("System can't support "
 109                                                       + "ConditionalFeature.SCENE3D");
 110         }
 111 
 112         setColor(color);
 113         this.localToSceneTransformProperty().addListener(observable ->
 114                 NodeHelper.markDirty(this, DirtyBits.NODE_LIGHT_TRANSFORM));
 115     }
 116 
 117     /**
 118      * Specifies the color of light source.
 119      *
 120      * @defaultValue null
 121      */
 122     private ObjectProperty<Color> color;
 123 
 124     public final void setColor(Color value) {
 125         colorProperty().set(value);
 126     }
 127 
 128     public final Color getColor() {
 129         return color == null ? null : color.get();
 130     }
 131 
 132     public final ObjectProperty<Color> colorProperty() {
 133         if (color == null) {
 134             color = new SimpleObjectProperty<Color>(LightBase.this, "color") {
 135                 @Override
 136                 protected void invalidated() {
 137                     NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT);
 138                 }
 139             };
 140         }
 141         return color;
 142     }
 143 
 144     /**
 145      * Defines the light on or off.
 146      *
 147      * @defaultValue true
 148      */
 149     private BooleanProperty lightOn;
 150 
 151     public final void setLightOn(boolean value) {
 152         lightOnProperty().set(value);
 153     }
 154 
 155     public final boolean isLightOn() {
 156         return lightOn == null ? true : lightOn.get();
 157     }
 158 
 159     public final BooleanProperty lightOnProperty() {
 160         if (lightOn == null) {
 161             lightOn = new SimpleBooleanProperty(LightBase.this, "lightOn", true) {
 162                 @Override
 163                 protected void invalidated() {
 164                     NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT);
 165                 }
 166             };
 167         }
 168         return lightOn;
 169     }
 170 
 171     private ObservableList<Node> scope;
 172 
 173     /**
 174      * Gets the list of nodes that specifies the
 175      * hierarchical scope of this Light. If the scope list is empty,
 176      * the Light node has universe scope: all nodes under it's scene
 177      * are affected by it. If the scope list is non-empty, only those
 178      * 3D Shape nodes in the scope list and under the Group nodes in the
 179      * scope list are affected by this Light node.
 180      */
 181     public ObservableList<Node> getScope() {
 182         if (scope == null) {
 183             scope = new TrackableObservableList<Node>() {
 184 
 185                 @Override
 186                 protected void onChanged(Change<Node> c) {
 187                     NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT_SCOPE);
 188                     while (c.next()) {
 189                         for (Node node : c.getRemoved()) {
 190                             // Update the removed nodes
 191                             if (node instanceof Parent || node instanceof Shape3D) {
 192                                 markChildrenDirty(node);
 193                             }
 194                         }
 195                         for (Node node : c.getAddedSubList()) {
 196                             if (node instanceof Parent || node instanceof Shape3D) {
 197                                 markChildrenDirty(node);
 198                             }
 199                         }
 200                     }
 201                 }
 202             };
 203         }
 204         return scope;
 205     }
 206 
 207     @Override
 208     void scenesChanged(final Scene newScene, final SubScene newSubScene,
 209                        final Scene oldScene, final SubScene oldSubScene) {
 210         // This light is owned by the Scene/SubScene, and thus must change
 211         // accordingly. Note lights can owned by either a Scene or SubScene,
 212         // but not both.
 213         if (oldSubScene != null) {
 214             oldSubScene.removeLight(this);
 215         } else if (oldScene != null) {
 216             oldScene.removeLight(this);
 217         }
 218         if (newSubScene != null) {
 219             newSubScene.addLight(this);
 220         } else if (newScene != null) {
 221             newScene.addLight(this);
 222         }
 223     }
 224 
 225     private void markOwnerDirty() {
 226         // if the light is part of the scene/subScene, we will need to notify
 227         // the owner to mark the entire scene/subScene dirty.
 228         SubScene subScene = getSubScene();
 229         if (subScene != null) {
 230             subScene.markContentDirty();
 231         } else {
 232             Scene scene = getScene();
 233             if (scene != null) {
 234                 scene.setNeedsRepaint();
 235             }
 236         }
 237     }
 238 
 239     private void markChildrenDirty(Node node) {
 240         if (node instanceof Shape3D) {
 241             // Dirty using a lightweight DirtyBits.NODE_DRAWMODE bit
 242             NodeHelper.markDirty(((Shape3D) node), DirtyBits.NODE_DRAWMODE);
 243         } else if (node instanceof Parent) {
 244             for (Node child : ((Parent) node).getChildren()) {
 245                 markChildrenDirty(child);
 246             }
 247         }
 248     }
 249 
 250     /*
 251      * Note: This method MUST only be called via its accessor method.
 252      */
 253     private void doMarkDirty(DirtyBits dirtyBit) {
 254         if ((scope == null) || getScope().isEmpty()) {
 255             // This light affect the entire scene/subScene
 256             markOwnerDirty();
 257         } else if (dirtyBit != DirtyBits.NODE_LIGHT_SCOPE) {
 258             // Skip NODE_LIGHT_SCOPE dirty since it is processed on scope change.
 259             ObservableList<Node> tmpScope = getScope();
 260             for (int i = 0, max = tmpScope.size(); i < max; i++) {
 261                 markChildrenDirty(tmpScope.get(i));
 262             }
 263         }
 264     }
 265 
 266     /*
 267      * Note: This method MUST only be called via its accessor method.
 268      */
 269     private void doUpdatePeer() {
 270         NGLightBase peer = getPeer();
 271         if (isDirty(DirtyBits.NODE_LIGHT)) {
 272             peer.setColor((getColor() == null) ?
 273                     Toolkit.getPaintAccessor().getPlatformPaint(Color.WHITE)
 274                     : Toolkit.getPaintAccessor().getPlatformPaint(getColor()));
 275             peer.setLightOn(isLightOn());
 276         }
 277 
 278         if (isDirty(DirtyBits.NODE_LIGHT_SCOPE)) {
 279             if (scope != null) {
 280                 ObservableList<Node> tmpScope = getScope();
 281                 if (tmpScope.isEmpty()) {
 282                     peer.setScope(null);
 283                 } else {
 284                     Object ngList[] = new Object[tmpScope.size()];
 285                     for (int i = 0; i < tmpScope.size(); i++) {
 286                         Node n = tmpScope.get(i);
 287                         ngList[i] = n.getPeer();
 288                     }
 289                     peer.setScope(ngList);
 290                 }
 291             }
 292         }
 293 
 294         if (isDirty(DirtyBits.NODE_LIGHT_TRANSFORM)) {
 295             localToSceneTx.setToIdentity();
 296             TransformHelper.apply(getLocalToSceneTransform(), localToSceneTx);
 297             // TODO: 3D - For now, we are treating the scene as world. This may need to change
 298             // for the fixed eye position case.
 299             peer.setWorldTransform(localToSceneTx);
 300         }
 301     }
 302 
 303      /**
 304      * @treatAsPrivate implementation detail
 305      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 306      */
 307     @Deprecated
 308     @Override
 309     public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 310         // TODO: 3D - Check is this the right default
 311         return new BoxBounds();
 312     }
 313 
 314     /**
 315      * @treatAsPrivate implementation detail
 316      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 317      */
 318     @Deprecated
 319     @Override
 320     protected boolean impl_computeContains(double localX, double localY) {
 321         // TODO: 3D - Check is this the right default
 322         return false;
 323     }
 324 
 325     /**
 326      * @treatAsPrivate implementation detail
 327      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 328      */
 329     @Deprecated
 330     @Override
 331     public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
 332         throw new UnsupportedOperationException("Not supported yet.");
 333     }
 334 
 335 }