1 /*
   2  * Copyright (c) 2008, 2013, 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 com.sun.scenario.effect;
  27 
  28 import com.sun.javafx.geom.BaseBounds;
  29 import com.sun.javafx.geom.DirtyRegionContainer;
  30 import com.sun.javafx.geom.DirtyRegionPool;
  31 import com.sun.javafx.geom.Point2D;
  32 import com.sun.javafx.geom.Rectangle;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 import com.sun.scenario.effect.light.Light;
  35 
  36 /**
  37  * An effect that applies diffuse and specular lighting to an arbitrary
  38  * input using a positionable light source.
  39  */
  40 public class PhongLighting extends CoreEffect {
  41 
  42     private float surfaceScale;
  43     private float diffuseConstant;
  44     private float specularConstant;
  45     private float specularExponent;
  46     private Light light;
  47 
  48     /**
  49      * Constructs a new {@code PhongLighting} effect for the given
  50      * {@code Light}, with default values for all other properties,
  51      * using the default input for source data.
  52      * This is a convenience constructor that automatically generates a
  53      * bump map using the default input.
  54      *
  55      * @param light the light source
  56      * @throws IllegalArgumentException if {@code light} is null
  57      */
  58     public PhongLighting(Light light) {
  59         this(light, new GaussianShadow(10f), DefaultInput);
  60     }
  61 
  62     /**
  63      * Constructs a new {@code PhongLighting} effect for the given
  64      * {@code Light} and the given bump and content input {@code Effect}s
  65      * with default values for all other properties.
  66      *
  67      * @param light the light source
  68      * @param bumpInput the input containing the bump map
  69      * @param contentInput the input containing the content data
  70      * @throws IllegalArgumentException if {@code light} is null
  71      */
  72     public PhongLighting(Light light, Effect bumpInput, Effect contentInput) {
  73         super(bumpInput, contentInput);
  74 
  75         this.surfaceScale = 1f;
  76         this.diffuseConstant = 1f;
  77         this.specularConstant = 1f;
  78         this.specularExponent = 1f;
  79 
  80         setLight(light);
  81     }
  82 
  83     /**
  84      * Returns the bump input for this {@code Effect}.
  85      *
  86      * @return the bump input for this {@code Effect}
  87      */
  88     public final Effect getBumpInput() {
  89         return getInputs().get(0);
  90     }
  91 
  92     /**
  93      * Sets the bump input for this {@code Effect} to a specific
  94      * {@code Effect} or to the default input if {@code input} is
  95      * {@code null}.
  96      *
  97      * @param bumpInput the bump input for this {@code Effect}
  98      */
  99     public void setBumpInput(Effect bumpInput) {
 100         setInput(0, bumpInput);
 101     }
 102 
 103     /**
 104      * Returns the content input for this {@code Effect}.
 105      *
 106      * @return the content input for this {@code Effect}
 107      */
 108     public final Effect getContentInput() {
 109         return getInputs().get(1);
 110     }
 111 
 112     private Effect getContentInput(Effect defaultInput) {
 113         return getDefaultedInput(1, defaultInput);
 114     }
 115 
 116     /**
 117      * Sets the content input for this {@code Effect} to a specific
 118      * {@code Effect} or to the default input if {@code input} is
 119      * {@code null}.
 120      *
 121      * @param contentInput the content input for this {@code Effect}
 122      */
 123     public void setContentInput(Effect contentInput) {
 124         setInput(1, contentInput);
 125     }
 126 
 127     /**
 128      * Returns the light source.
 129      *
 130      * @return the light source
 131      */
 132     public Light getLight() {
 133         return light;
 134     }
 135 
 136     /**
 137      * Sets the light source.
 138      *
 139      * @param light the light source
 140      * @throws IllegalArgumentException if {@code light} is null
 141      */
 142     public void setLight(Light light) {
 143         if (light == null) {
 144             throw new IllegalArgumentException("Light must be non-null");
 145         }
 146         this.light = light;
 147         updatePeerKey("PhongLighting_" + light.getType().name());
 148     }
 149     
 150     /**
 151      * Returns the diffuse constant.
 152      *
 153      * @return the diffuse constant value
 154      */
 155     public float getDiffuseConstant() {
 156         return diffuseConstant;
 157     }
 158 
 159     /**
 160      * Sets the diffuse constant.
 161      * <pre>
 162      *       Min: 0.0
 163      *       Max: 2.0
 164      *   Default: 1.0
 165      *  Identity: n/a
 166      * </pre>
 167      *
 168      * @param diffuseConstant the diffuse constant value
 169      * @throws IllegalArgumentException if {@code diffuseConstant} is outside
 170      * the allowable range
 171      */
 172     public void setDiffuseConstant(float diffuseConstant) {
 173         if (diffuseConstant < 0f || diffuseConstant > 2f) {
 174             throw new IllegalArgumentException("Diffuse constant must be in the range [0,2]");
 175         }
 176         float old = this.diffuseConstant;
 177         this.diffuseConstant = diffuseConstant;
 178     }
 179 
 180     /**
 181      * Returns the specular constant.
 182      *
 183      * @return the specular constant value
 184      */
 185     public float getSpecularConstant() {
 186         return specularConstant;
 187     }
 188 
 189     /**
 190      * Sets the specular constant.
 191      * <pre>
 192      *       Min: 0.0
 193      *       Max: 2.0
 194      *   Default: 1.0
 195      *  Identity: n/a
 196      * </pre>
 197      *
 198      * @param specularConstant the specular constant value
 199      * @throws IllegalArgumentException if {@code specularConstant} is outside
 200      * the allowable range
 201      */
 202     public void setSpecularConstant(float specularConstant) {
 203         if (specularConstant < 0f || specularConstant > 2f) {
 204             throw new IllegalArgumentException("Specular constant must be in the range [0,2]");
 205         }
 206         float old = this.specularConstant;
 207         this.specularConstant = specularConstant;
 208     }
 209 
 210     /**
 211      * Returns the specular exponent.
 212      *
 213      * @return the specular exponent value
 214      */
 215     public float getSpecularExponent() {
 216         return specularExponent;
 217     }
 218 
 219     /**
 220      * Sets the specular exponent.
 221      * <pre>
 222      *       Min:  0.0
 223      *       Max: 40.0
 224      *   Default:  1.0
 225      *  Identity:  n/a
 226      * </pre>
 227      *
 228      * @param specularExponent the specular exponent value
 229      * @throws IllegalArgumentException if {@code specularExponent} is outside
 230      * the allowable range
 231      */
 232     public void setSpecularExponent(float specularExponent) {
 233         if (specularExponent < 0f || specularExponent > 40f) {
 234             throw new IllegalArgumentException("Specular exponent must be in the range [0,40]");
 235         }
 236         float old = this.specularExponent;
 237         this.specularExponent = specularExponent;
 238     }
 239 
 240     /**
 241      * Returns the surface scale.
 242      *
 243      * @return the surface scale value
 244      */
 245     public float getSurfaceScale() {
 246         return surfaceScale;
 247     }
 248 
 249     /**
 250      * Sets the surface scale.
 251      * <pre>
 252      *       Min:  0.0
 253      *       Max: 10.0
 254      *   Default:  1.0
 255      *  Identity:  n/a
 256      * </pre>
 257      *
 258      * @param surfaceScale the surface scale value
 259      * @throws IllegalArgumentException if {@code surfaceScale} is outside
 260      * the allowable range
 261      */
 262     public void setSurfaceScale(float surfaceScale) {
 263         if (surfaceScale < 0f || surfaceScale > 10f) {
 264             throw new IllegalArgumentException("Surface scale must be in the range [0,10]");
 265         }
 266         float old = this.surfaceScale;
 267         this.surfaceScale = surfaceScale;
 268     }
 269 
 270     @Override
 271     public BaseBounds getBounds(BaseTransform transform,
 272                               Effect defaultInput)
 273     {
 274         // effect inherits its bounds from the content input
 275         return getContentInput(defaultInput).getBounds(transform, defaultInput);
 276     }
 277 
 278     @Override
 279     public Rectangle getResultBounds(BaseTransform transform,
 280                                      Rectangle outputClip,
 281                                      ImageData... inputDatas)
 282     {
 283         // result inherits its dimensions from the content input
 284         return super.getResultBounds(transform, outputClip, inputDatas[1]);
 285     }
 286 
 287     @Override
 288     public Point2D transform(Point2D p, Effect defaultInput) {
 289         return getContentInput(defaultInput).transform(p, defaultInput);
 290     }
 291 
 292     @Override
 293     public Point2D untransform(Point2D p, Effect defaultInput) {
 294         return getContentInput(defaultInput).untransform(p, defaultInput);
 295     }
 296 
 297     @Override
 298     protected Rectangle getInputClip(int inputIndex,
 299                                      BaseTransform transform,
 300                                      Rectangle outputClip)
 301     {
 302         // RT-27564
 303         // TODO: Since only the content input is used for the output
 304         // bounds we could attempt to factor the bounds of the content
 305         // input in our answer here for the other inputs.
 306         return outputClip;
 307     }
 308 
 309     @Override
 310     public boolean reducesOpaquePixels() {
 311         final Effect contentInput = getContentInput();
 312         return contentInput != null && contentInput.reducesOpaquePixels();
 313     }
 314 
 315     @Override
 316     public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
 317         Effect bump = getDefaultedInput(0, defaultInput);
 318         DirtyRegionContainer drc1 = bump.getDirtyRegions(defaultInput, regionPool);
 319         drc1.grow(1, 1);
 320 
 321         Effect content = getDefaultedInput(1, defaultInput);
 322         DirtyRegionContainer drc2 = content.getDirtyRegions(defaultInput, regionPool);
 323 
 324         drc1.merge(drc2);
 325         regionPool.checkIn(drc2);
 326         
 327         return drc1;
 328     }
 329 }