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