• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.jme3.scene.plugins.blender.materials;
2 
3 import com.jme3.math.ColorRGBA;
4 import com.jme3.scene.plugins.blender.BlenderContext;
5 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
6 import com.jme3.scene.plugins.blender.file.DynamicArray;
7 import com.jme3.scene.plugins.blender.file.Pointer;
8 import com.jme3.scene.plugins.blender.file.Structure;
9 import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
10 import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
11 import com.jme3.scene.plugins.blender.textures.TextureHelper;
12 import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
13 import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
14 import com.jme3.texture.Texture;
15 import com.jme3.texture.Texture.Type;
16 import com.jme3.texture.Texture.WrapMode;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.logging.Level;
23 import java.util.logging.Logger;
24 
25 /**
26  * This class holds the data about the material.
27  * @author Marcin Roguski (Kaelthas)
28  */
29 public 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")
MaterialContext(Structure structure, BlenderContext blenderContext)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 	 */
sortAndFilterTextures()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 	 */
isWithoutAlpha(Structure texture)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 	 */
getUvCoordinatesType()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 	 */
getProjectionType()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 	 */
getTextureDimension()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 	 */
getTexturesCount()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 	 */
getProjection(int textureIndex)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 	 */
readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader)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 	 */
readSpecularColor(Structure materialStructure, SpecularShader specularShader)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 	 */
getType(int texType)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 	 */
getName()399 	public String getName() {
400 		return name;
401 	}
402 
403 	/**
404 	 * @return a copy of diffuse color
405 	 */
getDiffuseColor()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 	 */
getDiffuseShader()413 	public DiffuseShader getDiffuseShader() {
414 		return diffuseShader;
415 	}
416 
417 	/**
418 	 * @return a copy of specular color
419 	 */
getSpecularColor()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 	 */
getSpecularShader()427 	public SpecularShader getSpecularShader() {
428 		return specularShader;
429 	}
430 
431 	/**
432 	 * @return an ambient color used by the material
433 	 */
getAmbientColor()434 	public ColorRGBA getAmbientColor() {
435 		return ambientColor;
436 	}
437 
438 	/**
439 	 * @return the sihiness of this material
440 	 */
getShininess()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 	 */
isShadeless()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 	 */
isVertexColor()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 	 */
isTransparent()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 	 */
isvTangent()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 	 */
getMTex(Texture texture)480 	public Structure getMTex(Texture texture) {
481 		return textureToMTexMap.get(texture);
482 	}
483 }
484