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 */
32package com.jme3.scene.plugins.blender.textures;
33
34import com.jme3.math.FastMath;
35import com.jme3.scene.plugins.blender.BlenderContext;
36import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
37import com.jme3.scene.plugins.blender.file.DynamicArray;
38import com.jme3.scene.plugins.blender.file.Pointer;
39import com.jme3.scene.plugins.blender.file.Structure;
40import com.jme3.texture.Texture;
41import java.util.Map;
42import java.util.TreeMap;
43import java.util.logging.Level;
44import 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