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