1 /* 2 * Copyright (c) 2009-2012 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 */ 32 package com.jme3.scene.plugins.blender.materials; 33 34 import com.jme3.asset.BlenderKey.FeaturesToLoad; 35 import com.jme3.material.MatParam; 36 import com.jme3.material.MatParamTexture; 37 import com.jme3.material.Material; 38 import com.jme3.material.RenderState.BlendMode; 39 import com.jme3.material.RenderState.FaceCullMode; 40 import com.jme3.math.ColorRGBA; 41 import com.jme3.math.FastMath; 42 import com.jme3.scene.plugins.blender.AbstractBlenderHelper; 43 import com.jme3.scene.plugins.blender.BlenderContext; 44 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; 45 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 46 import com.jme3.scene.plugins.blender.file.Pointer; 47 import com.jme3.scene.plugins.blender.file.Structure; 48 import com.jme3.shader.VarType; 49 import com.jme3.texture.Image; 50 import com.jme3.texture.Image.Format; 51 import com.jme3.texture.Texture; 52 import com.jme3.texture.Texture.Type; 53 import com.jme3.util.BufferUtils; 54 import java.nio.ByteBuffer; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.Map.Entry; 59 import java.util.logging.Level; 60 import java.util.logging.Logger; 61 62 public class MaterialHelper extends AbstractBlenderHelper { 63 private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); 64 protected static final float DEFAULT_SHININESS = 20.0f; 65 66 public static final String TEXTURE_TYPE_3D = "Texture"; 67 public static final String TEXTURE_TYPE_COLOR = "ColorMap"; 68 public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; 69 public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; 70 public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; 71 public static final String TEXTURE_TYPE_GLOW = "GlowMap"; 72 public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; 73 74 public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); 75 public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); 76 public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); 77 public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); 78 protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>(); 79 80 /** 81 * The type of the material's diffuse shader. 82 */ 83 public static enum DiffuseShader { 84 LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL 85 } 86 87 /** 88 * The type of the material's specular shader. 89 */ 90 public static enum SpecularShader { 91 COOKTORRENCE, PHONG, BLINN, TOON, WARDISO 92 } 93 94 /** Face cull mode. Should be excplicitly set before this helper is used. */ 95 protected FaceCullMode faceCullMode; 96 97 /** 98 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender 99 * versions. 100 * 101 * @param blenderVersion 102 * the version read from the blend file 103 * @param fixUpAxis 104 * a variable that indicates if the Y asxis is the UP axis or not 105 */ 106 public MaterialHelper(String blenderVersion, boolean fixUpAxis) { 107 super(blenderVersion, false); 108 // setting alpha masks 109 alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { 110 @Override 111 public void setImageSize(int width, int height) {} 112 113 @Override 114 public byte getAlpha(float x, float y) { 115 return (byte) 255; 116 } 117 }); 118 alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { 119 private float r; 120 private float[] center; 121 122 @Override 123 public void setImageSize(int width, int height) { 124 r = Math.min(width, height) * 0.5f; 125 center = new float[] { width * 0.5f, height * 0.5f }; 126 } 127 128 @Override 129 public byte getAlpha(float x, float y) { 130 float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); 131 return (byte) (d >= r ? 0 : 255); 132 } 133 }); 134 alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { 135 private float r; 136 private float[] center; 137 138 @Override 139 public void setImageSize(int width, int height) { 140 r = Math.min(width, height) * 0.5f; 141 center = new float[] { width * 0.5f, height * 0.5f }; 142 } 143 144 @Override 145 public byte getAlpha(float x, float y) { 146 float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); 147 return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); 148 } 149 }); 150 alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { 151 private float r; 152 private float[] center; 153 154 @Override 155 public void setImageSize(int width, int height) { 156 r = Math.min(width, height) * 0.5f; 157 center = new float[] { width * 0.5f, height * 0.5f }; 158 } 159 160 @Override 161 public byte getAlpha(float x, float y) { 162 float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; 163 return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); 164 } 165 }); 166 } 167 168 /** 169 * This method sets the face cull mode to be used with every loaded material. 170 * 171 * @param faceCullMode 172 * the face cull mode 173 */ 174 public void setFaceCullMode(FaceCullMode faceCullMode) { 175 this.faceCullMode = faceCullMode; 176 } 177 178 /** 179 * This method converts the material structure to jme Material. 180 * @param structure 181 * structure with material data 182 * @param blenderContext 183 * the blender context 184 * @return jme material 185 * @throws BlenderFileException 186 * an exception is throw when problems with blend file occur 187 */ 188 public Material toMaterial(Structure structure, BlenderContext blenderContext) throws BlenderFileException { 189 LOGGER.log(Level.INFO, "Loading material."); 190 if (structure == null) { 191 return blenderContext.getDefaultMaterial(); 192 } 193 Material result = (Material) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); 194 if (result != null) { 195 return result; 196 } 197 198 MaterialContext materialContext = new MaterialContext(structure, blenderContext); 199 LOGGER.log(Level.INFO, "Material's name: {0}", materialContext.name); 200 201 if(materialContext.textures.size() > 1) { 202 LOGGER.log(Level.WARNING, "Attetion! Many textures found for material: {0}. Only the first of each supported mapping types will be used!", materialContext.name); 203 } 204 205 // texture 206 Type colorTextureType = null; 207 Map<String, Texture> texturesMap = new HashMap<String, Texture>(); 208 for(Entry<Number, Texture> textureEntry : materialContext.loadedTextures.entrySet()) { 209 int mapto = textureEntry.getKey().intValue(); 210 Texture texture = textureEntry.getValue(); 211 if ((mapto & MaterialContext.MTEX_COL) != 0) { 212 colorTextureType = texture.getType(); 213 if (materialContext.shadeless) { 214 texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_COLOR, texture); 215 } else { 216 texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_DIFFUSE, texture); 217 } 218 } 219 if(texture.getType()==Type.TwoDimensional) {//so far only 2D textures can be mapped in other way than color 220 if ((mapto & MaterialContext.MTEX_NOR) != 0 && !materialContext.shadeless) { 221 //Structure mTex = materialContext.getMTex(texture); 222 //Texture normalMapTexture = textureHelper.convertToNormalMapTexture(texture, ((Number) mTex.getFieldValue("norfac")).floatValue()); 223 //texturesMap.put(TEXTURE_TYPE_NORMAL, normalMapTexture); 224 texturesMap.put(TEXTURE_TYPE_NORMAL, texture); 225 } 226 if ((mapto & MaterialContext.MTEX_EMIT) != 0) { 227 texturesMap.put(TEXTURE_TYPE_GLOW, texture); 228 } 229 if ((mapto & MaterialContext.MTEX_SPEC) != 0 && !materialContext.shadeless) { 230 texturesMap.put(TEXTURE_TYPE_SPECULAR, texture); 231 } 232 if ((mapto & MaterialContext.MTEX_ALPHA) != 0 && !materialContext.shadeless) { 233 texturesMap.put(TEXTURE_TYPE_ALPHA, texture); 234 } 235 } 236 } 237 238 //creating the material 239 if(colorTextureType==Type.ThreeDimensional) { 240 result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Texture3D/tex3D.j3md"); 241 } else { 242 if (materialContext.shadeless) { 243 result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); 244 245 if (!materialContext.transparent) { 246 materialContext.diffuseColor.a = 1; 247 } 248 249 result.setColor("Color", materialContext.diffuseColor); 250 } else { 251 result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); 252 result.setBoolean("UseMaterialColors", Boolean.TRUE); 253 254 // setting the colors 255 result.setBoolean("Minnaert", materialContext.diffuseShader == DiffuseShader.MINNAERT); 256 if (!materialContext.transparent) { 257 materialContext.diffuseColor.a = 1; 258 } 259 result.setColor("Diffuse", materialContext.diffuseColor); 260 261 result.setBoolean("WardIso", materialContext.specularShader == SpecularShader.WARDISO); 262 result.setColor("Specular", materialContext.specularColor); 263 264 result.setColor("Ambient", materialContext.ambientColor); 265 result.setFloat("Shininess", materialContext.shininess); 266 } 267 268 if (materialContext.vertexColor) { 269 result.setBoolean(materialContext.shadeless ? "VertexColor" : "UseVertexColor", true); 270 } 271 } 272 273 //applying textures 274 for(Entry<String, Texture> textureEntry : texturesMap.entrySet()) { 275 result.setTexture(textureEntry.getKey(), textureEntry.getValue()); 276 } 277 278 //applying other data 279 result.getAdditionalRenderState().setFaceCullMode(faceCullMode); 280 if (materialContext.transparent) { 281 result.setTransparent(true); 282 result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); 283 } 284 285 result.setName(materialContext.getName()); 286 blenderContext.setMaterialContext(result, materialContext); 287 blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); 288 return result; 289 } 290 291 /** 292 * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but 293 * returned itself. 294 * 295 * @param material 296 * a material to be cloned without textures 297 * @param imageType 298 * type of image defined by blender; the constants are defined in TextureHelper 299 * @return material without textures of a specified type 300 */ 301 public Material getNonTexturedMaterial(Material material, int imageType) { 302 String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA }; 303 Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length); 304 for (String textureParamName : textureParamNames) { 305 MatParamTexture matParamTexture = material.getTextureParam(textureParamName); 306 if (matParamTexture != null) { 307 textures.put(textureParamName, matParamTexture.getTextureValue()); 308 } 309 } 310 if (textures.isEmpty()) { 311 return material; 312 } else { 313 // clear all textures first so that wo de not waste resources cloning them 314 for (Entry<String, Texture> textureParamName : textures.entrySet()) { 315 String name = textureParamName.getValue().getName(); 316 try { 317 int type = Integer.parseInt(name); 318 if (type == imageType) { 319 material.clearParam(textureParamName.getKey()); 320 } 321 } catch (NumberFormatException e) { 322 LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name); 323 } 324 } 325 Material result = material.clone(); 326 // put the textures back in place 327 for (Entry<String, Texture> textureEntry : textures.entrySet()) { 328 material.setTexture(textureEntry.getKey(), textureEntry.getValue()); 329 } 330 return result; 331 } 332 } 333 334 /** 335 * This method converts the given material into particles-usable material. 336 * The texture and glow color are being copied. 337 * The method assumes it receives the Lighting type of material. 338 * @param material 339 * the source material 340 * @param blenderContext 341 * the blender context 342 * @return material converted into particles-usable material 343 */ 344 public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { 345 Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); 346 347 // copying texture 348 MatParam diffuseMap = material.getParam("DiffuseMap"); 349 if (diffuseMap != null) { 350 Texture texture = ((Texture) diffuseMap.getValue()).clone(); 351 352 // applying alpha mask to the texture 353 Image image = texture.getImage(); 354 ByteBuffer sourceBB = image.getData(0); 355 sourceBB.rewind(); 356 int w = image.getWidth(); 357 int h = image.getHeight(); 358 ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); 359 IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); 360 iAlphaMask.setImageSize(w, h); 361 362 for (int x = 0; x < w; ++x) { 363 for (int y = 0; y < h; ++y) { 364 bb.put(sourceBB.get()); 365 bb.put(sourceBB.get()); 366 bb.put(sourceBB.get()); 367 bb.put(iAlphaMask.getAlpha(x, y)); 368 } 369 } 370 371 image = new Image(Format.RGBA8, w, h, bb); 372 texture.setImage(image); 373 374 result.setTextureParam("Texture", VarType.Texture2D, texture); 375 } 376 377 // copying glow color 378 MatParam glowColor = material.getParam("GlowColor"); 379 if (glowColor != null) { 380 ColorRGBA color = (ColorRGBA) glowColor.getValue(); 381 result.setParam("GlowColor", VarType.Vector3, color); 382 } 383 return result; 384 } 385 386 /** 387 * This method indicates if the material has any kind of texture. 388 * 389 * @param material 390 * the material 391 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise 392 */ 393 public boolean hasTexture(Material material) { 394 if (material != null) { 395 if (material.getTextureParam(TEXTURE_TYPE_3D) != null) { 396 return true; 397 } 398 if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) { 399 return true; 400 } 401 if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) { 402 return true; 403 } 404 if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) { 405 return true; 406 } 407 if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) { 408 return true; 409 } 410 if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) { 411 return true; 412 } 413 if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) { 414 return true; 415 } 416 } 417 return false; 418 } 419 420 /** 421 * This method indicates if the material has a texture of a specified type. 422 * 423 * @param material 424 * the material 425 * @param textureType 426 * the type of the texture 427 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise 428 */ 429 public boolean hasTexture(Material material, String textureType) { 430 if (material != null) { 431 return material.getTextureParam(textureType) != null; 432 } 433 return false; 434 } 435 436 /** 437 * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or 438 * curve) but needs to have 'mat' field/ 439 * 440 * @param structureWithMaterials 441 * the structure containing the mesh data 442 * @param blenderContext 443 * the blender context 444 * @return a list of vertices colors, each color belongs to a single vertex 445 * @throws BlenderFileException 446 * this exception is thrown when the blend file structure is somehow invalid or corrupted 447 */ 448 public Material[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { 449 Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); 450 Material[] materials = null; 451 if (ppMaterials.isNotNull()) { 452 List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream()); 453 if (materialStructures != null && materialStructures.size() > 0) { 454 MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); 455 materials = new Material[materialStructures.size()]; 456 int i = 0; 457 for (Structure s : materialStructures) { 458 Material material = (Material) blenderContext.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); 459 if (material == null) { 460 material = materialHelper.toMaterial(s, blenderContext); 461 } 462 materials[i++] = material; 463 } 464 } 465 } 466 return materials; 467 } 468 469 /** 470 * This method converts rgb values to hsv values. 471 * 472 * @param r 473 * red value of the color 474 * @param g 475 * green value of the color 476 * @param b 477 * blue value of the color 478 * @param hsv 479 * hsv values of a color (this table contains the result of the transformation) 480 */ 481 public void rgbToHsv(float r, float g, float b, float[] hsv) { 482 float cmax = r; 483 float cmin = r; 484 cmax = g > cmax ? g : cmax; 485 cmin = g < cmin ? g : cmin; 486 cmax = b > cmax ? b : cmax; 487 cmin = b < cmin ? b : cmin; 488 489 hsv[2] = cmax; /* value */ 490 if (cmax != 0.0) { 491 hsv[1] = (cmax - cmin) / cmax; 492 } else { 493 hsv[1] = 0.0f; 494 hsv[0] = 0.0f; 495 } 496 if (hsv[1] == 0.0) { 497 hsv[0] = -1.0f; 498 } else { 499 float cdelta = cmax - cmin; 500 float rc = (cmax - r) / cdelta; 501 float gc = (cmax - g) / cdelta; 502 float bc = (cmax - b) / cdelta; 503 if (r == cmax) { 504 hsv[0] = bc - gc; 505 } else if (g == cmax) { 506 hsv[0] = 2.0f + rc - bc; 507 } else { 508 hsv[0] = 4.0f + gc - rc; 509 } 510 hsv[0] *= 60.0f; 511 if (hsv[0] < 0.0f) { 512 hsv[0] += 360.0f; 513 } 514 } 515 516 hsv[0] /= 360.0f; 517 if (hsv[0] < 0.0f) { 518 hsv[0] = 0.0f; 519 } 520 } 521 522 /** 523 * This method converts rgb values to hsv values. 524 * 525 * @param h 526 * hue 527 * @param s 528 * saturation 529 * @param v 530 * value 531 * @param rgb 532 * rgb result vector (should have 3 elements) 533 */ 534 public void hsvToRgb(float h, float s, float v, float[] rgb) { 535 h *= 360.0f; 536 if (s == 0.0) { 537 rgb[0] = rgb[1] = rgb[2] = v; 538 } else { 539 if (h == 360) { 540 h = 0; 541 } else { 542 h /= 60; 543 } 544 int i = (int) Math.floor(h); 545 float f = h - i; 546 float p = v * (1.0f - s); 547 float q = v * (1.0f - s * f); 548 float t = v * (1.0f - s * (1.0f - f)); 549 switch (i) { 550 case 0: 551 rgb[0] = v; 552 rgb[1] = t; 553 rgb[2] = p; 554 break; 555 case 1: 556 rgb[0] = q; 557 rgb[1] = v; 558 rgb[2] = p; 559 break; 560 case 2: 561 rgb[0] = p; 562 rgb[1] = v; 563 rgb[2] = t; 564 break; 565 case 3: 566 rgb[0] = p; 567 rgb[1] = q; 568 rgb[2] = v; 569 break; 570 case 4: 571 rgb[0] = t; 572 rgb[1] = p; 573 rgb[2] = v; 574 break; 575 case 5: 576 rgb[0] = v; 577 rgb[1] = p; 578 rgb[2] = q; 579 break; 580 } 581 } 582 } 583 584 @Override 585 public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { 586 return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0; 587 } 588 } 589