1 /* 2 * Copyright (c) 2008, 2014, 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 }