package com.jme3.scene.plugins.blender.materials;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
/**
* This class holds the data about the material.
* @author Marcin Roguski (Kaelthas)
*/
public final class MaterialContext implements Savable {
private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName());
// texture mapping types
public static final int MTEX_COL = 0x01;
public static final int MTEX_NOR = 0x02;
public static final int MTEX_SPEC = 0x04;
public static final int MTEX_EMIT = 0x40;
public static final int MTEX_ALPHA = 0x80;
public static final int MTEX_AMB = 0x800;
public static final int FLAG_TRANSPARENT = 0x10000;
/* package */final String name;
/* package */final List<CombinedTexture> loadedTextures;
/* package */final ColorRGBA diffuseColor;
/* package */final DiffuseShader diffuseShader;
/* package */final SpecularShader specularShader;
/* package */final ColorRGBA specularColor;
/* package */final float ambientFactor;
/* package */final float shininess;
/* package */final boolean shadeless;
/* package */final boolean vertexColor;
/* package */final boolean transparent;
/* package */final boolean vTangent;
/* package */FaceCullMode faceCullMode;
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
name = structure.getName();
int mode = ((Number) structure.getFieldValue("mode")).intValue();
shadeless = (mode & 0x4) != 0;
vertexColor = (mode & 0x80) != 0;
vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents
int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
diffuseShader = DiffuseShader.values()[diff_shader];
ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue();
if (shadeless) {
float r = ((Number) structure.getFieldValue("r")).floatValue();
float g = ((Number) structure.getFieldValue("g")).floatValue();
float b = ((Number) structure.getFieldValue("b")).floatValue();
float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
diffuseColor = new ColorRGBA(r, g, b, alpha);
specularShader = null;
specularColor = null;
shininess = 0.0f;
} else {
diffuseColor = this.readDiffuseColor(structure, diffuseShader);
int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue();
specularShader = SpecularShader.values()[spec_shader];
specularColor = this.readSpecularColor(structure);
float shininess = ((Number) structure.getFieldValue("har")).floatValue();// this is (probably) the specular hardness in blender
this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
}
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false);
long flag = ((Number)structure.getFieldValue("flag")).longValue();
if((flag & FLAG_TRANSPARENT) != 0) {
// veryfying if the transparency is present
// (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
// it is not required
boolean transparent = false;
if (diffuseColor != null) {
transparent = diffuseColor.a < 1.0f;
if (loadedTextures.size() > 0) {// texutre covers the material color
diffuseColor.set(1, 1, 1, 1);
}
}
if (specularColor != null) {
transparent = transparent || specularColor.a < 1.0f;
}
this.transparent = transparent;
} else {
transparent = false;
}
}
/**
* @return the name of the material
*/
public String getName() {
return name;
}
/**
* Applies material to a given geometry.
*
* @param geometry
* the geometry
* @param geometriesOMA
* the geometries OMA
* @param userDefinedUVCoordinates
* UV coords defined by user
* @param blenderContext
* the blender context
*/
public void applyMaterial(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
Material material = null;
if (shadeless) {
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
if (!transparent) {
diffuseColor.a = 1;
}
material.setColor("Color", diffuseColor);
} else {
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
material.setBoolean("UseMaterialColors", Boolean.TRUE);
// setting the colors
if (!transparent) {
diffuseColor.a = 1;
}
material.setColor("Diffuse", diffuseColor);
material.setColor("Specular", specularColor);
material.setFloat("Shininess", shininess);
material.setColor("Ambient", new ColorRGBA(ambientFactor, ambientFactor, ambientFactor, 1f));
}
// applying textures
int textureIndex = 0;
if (loadedTextures != null && loadedTextures.size() > 0) {
if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) {
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length);
}
for (CombinedTexture combinedTexture : loadedTextures) {
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
List<Vector2f> uvs = combinedTexture.getResultUVS();
if(uvs != null && uvs.size() > 0) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
}//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file)
if(usedUserUVSet != null) {
userDefinedUVCoordinates = new HashMap<>(userDefinedUVCoordinates);
userDefinedUVCoordinates.remove(usedUserUVSet);
}
} else {
LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
}
}
}
if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
LOGGER.fine("Storing unused, user defined UV coordinates sets.");
if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) {
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length);
}
for (Entry<String, List<Vector2f>> entry : userDefinedUVCoordinates.entrySet()) {
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
List<Vector2f> uvs = entry.getValue();
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
} else {
LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] {
entry.getKey(), TextureHelper.TEXCOORD_TYPES.length
});
}
}
}
// applying additional data
material.setName(name);
if (vertexColor) {
material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true);
}
material.getAdditionalRenderState().setFaceCullMode(faceCullMode != null ? faceCullMode : blenderContext.getBlenderKey().getFaceCullMode());
if (transparent) {
material.setTransparent(true);
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
geometry.setQueueBucket(Bucket.Transparent);
}
geometry.setMaterial(material);
}
/**
* Sets the texture to the given material.
*
* @param material
* the material that we add texture to
* @param mapTo
* the texture mapping type
* @param texture
* the added texture
*/
private void setTexture(Material material, int mapTo, Texture texture) {
switch (mapTo) {
case MTEX_COL:
material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture);
break;
case MTEX_NOR:
material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture);
break;
case MTEX_SPEC:
material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture);
break;
case MTEX_EMIT:
material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture);
break;
case MTEX_ALPHA:
if (!shadeless) {
material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture);
} else {
LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name);
}
break;
case MTEX_AMB:
material.setTexture(MaterialHelper.TEXTURE_TYPE_LIGHTMAP, texture);
break;
default:
LOGGER.severe("Unknown mapping type: " + mapTo);
}
}
/**
* @return <b>true</b> if the material has at least one generated texture and <b>false</b> otherwise
*/
public boolean hasGeneratedTextures() {
if (loadedTextures != null) {
for (CombinedTexture generatedTextures : loadedTextures) {
if (generatedTextures.hasGeneratedTextures()) {
return true;
}
}
}
return false;
}
/**
* This method sets the face cull mode.
* @param faceCullMode
* the face cull mode
*/
public void setFaceCullMode(FaceCullMode faceCullMode) {
this.faceCullMode = faceCullMode;
}
/**
* This method returns the diffuse color.
*
* @param materialStructure
* the material structure
* @param diffuseShader
* the diffuse shader
* @return the diffuse color
*/
private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
// bitwise 'or' of all textures mappings
int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
// diffuse color
float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
if ((commonMapto & 0x01) == 0x01) {// Col
return new ColorRGBA(r, g, b, alpha);
} else {
switch (diffuseShader) {
case FRESNEL:
case ORENNAYAR:
case TOON:
break;// TODO: find what is the proper modification
case MINNAERT:
case LAMBERT:// TODO: check if that is correct
float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
r *= ref;
g *= ref;
b *= ref;
break;
default:
throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
}
return new ColorRGBA(r, g, b, alpha);
}
}
/**
* This method returns a specular color used by the material.
*
* @param materialStructure
* the material structure filled with data
* @return a specular color used by the material
*/
private ColorRGBA readSpecularColor(Structure materialStructure) {
float specularIntensity = ((Number) materialStructure.getFieldValue("spec")).floatValue();
float r = ((Number) materialStructure.getFieldValue("specr")).floatValue() * specularIntensity;
float g = ((Number) materialStructure.getFieldValue("specg")).floatValue() * specularIntensity;
float b = ((Number) materialStructure.getFieldValue("specb")).floatValue() * specularIntensity;
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
return new ColorRGBA(r, g, b, alpha);
}
@Override
public void write(JmeExporter e) throws IOException {
throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!");
}
@Override
public void read(JmeImporter e) throws IOException {
throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!");
}
}