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