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 }