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