1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.scene.plugins.blender.textures; 33 34 import com.jme3.math.FastMath; 35 import com.jme3.scene.plugins.blender.BlenderContext; 36 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 37 import com.jme3.scene.plugins.blender.file.DynamicArray; 38 import com.jme3.scene.plugins.blender.file.Pointer; 39 import com.jme3.scene.plugins.blender.file.Structure; 40 import com.jme3.texture.Texture; 41 import java.util.Map; 42 import java.util.TreeMap; 43 import java.util.logging.Level; 44 import java.util.logging.Logger; 45 46 /** 47 * This class is a base class for texture generators. 48 * @author Marcin Roguski (Kaelthas) 49 */ 50 /* package */abstract class TextureGenerator { 51 private static final Logger LOGGER = Logger.getLogger(TextureGenerator.class.getName()); 52 53 protected NoiseGenerator noiseGenerator; 54 55 public TextureGenerator(NoiseGenerator noiseGenerator) { 56 this.noiseGenerator = noiseGenerator; 57 } 58 59 /** 60 * This method generates the texture. 61 * @param tex 62 * texture's structure 63 * @param width 64 * the width of the result texture 65 * @param height 66 * the height of the result texture 67 * @param depth 68 * the depth of the texture 69 * @param blenderContext 70 * the blender context 71 * @return newly generated texture 72 */ 73 protected abstract Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext); 74 75 /** 76 * This method reads the colorband data from the given texture structure. 77 * 78 * @param tex 79 * the texture structure 80 * @param blenderContext 81 * the blender context 82 * @return read colorband or null if not present 83 */ 84 private ColorBand readColorband(Structure tex, BlenderContext blenderContext) { 85 ColorBand result = null; 86 int flag = ((Number) tex.getFieldValue("flag")).intValue(); 87 if ((flag & NoiseGenerator.TEX_COLORBAND) != 0) { 88 Pointer pColorband = (Pointer) tex.getFieldValue("coba"); 89 Structure colorbandStructure; 90 try { 91 colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0); 92 result = new ColorBand(colorbandStructure); 93 } catch (BlenderFileException e) { 94 LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); 95 } 96 } 97 return result; 98 } 99 100 protected float[][] computeColorband(Structure tex, BlenderContext blenderContext) { 101 ColorBand colorBand = this.readColorband(tex, blenderContext); 102 float[][] result = null; 103 if(colorBand!=null) { 104 result = new float[1001][4];//1001 - amount of possible cursor positions; 4 = [r, g, b, a] 105 ColorBandData[] dataArray = colorBand.data; 106 107 if(dataArray.length==1) {//special case; use only one color for all types of colorband interpolation 108 for(int i=0;i<result.length;++i) { 109 result[i][0] = dataArray[0].r; 110 result[i][1] = dataArray[0].g; 111 result[i][2] = dataArray[0].b; 112 result[i][3] = dataArray[0].a; 113 } 114 } else { 115 int currentCursor = 0; 116 ColorBandData currentData = dataArray[0]; 117 ColorBandData nextData = dataArray[0]; 118 switch(colorBand.ipoType) { 119 case ColorBand.IPO_LINEAR: 120 float rDiff = 0, gDiff = 0, bDiff = 0, aDiff = 0, posDiff; 121 for(int i=0;i<result.length;++i) { 122 posDiff = i - currentData.pos; 123 result[i][0] = currentData.r + rDiff * posDiff; 124 result[i][1] = currentData.g + gDiff * posDiff; 125 result[i][2] = currentData.b + bDiff * posDiff; 126 result[i][3] = currentData.a + aDiff * posDiff; 127 if(nextData.pos==i) { 128 currentData = dataArray[currentCursor++]; 129 if(currentCursor < dataArray.length) { 130 nextData = dataArray[currentCursor]; 131 //calculate differences 132 int d = nextData.pos - currentData.pos; 133 rDiff = (nextData.r - currentData.r)/d; 134 gDiff = (nextData.g - currentData.g)/d; 135 bDiff = (nextData.b - currentData.b)/d; 136 aDiff = (nextData.a - currentData.a)/d; 137 } else { 138 rDiff = gDiff = bDiff = aDiff = 0; 139 } 140 } 141 } 142 break; 143 case ColorBand.IPO_BSPLINE: 144 case ColorBand.IPO_CARDINAL: 145 Map<Integer, ColorBandData> cbDataMap = new TreeMap<Integer, ColorBandData>(); 146 for(int i=0;i<colorBand.data.length;++i) { 147 cbDataMap.put(Integer.valueOf(i), colorBand.data[i]); 148 } 149 150 if(colorBand.data[0].pos==0) { 151 cbDataMap.put(Integer.valueOf(-1), colorBand.data[0]); 152 } else { 153 ColorBandData cbData = colorBand.data[0].clone(); 154 cbData.pos = 0; 155 cbDataMap.put(Integer.valueOf(-1), cbData); 156 cbDataMap.put(Integer.valueOf(-2), cbData); 157 } 158 159 if(colorBand.data[colorBand.data.length - 1].pos==1000) { 160 cbDataMap.put(Integer.valueOf(colorBand.data.length), colorBand.data[colorBand.data.length - 1]); 161 } else { 162 ColorBandData cbData = colorBand.data[colorBand.data.length - 1].clone(); 163 cbData.pos = 1000; 164 cbDataMap.put(Integer.valueOf(colorBand.data.length), cbData); 165 cbDataMap.put(Integer.valueOf(colorBand.data.length + 1), cbData); 166 } 167 168 float[] ipoFactors = new float[4]; 169 float f; 170 171 ColorBandData data0 = cbDataMap.get(currentCursor - 2); 172 ColorBandData data1 = cbDataMap.get(currentCursor - 1); 173 ColorBandData data2 = cbDataMap.get(currentCursor); 174 ColorBandData data3 = cbDataMap.get(currentCursor + 1); 175 176 for(int i=0;i<result.length;++i) { 177 if (data2.pos != data1.pos) { 178 f = (i - data2.pos) / (float)(data1.pos - data2.pos); 179 } else { 180 f = 0.0f; 181 } 182 183 f = FastMath.clamp(f, 0.0f, 1.0f); 184 185 this.getIpoData(colorBand, f, ipoFactors); 186 result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; 187 result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; 188 result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; 189 result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; 190 result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); 191 result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); 192 result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); 193 result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); 194 195 if(nextData.pos==i) { 196 ++currentCursor; 197 data0 = cbDataMap.get(currentCursor - 2); 198 data1 = cbDataMap.get(currentCursor - 1); 199 data2 = cbDataMap.get(currentCursor); 200 data3 = cbDataMap.get(currentCursor + 1); 201 } 202 } 203 break; 204 case ColorBand.IPO_EASE: 205 float d, a, b, d2; 206 for(int i=0;i<result.length;++i) { 207 if(nextData.pos != currentData.pos) { 208 d = (i - currentData.pos) / (float)(nextData.pos - currentData.pos); 209 d2 = d * d; 210 a = 3.0f * d2 - 2.0f * d * d2; 211 b = 1.0f - a; 212 } else { 213 d = a = 0.0f; 214 b = 1.0f; 215 } 216 217 result[i][0] = b * currentData.r + a * nextData.r; 218 result[i][1] = b * currentData.g + a * nextData.g; 219 result[i][2] = b * currentData.b + a * nextData.b; 220 result[i][3] = b * currentData.a + a * nextData.a; 221 if(nextData.pos==i) { 222 currentData = dataArray[currentCursor++]; 223 if(currentCursor < dataArray.length) { 224 nextData = dataArray[currentCursor]; 225 } 226 } 227 } 228 break; 229 case ColorBand.IPO_CONSTANT: 230 for(int i=0;i<result.length;++i) { 231 result[i][0] = currentData.r; 232 result[i][1] = currentData.g; 233 result[i][2] = currentData.b; 234 result[i][3] = currentData.a; 235 if(nextData.pos==i) { 236 currentData = dataArray[currentCursor++]; 237 if(currentCursor < dataArray.length) { 238 nextData = dataArray[currentCursor]; 239 } 240 } 241 } 242 break; 243 default: 244 throw new IllegalStateException("Unknown interpolation type: " + colorBand.ipoType); 245 } 246 } 247 } 248 return result; 249 } 250 251 /** 252 * This method returns the data for either B-spline of Cardinal interpolation. 253 * @param colorBand the color band 254 * @param d distance factor for the current intensity 255 * @param ipoFactors table to store the results (size of the table must be at least 4) 256 */ 257 private void getIpoData(ColorBand colorBand, float d, float[] ipoFactors) { 258 float d2 = d * d; 259 float d3 = d2 * d; 260 if(colorBand.ipoType==ColorBand.IPO_BSPLINE) { 261 ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; 262 ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; 263 ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; 264 ipoFactors[3] = 0.71f * d3 - 0.71f * d2; 265 } else if(colorBand.ipoType==ColorBand.IPO_CARDINAL) { 266 ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; 267 ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; 268 ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; 269 ipoFactors[3] = 0.16666666f * d3; 270 } else { 271 throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); 272 } 273 } 274 275 /** 276 * This method applies brightness and contrast for RGB textures. 277 * @param tex texture structure 278 * @param texres 279 */ 280 protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { 281 texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness; 282 if (texres.red < 0.0f) { 283 texres.red = 0.0f; 284 } 285 texres.green =(texres.green - 0.5f) * bacd.contrast + bacd.brightness; 286 if (texres.green < 0.0f) { 287 texres.green = 0.0f; 288 } 289 texres.blue = (texres.blue - 0.5f) * bacd.contrast + bacd.brightness; 290 if (texres.blue < 0.0f) { 291 texres.blue = 0.0f; 292 } 293 } 294 295 /** 296 * This method applies brightness and contrast for Luminance textures. 297 * @param texres 298 * @param contrast 299 * @param brightness 300 */ 301 protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { 302 texres.intensity = (texres.intensity - 0.5f) * contrast + brightness; 303 if (texres.intensity < 0.0f) { 304 texres.intensity = 0.0f; 305 } else if (texres.intensity > 1.0f) { 306 texres.intensity = 1.0f; 307 } 308 } 309 310 /** 311 * A class constaining the colorband data. 312 * 313 * @author Marcin Roguski (Kaelthas) 314 */ 315 protected static class ColorBand { 316 //interpolation types 317 public static final int IPO_LINEAR = 0; 318 public static final int IPO_EASE = 1; 319 public static final int IPO_BSPLINE = 2; 320 public static final int IPO_CARDINAL = 3; 321 public static final int IPO_CONSTANT = 4; 322 323 public int cursorsAmount, ipoType; 324 public ColorBandData[] data; 325 326 /** 327 * Constructor. Loads the data from the given structure. 328 * 329 * @param cbdataStructure 330 * the colorband structure 331 */ 332 @SuppressWarnings("unchecked") 333 public ColorBand(Structure colorbandStructure) { 334 this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); 335 this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); 336 this.data = new ColorBandData[this.cursorsAmount]; 337 DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data"); 338 for (int i = 0; i < this.cursorsAmount; ++i) { 339 this.data[i] = new ColorBandData(data.get(i)); 340 } 341 } 342 } 343 344 /** 345 * Class to store the single colorband cursor data. 346 * 347 * @author Marcin Roguski (Kaelthas) 348 */ 349 protected static class ColorBandData implements Cloneable { 350 public final float r, g, b, a; 351 public int pos; 352 353 /** 354 * Copy constructor. 355 */ 356 private ColorBandData(ColorBandData data) { 357 this.r = data.r; 358 this.g = data.g; 359 this.b = data.b; 360 this.a = data.a; 361 this.pos = data.pos; 362 } 363 364 /** 365 * Constructor. Loads the data from the given structure. 366 * 367 * @param cbdataStructure 368 * the structure containing the CBData object 369 */ 370 public ColorBandData(Structure cbdataStructure) { 371 this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); 372 this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); 373 this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); 374 this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); 375 this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); 376 } 377 378 @Override 379 public ColorBandData clone() { 380 try { 381 return (ColorBandData) super.clone(); 382 } catch (CloneNotSupportedException e) { 383 return new ColorBandData(this); 384 } 385 } 386 387 @Override 388 public String toString() { 389 return "P: " + this.pos + " [" + this.r+", "+this.g+", "+this.b+", "+this.a+"]"; 390 } 391 } 392 393 /** 394 * This class contains brightness and contrast data. 395 * @author Marcin Roguski (Kaelthas) 396 */ 397 protected static class BrightnessAndContrastData { 398 public final float contrast; 399 public final float brightness; 400 public final float rFactor; 401 public final float gFactor; 402 public final float bFactor; 403 404 /** 405 * Constructor reads the required data from the given structure. 406 * @param tex texture structure 407 */ 408 public BrightnessAndContrastData(Structure tex) { 409 contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); 410 brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; 411 rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); 412 gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); 413 bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); 414 } 415 } 416 } 417