Home | History | Annotate | Download | only in textures
      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