1package com.jme3.scene.plugins.blender.materials;
2
3import com.jme3.math.ColorRGBA;
4import com.jme3.scene.plugins.blender.BlenderContext;
5import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
6import com.jme3.scene.plugins.blender.file.DynamicArray;
7import com.jme3.scene.plugins.blender.file.Pointer;
8import com.jme3.scene.plugins.blender.file.Structure;
9import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
10import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
11import com.jme3.scene.plugins.blender.textures.TextureHelper;
12import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
13import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
14import com.jme3.texture.Texture;
15import com.jme3.texture.Texture.Type;
16import com.jme3.texture.Texture.WrapMode;
17import java.util.ArrayList;
18import java.util.HashMap;
19import java.util.List;
20import java.util.Map;
21import java.util.Map.Entry;
22import java.util.logging.Level;
23import java.util.logging.Logger;
24
25/**
26 * This class holds the data about the material.
27 * @author Marcin Roguski (Kaelthas)
28 */
29public final class MaterialContext {
30	private static final Logger			LOGGER				= Logger.getLogger(MaterialContext.class.getName());
31
32	//texture mapping types
33	public static final int				MTEX_COL = 0x01;
34	public static final int				MTEX_NOR = 0x02;
35	public static final int				MTEX_SPEC = 0x04;
36	public static final int				MTEX_EMIT = 0x40;
37	public static final int				MTEX_ALPHA = 0x80;
38
39	/* package */final String			name;
40	/* package */final List<Structure>	mTexs;
41	/* package */final List<Structure>	textures;
42	/* package */final Map<Number, Texture> loadedTextures;
43	/* package */final Map<Texture, Structure> textureToMTexMap;
44	/* package */final int				texturesCount;
45	/* package */final Type				textureType;
46
47	/* package */final ColorRGBA		diffuseColor;
48	/* package */final DiffuseShader 	diffuseShader;
49	/* package */final SpecularShader 	specularShader;
50	/* package */final ColorRGBA		specularColor;
51	/* package */final ColorRGBA		ambientColor;
52	/* package */final float 			shininess;
53	/* package */final boolean			shadeless;
54	/* package */final boolean			vertexColor;
55	/* package */final boolean			transparent;
56	/* package */final boolean			vTangent;
57
58	/* package */int					uvCoordinatesType	= -1;
59	/* package */int					projectionType;
60
61	@SuppressWarnings("unchecked")
62	/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
63		name = structure.getName();
64
65		int mode = ((Number) structure.getFieldValue("mode")).intValue();
66		shadeless = (mode & 0x4) != 0;
67		vertexColor = (mode & 0x80) != 0;
68		vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents
69
70		int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
71		diffuseShader = DiffuseShader.values()[diff_shader];
72
73		if(this.shadeless) {
74            float r = ((Number) structure.getFieldValue("r")).floatValue();
75            float g = ((Number) structure.getFieldValue("g")).floatValue();
76            float b = ((Number) structure.getFieldValue("b")).floatValue();
77            float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
78
79			diffuseColor = new ColorRGBA(r, g, b, alpha);
80			specularShader = null;
81			specularColor = ambientColor = null;
82			shininess = 0.0f;
83		} else {
84			diffuseColor = this.readDiffuseColor(structure, diffuseShader);
85
86			int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue();
87			specularShader = SpecularShader.values()[spec_shader];
88			specularColor = this.readSpecularColor(structure, specularShader);
89
90			float r = ((Number) structure.getFieldValue("ambr")).floatValue();
91			float g = ((Number) structure.getFieldValue("ambg")).floatValue();
92			float b = ((Number) structure.getFieldValue("ambb")).floatValue();
93			float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
94			ambientColor = new ColorRGBA(r, g, b, alpha);
95
96			float shininess = ((Number) structure.getFieldValue("emit")).floatValue();
97			this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
98		}
99
100		mTexs = new ArrayList<Structure>();
101		textures = new ArrayList<Structure>();
102
103		DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
104		int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
105		Type firstTextureType = null;
106		for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
107			Pointer p = mtexsArray.get(i);
108			if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
109				Structure mtex = p.fetchData(blenderContext.getInputStream()).get(0);
110
111				// the first texture determines the texture coordinates type
112				if (uvCoordinatesType == -1) {
113					uvCoordinatesType = ((Number) mtex.getFieldValue("texco")).intValue();
114					projectionType = ((Number) mtex.getFieldValue("mapping")).intValue();
115				} else if (uvCoordinatesType != ((Number) mtex.getFieldValue("texco")).intValue()) {
116					LOGGER.log(Level.WARNING, "The texture with index: {0} has different UV coordinates type than the first texture! This texture will NOT be loaded!", i + 1);
117					continue;
118				}
119
120				Pointer pTex = (Pointer) mtex.getFieldValue("tex");
121				if (pTex.isNotNull()) {
122					Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
123					int type = ((Number) tex.getFieldValue("type")).intValue();
124					Type textureType = this.getType(type);
125					if (textureType != null) {
126						if (firstTextureType == null) {
127							firstTextureType = textureType;
128							mTexs.add(mtex);
129							textures.add(tex);
130						} else if (firstTextureType == textureType) {
131							mTexs.add(mtex);
132							textures.add(tex);
133						} else {
134							LOGGER.log(Level.WARNING, "The texture with index: {0} is of different dimension than the first one! This texture will NOT be loaded!", i + 1);
135						}
136					}
137				}
138			}
139		}
140
141		//loading the textures and merging them
142		Map<Number, List<Structure[]>> sortedTextures = this.sortAndFilterTextures();
143		loadedTextures = new HashMap<Number, Texture>(sortedTextures.size());
144		textureToMTexMap = new HashMap<Texture, Structure>();
145		float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a};
146		TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
147		for(Entry<Number, List<Structure[]>> entry : sortedTextures.entrySet()) {
148			if(entry.getValue().size()>0) {
149				List<Texture> textures = new ArrayList<Texture>(entry.getValue().size());
150				for(Structure[] mtexAndTex : entry.getValue()) {
151					int texflag = ((Number) mtexAndTex[0].getFieldValue("texflag")).intValue();
152					boolean negateTexture = (texflag & 0x04) != 0;
153					Texture texture = textureHelper.getTexture(mtexAndTex[1], blenderContext);
154					int blendType = ((Number) mtexAndTex[0].getFieldValue("blendtype")).intValue();
155					float[] color = new float[] { ((Number) mtexAndTex[0].getFieldValue("r")).floatValue(),
156												  ((Number) mtexAndTex[0].getFieldValue("g")).floatValue(),
157												  ((Number) mtexAndTex[0].getFieldValue("b")).floatValue() };
158					float colfac = ((Number) mtexAndTex[0].getFieldValue("colfac")).floatValue();
159					TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat());
160					texture = textureBlender.blend(diffuseColorArray, texture, color, colfac, blendType, negateTexture, blenderContext);
161					texture.setWrap(WrapMode.Repeat);
162					textures.add(texture);
163					textureToMTexMap.put(texture, mtexAndTex[0]);
164				}
165				loadedTextures.put(entry.getKey(), textureHelper.mergeTextures(textures, this));
166			}
167		}
168
169		this.texturesCount = mTexs.size();
170		this.textureType = firstTextureType;
171
172		//veryfying if  the transparency is present
173		//(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
174		//it is not required
175		boolean transparent = false;
176		if(diffuseColor != null) {
177			transparent = diffuseColor.a < 1.0f;
178			if(sortedTextures.size() > 0) {//texutre covers the material color
179				diffuseColor.set(1, 1, 1, 1);
180			}
181		}
182		if(specularColor != null) {
183			transparent = transparent || specularColor.a < 1.0f;
184		}
185		if(ambientColor != null) {
186			transparent = transparent || ambientColor.a < 1.0f;
187		}
188		this.transparent = transparent;
189	}
190
191	/**
192	 * This method sorts the textures by their mapping type.
193	 * In each group only textures of one type are put (either two- or three-dimensional).
194	 * If the mapping type is MTEX_COL then if the texture has no alpha channel then all textures before it are
195	 * discarded and will not be loaded and merged because texture with no alpha will cover them anyway.
196	 * @return a map with sorted and filtered textures
197	 */
198	private Map<Number, List<Structure[]>> sortAndFilterTextures() {
199		Map<Number, List<Structure[]>> result = new HashMap<Number, List<Structure[]>>();
200		for (int i = 0; i < mTexs.size(); ++i) {
201			Structure mTex = mTexs.get(i);
202			Structure texture  = textures.get(i);
203			Number mapto = (Number) mTex.getFieldValue("mapto");
204			List<Structure[]> mtexs = result.get(mapto);
205			if(mtexs==null) {
206				mtexs = new ArrayList<Structure[]>();
207				result.put(mapto, mtexs);
208			}
209			if(mapto.intValue() == MTEX_COL && this.isWithoutAlpha(textures.get(i))) {
210				mtexs.clear();//remove previous textures, they will be covered anyway
211			}
212			mtexs.add(new Structure[] {mTex, texture});
213		}
214		return result;
215	}
216
217	/**
218	 * This method determines if the given texture has no alpha channel.
219	 *
220	 * @param texture
221	 *            the texture to check for alpha channel
222	 * @return <b>true</b> if the texture has no alpha channel and <b>false</b>
223	 *         otherwise
224	 */
225	private boolean isWithoutAlpha(Structure texture) {
226		int flag = ((Number) texture.getFieldValue("flag")).intValue();
227		if((flag & 0x01) == 0) {//the texture has no colorband
228			int type = ((Number) texture.getFieldValue("type")).intValue();
229			if(type==TextureHelper.TEX_MAGIC) {
230				return true;
231			}
232			if(type==TextureHelper.TEX_VORONOI) {
233				int voronoiColorType = ((Number) texture.getFieldValue("vn_coltype")).intValue();
234				return voronoiColorType != 0;//voronoiColorType == 0: intensity, voronoiColorType != 0: col1, col2 or col3
235			}
236			if(type==TextureHelper.TEX_CLOUDS) {
237				int sType = ((Number) texture.getFieldValue("stype")).intValue();
238				return sType == 1;//sType==0: without colors, sType==1: with colors
239			}
240		}
241		return false;
242	}
243
244	/**
245	 * This method returns the current material's texture UV coordinates type.
246	 * @return uv coordinates type
247	 */
248	public int getUvCoordinatesType() {
249		return uvCoordinatesType;
250	}
251
252	/**
253	 * This method returns the proper projection type for the material's texture.
254	 * This applies only to 2D textures.
255	 * @return texture's projection type
256	 */
257	public int getProjectionType() {
258		return projectionType;
259	}
260
261	/**
262	 * This method returns current material's texture dimension.
263	 * @return the material's texture dimension
264	 */
265	public int getTextureDimension() {
266		return this.textureType == Type.TwoDimensional ? 2 : 3;
267	}
268
269	/**
270	 * This method returns the amount of textures applied for the current
271	 * material.
272	 *
273	 * @return the amount of textures applied for the current material
274	 */
275	public int getTexturesCount() {
276		return textures == null ? 0 : textures.size();
277	}
278
279	/**
280	 * This method returns the projection array that indicates where the current coordinate factor X, Y or Z (represented
281	 * by the index in the array) will be used where (indicated by the value in the array).
282	 * For example the configuration: [1,2,3] means that X - coordinate will be used as X, Y as Y and Z as Z.
283	 * The configuration [2,1,0] means that Z will be used instead of X coordinate, Y will be used as Y and
284	 * Z will not be used at all (0 will be in its place).
285	 * @param textureIndex
286	 *        the index of the texture
287	 * @return texture projection array
288	 */
289	public int[] getProjection(int textureIndex) {
290		Structure mtex = mTexs.get(textureIndex);
291		return new int[] { ((Number) mtex.getFieldValue("projx")).intValue(), ((Number) mtex.getFieldValue("projy")).intValue(), ((Number) mtex.getFieldValue("projz")).intValue() };
292	}
293
294	/**
295	 * This method returns the diffuse color.
296	 *
297	 * @param materialStructure the material structure
298	 * @param diffuseShader the diffuse shader
299	 * @return the diffuse color
300	 */
301	private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
302		// bitwise 'or' of all textures mappings
303		int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
304
305		// diffuse color
306		float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
307		float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
308		float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
309		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
310		if ((commonMapto & 0x01) == 0x01) {// Col
311			return new ColorRGBA(r, g, b, alpha);
312		} else {
313			switch (diffuseShader) {
314				case FRESNEL:
315				case ORENNAYAR:
316				case TOON:
317					break;// TODO: find what is the proper modification
318				case MINNAERT:
319				case LAMBERT:// TODO: check if that is correct
320					float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
321					r *= ref;
322					g *= ref;
323					b *= ref;
324					break;
325				default:
326					throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
327			}
328			return new ColorRGBA(r, g, b, alpha);
329		}
330	}
331
332	/**
333	 * This method returns a specular color used by the material.
334	 *
335	 * @param materialStructure
336	 *        the material structure filled with data
337	 * @return a specular color used by the material
338	 */
339	private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) {
340		float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();
341		float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();
342		float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();
343		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
344		switch (specularShader) {
345			case BLINN:
346			case COOKTORRENCE:
347			case TOON:
348			case WARDISO:// TODO: find what is the proper modification
349				break;
350			case PHONG:// TODO: check if that is correct
351				float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();
352				r *= spec * 0.5f;
353				g *= spec * 0.5f;
354				b *= spec * 0.5f;
355				break;
356			default:
357				throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());
358		}
359		return new ColorRGBA(r, g, b, alpha);
360	}
361
362	/**
363	 * This method determines the type of the texture.
364	 * @param texType
365	 *        texture type (from blender)
366	 * @return texture type (used by jme)
367	 */
368	private Type getType(int texType) {
369		switch (texType) {
370			case TextureHelper.TEX_IMAGE:// (it is first because probably this will be most commonly used)
371				return Type.TwoDimensional;
372			case TextureHelper.TEX_CLOUDS:
373			case TextureHelper.TEX_WOOD:
374			case TextureHelper.TEX_MARBLE:
375			case TextureHelper.TEX_MAGIC:
376			case TextureHelper.TEX_BLEND:
377			case TextureHelper.TEX_STUCCI:
378			case TextureHelper.TEX_NOISE:
379			case TextureHelper.TEX_MUSGRAVE:
380			case TextureHelper.TEX_VORONOI:
381			case TextureHelper.TEX_DISTNOISE:
382				return Type.ThreeDimensional;
383			case TextureHelper.TEX_NONE:// No texture, do nothing
384				return null;
385			case TextureHelper.TEX_POINTDENSITY:
386			case TextureHelper.TEX_VOXELDATA:
387			case TextureHelper.TEX_PLUGIN:
388			case TextureHelper.TEX_ENVMAP:
389				LOGGER.log(Level.WARNING, "Texture type NOT supported: {0}", texType);
390				return null;
391			default:
392				throw new IllegalStateException("Unknown texture type: " + texType);
393		}
394	}
395
396	/**
397	 * @return he material's name
398	 */
399	public String getName() {
400		return name;
401	}
402
403	/**
404	 * @return a copy of diffuse color
405	 */
406	public ColorRGBA getDiffuseColor() {
407		return diffuseColor.clone();
408	}
409
410	/**
411	 * @return an enum describing the type of a diffuse shader used by this material
412	 */
413	public DiffuseShader getDiffuseShader() {
414		return diffuseShader;
415	}
416
417	/**
418	 * @return a copy of specular color
419	 */
420	public ColorRGBA getSpecularColor() {
421		return specularColor.clone();
422	}
423
424	/**
425	 * @return an enum describing the type of a specular shader used by this material
426	 */
427	public SpecularShader getSpecularShader() {
428		return specularShader;
429	}
430
431	/**
432	 * @return an ambient color used by the material
433	 */
434	public ColorRGBA getAmbientColor() {
435		return ambientColor;
436	}
437
438	/**
439	 * @return the sihiness of this material
440	 */
441	public float getShininess() {
442		return shininess;
443	}
444
445	/**
446	 * @return <b>true</b> if the material is shadeless and <b>false</b> otherwise
447	 */
448	public boolean isShadeless() {
449		return shadeless;
450	}
451
452	/**
453	 * @return <b>true</b> if the material uses vertex color and <b>false</b> otherwise
454	 */
455	public boolean isVertexColor() {
456		return vertexColor;
457	}
458
459	/**
460	 * @return <b>true</b> if the material is transparent and <b>false</b> otherwise
461	 */
462	public boolean isTransparent() {
463		return transparent;
464	}
465
466	/**
467	 * @return <b>true</b> if the material uses tangents and <b>false</b> otherwise
468	 */
469	public boolean isvTangent() {
470		return vTangent;
471	}
472
473	/**
474	 * @param texture
475	 *            the texture for which its mtex structure definition will be
476	 *            fetched
477	 * @return mtex structure of the current texture or <b>null</b> if none
478	 *         exists
479	 */
480	public Structure getMTex(Texture texture) {
481		return textureToMTexMap.get(texture);
482	}
483}
484