1 /*
   2  * Copyright (c) 2010, 2015, 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.effect;
  27 
  28 import javafx.beans.Observable;
  29 import javafx.beans.property.DoubleProperty;
  30 import javafx.beans.property.DoublePropertyBase;
  31 import javafx.beans.property.ObjectProperty;
  32 import javafx.beans.property.ObjectPropertyBase;
  33 import javafx.scene.Node;
  34 
  35 import com.sun.javafx.util.Utils;
  36 import com.sun.javafx.effect.EffectDirtyBits;
  37 import com.sun.javafx.geom.BaseBounds;
  38 import com.sun.javafx.geom.transform.BaseTransform;
  39 import com.sun.javafx.scene.BoundsAccessor;
  40 import com.sun.scenario.effect.PhongLighting;
  41 
  42 /**
  43  * An effect that simulates a light source shining on the given content,
  44  * which can be used to give flat objects a more realistic, three-dimensional
  45  * appearance.
  46  *
  47  * <p>
  48  * Example:
  49  * <pre><code>
  50  * Light.Distant light = new Light.Distant();
  51  * light.setAzimuth(-135.0);
  52  *
  53  * Lighting lighting = new Lighting();
  54  * lighting.setLight(light);
  55  * lighting.setSurfaceScale(5.0);
  56  *
  57  * Text text = new Text();
  58  * text.setText("JavaFX!");
  59  * text.setFill(Color.STEELBLUE);
  60  * text.setFont(Font.font(null, FontWeight.BOLD, 60));
  61  * text.setX(10.0);
  62  * text.setY(10.0);
  63  * text.setTextOrigin(VPos.TOP);
  64  *
  65  * text.setEffect(lighting);
  66  * </pre></code>
  67  * <p> The code above produces the following: </p>
  68  * <p>
  69  * <img * src="doc-files/lighting.png"/>
  70  * </p>
  71  * @since JavaFX 2.0
  72  */
  73 public class Lighting extends Effect {
  74     @Override
  75     com.sun.scenario.effect.PhongLighting impl_createImpl() {
  76         return new PhongLighting(getLightInternal().impl_getImpl());
  77     };
  78 
  79     /**
  80      * Creates a new instance of Lighting with default parameters.
  81      */
  82     public Lighting() {
  83         Shadow shadow = new Shadow();
  84         shadow.setRadius(10.0f);
  85         setBumpInput(shadow);
  86     }
  87 
  88     /**
  89      * Creates a new instance of Lighting with the specified light.
  90      * @param light the light source for this {@code Lighting} effect
  91      * @since JavaFX 2.1
  92      */
  93     public Lighting(Light light) {
  94         Shadow shadow = new Shadow();
  95         shadow.setRadius(10.0f);
  96         setBumpInput(shadow);
  97         setLight(light);
  98     }
  99 
 100     private final Light defaultLight = new Light.Distant();
 101 
 102     /**
 103      * The light source for this {@code Lighting} effect.
 104      */
 105     private ObjectProperty<Light> light = new ObjectPropertyBase<Light>(new Light.Distant()) {
 106         @Override
 107         public void invalidated() {
 108             markDirty(EffectDirtyBits.EFFECT_DIRTY);
 109             effectBoundsChanged();
 110         }
 111 
 112         @Override
 113         public Object getBean() {
 114             return Lighting.this;
 115         }
 116 
 117         @Override
 118         public String getName() {
 119             return "light";
 120         }
 121     };
 122 
 123 
 124     public final void setLight(Light value) {
 125         lightProperty().set(value);
 126     }
 127 
 128     public final Light getLight() {
 129         return light.get();
 130     }
 131 
 132     public final ObjectProperty<Light> lightProperty() {
 133         return light;
 134     }
 135 
 136     private final LightChangeListener lightChangeListener = new LightChangeListener();
 137 
 138     /**
 139      * @treatAsPrivate implementation detail
 140      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 141      */
 142     @Deprecated
 143     @Override
 144     public Effect impl_copy() {
 145         Lighting lighting = new Lighting(this.getLight());
 146         lighting.setBumpInput(this.getBumpInput());
 147         lighting.setContentInput(this.getContentInput());
 148         lighting.setDiffuseConstant(this.getDiffuseConstant());
 149         lighting.setSpecularConstant(this.getSpecularConstant());
 150         lighting.setSpecularExponent(this.getSpecularExponent());
 151         lighting.setSurfaceScale(this.getSurfaceScale());
 152         return lighting;
 153     }
 154     private class LightChangeListener extends EffectChangeListener {
 155         Light light;
 156 
 157         public void register(Light value) {
 158             light = value;
 159             super.register(light == null ? null : light.effectDirtyProperty());
 160         }
 161 
 162         @Override
 163         public void invalidated(Observable valueModel) {
 164             if (light.impl_isEffectDirty()) {
 165                 markDirty(EffectDirtyBits.EFFECT_DIRTY);
 166                 effectBoundsChanged();
 167             }
 168         }
 169     };
 170     /**
 171      * The optional bump map input.
 172      * If not specified, a bump map will be automatically generated
 173      * from the default input.
 174      * If set to {@code null}, or left unspecified, a graphical image of
 175      * the {@code Node} to which the {@code Effect} is attached will be
 176      * used to generate a default bump map.
 177      * @defaultValue a Shadow effect with a radius of 10
 178      */
 179     private ObjectProperty<Effect> bumpInput;
 180 
 181 
 182     public final void setBumpInput(Effect value) {
 183         bumpInputProperty().set(value);
 184     }
 185 
 186     public final Effect getBumpInput() {
 187         return bumpInput == null ? null : bumpInput.get();
 188     }
 189 
 190     public final ObjectProperty<Effect> bumpInputProperty() {
 191         if (bumpInput == null) {
 192             bumpInput = new EffectInputProperty("bumpInput");
 193         }
 194         return bumpInput;
 195     }
 196 
 197     /**
 198      * The content input for this {@code Effect}.
 199      * If set to {@code null}, or left unspecified, a graphical image of
 200      * the {@code Node} to which the {@code Effect} is attached will be
 201      * used as the input.
 202      * @defaultValue null
 203      */
 204     private ObjectProperty<Effect> contentInput;
 205 
 206 
 207     public final void setContentInput(Effect value) {
 208         contentInputProperty().set(value);
 209     }
 210 
 211     public final Effect getContentInput() {
 212         return contentInput == null ? null : contentInput.get();
 213     }
 214 
 215     public final ObjectProperty<Effect> contentInputProperty() {
 216         if (contentInput == null) {
 217             contentInput = new EffectInputProperty("contentInput");
 218         }
 219         return contentInput;
 220     }
 221 
 222     @Override
 223     boolean impl_checkChainContains(Effect e) {
 224         Effect localBumpInput = getBumpInput();
 225         Effect localContentInput = getContentInput();
 226         if (localContentInput == e || localBumpInput == e)
 227             return true;
 228         if (localContentInput != null && localContentInput.impl_checkChainContains(e))
 229             return true;
 230         if (localBumpInput != null && localBumpInput.impl_checkChainContains(e))
 231             return true;
 232 
 233         return false;
 234     }
 235 
 236     /**
 237      * The diffuse constant.
 238      * <pre>
 239      *       Min: 0.0
 240      *       Max: 2.0
 241      *   Default: 1.0
 242      *  Identity: n/a
 243      * </pre>
 244      * @defaultValue 1.0
 245      */
 246     private DoubleProperty diffuseConstant;
 247 
 248 
 249     public final void setDiffuseConstant(double value) {
 250         diffuseConstantProperty().set(value);
 251     }
 252 
 253     public final double getDiffuseConstant() {
 254         return diffuseConstant == null ? 1 : diffuseConstant.get();
 255     }
 256 
 257     public final DoubleProperty diffuseConstantProperty() {
 258         if (diffuseConstant == null) {
 259             diffuseConstant = new DoublePropertyBase(1) {
 260 
 261                 @Override
 262                 public void invalidated() {
 263                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 264                 }
 265 
 266                 @Override
 267                 public Object getBean() {
 268                     return Lighting.this;
 269                 }
 270 
 271                 @Override
 272                 public String getName() {
 273                     return "diffuseConstant";
 274                 }
 275             };
 276         }
 277         return diffuseConstant;
 278     }
 279 
 280     /**
 281      * The specular constant.
 282      * <pre>
 283      *       Min: 0.0
 284      *       Max: 2.0
 285      *   Default: 0.3
 286      *  Identity: n/a
 287      * </pre>
 288      * @defaultValue 0.3
 289      */
 290     private DoubleProperty specularConstant;
 291 
 292 
 293     public final void setSpecularConstant(double value) {
 294         specularConstantProperty().set(value);
 295     }
 296 
 297     public final double getSpecularConstant() {
 298         return specularConstant == null ? 0.3 : specularConstant.get();
 299     }
 300 
 301     public final DoubleProperty specularConstantProperty() {
 302         if (specularConstant == null) {
 303             specularConstant = new DoublePropertyBase(0.3) {
 304 
 305                 @Override
 306                 public void invalidated() {
 307                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 308                 }
 309 
 310                 @Override
 311                 public Object getBean() {
 312                     return Lighting.this;
 313                 }
 314 
 315                 @Override
 316                 public String getName() {
 317                     return "specularConstant";
 318                 }
 319             };
 320         }
 321         return specularConstant;
 322     }
 323 
 324     /**
 325      * The specular exponent.
 326      * <pre>
 327      *       Min:  0.0
 328      *       Max: 40.0
 329      *   Default: 20.0
 330      *  Identity:  n/a
 331      * </pre>
 332      * @defaultValue 20.0
 333      */
 334     private DoubleProperty specularExponent;
 335 
 336 
 337     public final void setSpecularExponent(double value) {
 338         specularExponentProperty().set(value);
 339     }
 340 
 341     public final double getSpecularExponent() {
 342         return specularExponent == null ? 20 : specularExponent.get();
 343     }
 344 
 345     public final DoubleProperty specularExponentProperty() {
 346         if (specularExponent == null) {
 347             specularExponent = new DoublePropertyBase(20) {
 348 
 349                 @Override
 350                 public void invalidated() {
 351                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 352                 }
 353 
 354                 @Override
 355                 public Object getBean() {
 356                     return Lighting.this;
 357                 }
 358 
 359                 @Override
 360                 public String getName() {
 361                     return "specularExponent";
 362                 }
 363             };
 364         }
 365         return specularExponent;
 366     }
 367 
 368     /**
 369      * The surface scale factor.
 370      * <pre>
 371      *       Min:  0.0
 372      *       Max: 10.0
 373      *   Default:  1.5
 374      *  Identity:  n/a
 375      * </pre>
 376      * @defaultValue 1.5
 377      */
 378     private DoubleProperty surfaceScale;
 379 
 380 
 381     public final void setSurfaceScale(double value) {
 382         surfaceScaleProperty().set(value);
 383     }
 384 
 385     public final double getSurfaceScale() {
 386         return surfaceScale == null ? 1.5 : surfaceScale.get();
 387     }
 388 
 389     public final DoubleProperty surfaceScaleProperty() {
 390         if (surfaceScale == null) {
 391             surfaceScale = new DoublePropertyBase(1.5) {
 392 
 393                 @Override
 394                 public void invalidated() {
 395                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 396                 }
 397 
 398                 @Override
 399                 public Object getBean() {
 400                     return Lighting.this;
 401                 }
 402 
 403                 @Override
 404                 public String getName() {
 405                     return "surfaceScale";
 406                 }
 407             };
 408         }
 409         return surfaceScale;
 410     }
 411 
 412     private Light getLightInternal() {
 413         Light localLight = getLight();
 414         return localLight == null ? defaultLight : localLight;
 415     }
 416 
 417     @Override
 418     void impl_update() {
 419         Effect localBumpInput = getBumpInput();
 420 
 421         if (localBumpInput != null) {
 422             localBumpInput.impl_sync();
 423         }
 424 
 425         Effect localContentInput = getContentInput();
 426         if (localContentInput != null) {
 427             localContentInput.impl_sync();
 428         }
 429 
 430         PhongLighting peer = (PhongLighting) impl_getImpl();
 431         peer.setBumpInput(localBumpInput == null ? null : localBumpInput.impl_getImpl());
 432         peer.setContentInput(localContentInput == null ? null : localContentInput.impl_getImpl());
 433         peer.setDiffuseConstant((float)Utils.clamp(0, getDiffuseConstant(), 2));
 434         peer.setSpecularConstant((float)Utils.clamp(0, getSpecularConstant(), 2));
 435         peer.setSpecularExponent((float)Utils.clamp(0, getSpecularExponent(), 40));
 436         peer.setSurfaceScale((float)Utils.clamp(0, getSurfaceScale(), 10));
 437         // we don't need to register on default light in case the light is null
 438         // because default light never changes
 439         lightChangeListener.register(getLight());
 440 
 441         getLightInternal().impl_sync();
 442         peer.setLight(getLightInternal().impl_getImpl());
 443     }
 444 
 445     /**
 446      * @treatAsPrivate implementation detail
 447      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 448      */
 449     @Deprecated
 450     @Override
 451     public BaseBounds impl_getBounds(BaseBounds bounds,
 452                                      BaseTransform tx,
 453                                      Node node,
 454                                      BoundsAccessor boundsAccessor) {
 455         return getInputBounds(bounds, tx, node, boundsAccessor, getContentInput());
 456     }
 457 }