Home | History | Annotate | Download | only in materials
      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")
     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