import com.jme3.util.BufferUtils;\r
\r
public class MaterialHelper extends AbstractBlenderHelper {\r
-\r
- private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());\r
- protected static final float DEFAULT_SHININESS = 20.0f;\r
- public static final String TEXTURE_TYPE_COLOR = "ColorMap";\r
- public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";\r
- public static final String TEXTURE_TYPE_NORMAL = "NormalMap";\r
- public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";\r
- public static final String TEXTURE_TYPE_GLOW = "GlowMap";\r
- public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";\r
- public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0);\r
- public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1);\r
- public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2);\r
- public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);\r
- protected final Map<Integer, AlphaMask> alphaMasks = new HashMap<Integer, AlphaMask>();\r
-\r
- /**\r
- * The type of the material's diffuse shader.\r
- */\r
- public static enum DiffuseShader {\r
-\r
- LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL\r
- }\r
-\r
- /**\r
- * The type of the material's specular shader.\r
- */\r
- public static enum SpecularShader {\r
-\r
- COOKTORRENCE, PHONG, BLINN, TOON, WARDISO\r
- }\r
- /** Face cull mode. Should be excplicitly set before this helper is used. */\r
- protected FaceCullMode faceCullMode;\r
-\r
- /**\r
- * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender\r
- * versions.\r
- * \r
- * @param blenderVersion\r
- * the version read from the blend file\r
- */\r
- public MaterialHelper(String blenderVersion) {\r
- super(blenderVersion);\r
- // setting alpha masks\r
- alphaMasks.put(ALPHA_MASK_NONE, new AlphaMask() {\r
-\r
- @Override\r
- public void setImageSize(int width, int height) {\r
- }\r
-\r
- @Override\r
- public byte getAlpha(float x, float y) {\r
- return (byte) 255;\r
- }\r
- });\r
- alphaMasks.put(ALPHA_MASK_CIRCLE, new AlphaMask() {\r
-\r
- private float r;\r
- private float[] center;\r
-\r
- @Override\r
- public void setImageSize(int width, int height) {\r
- r = Math.min(width, height) * 0.5f;\r
- center = new float[]{width * 0.5f, height * 0.5f};\r
- }\r
-\r
- @Override\r
- public byte getAlpha(float x, float y) {\r
- float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));\r
- return (byte) (d >= r ? 0 : 255);\r
- }\r
- });\r
- alphaMasks.put(ALPHA_MASK_CONE, new AlphaMask() {\r
-\r
- private float r;\r
- private float[] center;\r
-\r
- @Override\r
- public void setImageSize(int width, int height) {\r
- r = Math.min(width, height) * 0.5f;\r
- center = new float[]{width * 0.5f, height * 0.5f};\r
- }\r
-\r
- @Override\r
- public byte getAlpha(float x, float y) {\r
- float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));\r
- return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);\r
- }\r
- });\r
- alphaMasks.put(ALPHA_MASK_HYPERBOLE, new AlphaMask() {\r
-\r
- private float r;\r
- private float[] center;\r
-\r
- @Override\r
- public void setImageSize(int width, int height) {\r
- r = Math.min(width, height) * 0.5f;\r
- center = new float[]{width * 0.5f, height * 0.5f};\r
- }\r
-\r
- @Override\r
- public byte getAlpha(float x, float y) {\r
- float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;\r
- return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);\r
- }\r
- });\r
- }\r
-\r
- /**\r
- * This method sets the face cull mode to be used with every loaded material.\r
- * \r
- * @param faceCullMode\r
- * the face cull mode\r
- */\r
- public void setFaceCullMode(FaceCullMode faceCullMode) {\r
- this.faceCullMode = faceCullMode;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public Material toMaterial(Structure structure, DataRepository dataRepository) throws BlenderFileException {\r
- LOGGER.log(Level.INFO, "Loading material.");\r
- if (structure == null) {\r
- return dataRepository.getDefaultMaterial();\r
- }\r
- Material result = (Material) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);\r
- if (result != null) {\r
- return result;\r
- }\r
-\r
- int mode = ((Number) structure.getFieldValue("mode")).intValue();\r
- boolean shadeless = (mode & 0x4) != 0;\r
- boolean vertexColor = (mode & 0x16) != 0;\r
- boolean transparent = (mode & 0x64) != 0;\r
-\r
- if (shadeless) {\r
- result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");\r
- } else {\r
- result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");\r
- }\r
-\r
- result.getAdditionalRenderState().setFaceCullMode(faceCullMode);\r
-\r
- if (transparent) {\r
- result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);\r
- }\r
-\r
- String name = structure.getName();\r
- LOGGER.log(Level.INFO, "Material's name: {0}", name);\r
- if (vertexColor) {\r
- if (shadeless) {\r
- result.setBoolean("VertexColor", true);\r
- } else {\r
- result.setBoolean("UseVertexColor", true);\r
- }\r
- }\r
-\r
- MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
- ColorRGBA diffuseColor = null;\r
- if (shadeless) {\r
- // color of shadeless? doesn't seem to work in blender ..\r
- } else {\r
- result.setBoolean("UseMaterialColors", true);\r
-\r
- // setting the colors\r
- DiffuseShader diffuseShader = materialHelper.getDiffuseShader(structure);\r
- result.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT);\r
- diffuseColor = materialHelper.getDiffuseColor(structure, diffuseShader);\r
- result.setColor("Diffuse", diffuseColor);\r
-\r
- SpecularShader specularShader = materialHelper.getSpecularShader(structure);\r
- result.setBoolean("WardIso", specularShader == SpecularShader.WARDISO);\r
- result.setColor("Specular", materialHelper.getSpecularColor(structure, specularShader));\r
-\r
- result.setColor("Ambient", materialHelper.getAmbientColor(structure));\r
- result.setFloat("Shininess", materialHelper.getShininess(structure));\r
- }\r
-\r
- // texture\r
- if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0) {\r
- TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);\r
- DynamicArray<Pointer> mtexs = (DynamicArray<Pointer>) structure.getFieldValue("mtex");\r
- for (int i = 0; i < mtexs.getTotalSize(); ++i) {\r
- Pointer p = mtexs.get(i);\r
- if (!p.isNull()) {\r
- List<Structure> mtex = p.fetchData(dataRepository.getInputStream());\r
- if (mtex.size() == 1) {\r
- Structure textureLink = mtex.get(0);\r
- int texflag = ((Number) textureLink.getFieldValue("texflag")).intValue();\r
- // int texco = ((Number) textureLink.getFieldValue("texco")).intValue();\r
- boolean negateTexture = (texflag & 0x04) == 0;\r
-\r
- // if(texco == 0x10) {//TEXCO_UV (this is only supported now)\r
- int mapto = ((Number) textureLink.getFieldValue("mapto")).intValue();\r
- if (mapto != 0) {\r
- Pointer pTex = (Pointer) textureLink.getFieldValue("tex");\r
- Structure tex = pTex.fetchData(dataRepository.getInputStream()).get(0);\r
- Texture texture = textureHelper.getTexture(tex, dataRepository);\r
- if (texture != null) {\r
- if ((mapto & 0x01) != 0) {// Col\r
- if (!shadeless){\r
- result.setBoolean("UseMaterialColors", false);\r
- }\r
- \r
- // blending the texture with material color and texture's defined color\r
- int blendType = ((Number) textureLink.getFieldValue("blendtype")).intValue();\r
- float[] color = new float[]{((Number) textureLink.getFieldValue("r")).floatValue(), ((Number) textureLink.getFieldValue("g")).floatValue(), ((Number) textureLink.getFieldValue("b")).floatValue()};\r
- float colfac = ((Number) textureLink.getFieldValue("colfac")).floatValue();\r
- texture = textureHelper.blendTexture(diffuseColor.getColorArray(), texture, color, colfac, blendType, negateTexture, dataRepository);\r
- texture.setWrap(WrapMode.Repeat);\r
- if (shadeless) {\r
- result.setTexture(TEXTURE_TYPE_COLOR, texture);\r
- } else {\r
- result.setTexture(TEXTURE_TYPE_DIFFUSE, texture);\r
- }\r
- }\r
- if ((mapto & 0x02) != 0) {// Nor\r
- result.setTexture(TEXTURE_TYPE_NORMAL, texture);\r
- }\r
- if ((mapto & 0x20) != 0) {// Spec\r
- result.setTexture(TEXTURE_TYPE_SPECULAR, texture);\r
- }\r
- if ((mapto & 0x40) != 0) {// Emit\r
- result.setTexture(TEXTURE_TYPE_GLOW, texture);\r
- }\r
- if ((mapto & 0x80) != 0) {// Alpha\r
- result.setTexture(TEXTURE_TYPE_ALPHA, texture);\r
- }\r
- } else {\r
- LOGGER.log(Level.WARNING, "Texture not found!");\r
- }\r
- }\r
- // } else {\r
- // Pointer pTex = (Pointer)textureLink.getFieldValue("tex");\r
- // List<Structure> texs = pTex.fetchData(dataRepository.getInputStream());\r
- // Structure tex = texs.get(0);\r
- // LOGGER.log(Level.WARNING, "Unsupported texture type: " + texco);\r
- // }\r
- } else {\r
- LOGGER.log(Level.WARNING, "Many textures. Not solved yet!");// TODO\r
- }\r
- }\r
- }\r
- }\r
- dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);\r
- return result;\r
- }\r
-\r
- /**\r
- * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but\r
- * returned itself.\r
- * \r
- * @param material\r
- * a material to be cloned without textures\r
- * @param imageType\r
- * type of image defined by blender; the constants are defined in TextureHelper\r
- * @return material without textures of a specified type\r
- */\r
- public Material getNonTexturedMaterial(Material material, int imageType) {\r
- String[] textureParamNames = new String[]{TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA};\r
- Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);\r
- for (String textureParamName : textureParamNames) {\r
- MatParamTexture matParamTexture = material.getTextureParam(textureParamName);\r
- if (matParamTexture != null) {\r
- textures.put(textureParamName, matParamTexture.getTextureValue());\r
- }\r
- }\r
- if (textures.isEmpty()) {\r
- return material;\r
- } else {\r
- // clear all textures first so that wo de not waste resources cloning them\r
- for (Entry<String, Texture> textureParamName : textures.entrySet()) {\r
- String name = textureParamName.getValue().getName();\r
- try {\r
- int type = Integer.parseInt(name);\r
- if (type == imageType) {\r
- material.clearParam(textureParamName.getKey());\r
- }\r
- } catch (NumberFormatException e) {\r
- LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name);\r
- }\r
- }\r
- Material result = material.clone();\r
- // put the textures back in place\r
- for (Entry<String, Texture> textureEntry : textures.entrySet()) {\r
- material.setTexture(textureEntry.getKey(), textureEntry.getValue());\r
- }\r
- return result;\r
- }\r
- }\r
-\r
- /**\r
- * This method converts the given material into particles-usable material.\r
- * The texture and glow color are being copied.\r
- * The method assumes it receives the Lighting type of material.\r
- * @param material\r
- * the source material\r
- * @param dataRepository\r
- * the data repository\r
- * @return material converted into particles-usable material\r
- */\r
- public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, DataRepository dataRepository) {\r
- Material result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");\r
-\r
- // copying texture\r
- MatParam diffuseMap = material.getParam("DiffuseMap");\r
- if (diffuseMap != null) {\r
- Texture texture = ((Texture) diffuseMap.getValue()).clone();\r
-\r
- // applying alpha mask to the texture\r
- Image image = texture.getImage();\r
- ByteBuffer sourceBB = image.getData(0);\r
- sourceBB.rewind();\r
- int w = image.getWidth();\r
- int h = image.getHeight();\r
- ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);\r
- AlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);\r
- iAlphaMask.setImageSize(w, h);\r
-\r
- for (int x = 0; x < w; ++x) {\r
- for (int y = 0; y < h; ++y) {\r
- bb.put(sourceBB.get());\r
- bb.put(sourceBB.get());\r
- bb.put(sourceBB.get());\r
- bb.put(iAlphaMask.getAlpha(x, y));\r
- }\r
- }\r
-\r
- image = new Image(Format.RGBA8, w, h, bb);\r
- texture.setImage(image);\r
-\r
- result.setTextureParam("Texture", VarType.Texture2D, texture);\r
- }\r
-\r
- // copying glow color\r
- MatParam glowColor = material.getParam("GlowColor");\r
- if (glowColor != null) {\r
- ColorRGBA color = (ColorRGBA) glowColor.getValue();\r
- result.setParam("GlowColor", VarType.Vector3, color);\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * This method indicates if the material has a texture of a specified type.\r
- * \r
- * @param material\r
- * the material\r
- * @param textureType\r
- * the type of the texture\r
- * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise\r
- */\r
- public boolean hasTexture(Material material, String textureType) {\r
- if (material != null) {\r
- return material.getTextureParam(textureType) != null;\r
- }\r
- return false;\r
- }\r
-\r
- /**\r
- * This method returns the diffuse color\r
- * \r
- * @param materialStructure\r
- * @param diffuseShader\r
- * @return\r
- */\r
- public ColorRGBA getDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {\r
- // bitwise 'or' of all textures mappings\r
- int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();\r
-\r
- // diffuse color\r
- float r = ((Number) materialStructure.getFieldValue("r")).floatValue();\r
- float g = ((Number) materialStructure.getFieldValue("g")).floatValue();\r
- float b = ((Number) materialStructure.getFieldValue("b")).floatValue();\r
- float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
- if ((commonMapto & 0x01) == 0x01) {// Col\r
- return new ColorRGBA(r, g, b, alpha);\r
- } else {\r
- switch (diffuseShader) {\r
- case FRESNEL:\r
- case ORENNAYAR:\r
- case TOON:\r
- break;// TODO: find what is the proper modification\r
- case MINNAERT:\r
- case LAMBERT:// TODO: check if that is correct\r
- float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();\r
- r *= ref;\r
- g *= ref;\r
- b *= ref;\r
- break;\r
- default:\r
- throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());\r
- }\r
- return new ColorRGBA(r, g, b, alpha);\r
- }\r
- }\r
-\r
- /**\r
- * This method returns an enum describing the type of a diffuse shader used by this material.\r
- * \r
- * @param materialStructure\r
- * the material structure filled with data\r
- * @return an enum describing the type of a diffuse shader used by this material\r
- */\r
- public DiffuseShader getDiffuseShader(Structure materialStructure) {\r
- int diff_shader = ((Number) materialStructure.getFieldValue("diff_shader")).intValue();\r
- return DiffuseShader.values()[diff_shader];\r
- }\r
-\r
- /**\r
- * This method returns an ambient color used by the material.\r
- * \r
- * @param materialStructure\r
- * the material structure filled with data\r
- * @return an ambient color used by the material\r
- */\r
- public ColorRGBA getAmbientColor(Structure materialStructure) {\r
- float r = ((Number) materialStructure.getFieldValue("ambr")).floatValue();\r
- float g = ((Number) materialStructure.getFieldValue("ambg")).floatValue();\r
- float b = ((Number) materialStructure.getFieldValue("ambb")).floatValue();\r
- float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
- return new ColorRGBA(r, g, b, alpha);\r
- }\r
-\r
- /**\r
- * This method returns an enum describing the type of a specular shader used by this material.\r
- * \r
- * @param materialStructure\r
- * the material structure filled with data\r
- * @return an enum describing the type of a specular shader used by this material\r
- */\r
- public SpecularShader getSpecularShader(Structure materialStructure) {\r
- int spec_shader = ((Number) materialStructure.getFieldValue("spec_shader")).intValue();\r
- return SpecularShader.values()[spec_shader];\r
- }\r
-\r
- /**\r
- * This method returns a specular color used by the material.\r
- * \r
- * @param materialStructure\r
- * the material structure filled with data\r
- * @return a specular color used by the material\r
- */\r
- public ColorRGBA getSpecularColor(Structure materialStructure, SpecularShader specularShader) {\r
- float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();\r
- float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();\r
- float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();\r
- float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
- switch (specularShader) {\r
- case BLINN:\r
- case COOKTORRENCE:\r
- case TOON:\r
- case WARDISO:// TODO: find what is the proper modification\r
- break;\r
- case PHONG:// TODO: check if that is correct\r
- float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();\r
- r *= spec * 0.5f;\r
- g *= spec * 0.5f;\r
- b *= spec * 0.5f;\r
- break;\r
- default:\r
- throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());\r
- }\r
- return new ColorRGBA(r, g, b, alpha);\r
- }\r
-\r
- /**\r
- * This method returns the sihiness of this material or DEFAULT_SHININESS value if not present.\r
- * \r
- * @param materialStructure\r
- * the material structure filled with data\r
- * @return the sihiness of this material or DEFAULT_SHININESS value if not present\r
- */\r
- public float getShininess(Structure materialStructure) {\r
- float shininess = ((Number) materialStructure.getFieldValue("emit")).floatValue();\r
- return shininess > 0.0f ? shininess : DEFAULT_SHININESS;\r
- }\r
-\r
- /**\r
- * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or\r
- * curve) but needs to have 'mat' field/\r
- * \r
- * @param structureWithMaterials\r
- * the structure containing the mesh data\r
- * @param dataRepository\r
- * the data repository\r
- * @return a list of vertices colors, each color belongs to a single vertex\r
- * @throws BlenderFileException\r
- * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
- */\r
- public Material[] getMaterials(Structure structureWithMaterials, DataRepository dataRepository) throws BlenderFileException {\r
- Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");\r
- Material[] materials = null;\r
- if (!ppMaterials.isNull()) {\r
- List<Structure> materialStructures = ppMaterials.fetchData(dataRepository.getInputStream());\r
- if (materialStructures != null && materialStructures.size() > 0) {\r
- MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
- materials = new Material[materialStructures.size()];\r
- int i = 0;\r
- for (Structure s : materialStructures) {\r
- Material material = (Material) dataRepository.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);\r
- if (material == null) {\r
- material = materialHelper.toMaterial(s, dataRepository);\r
- }\r
- materials[i++] = material;\r
- }\r
- }\r
- }\r
- return materials;\r
- }\r
-\r
- /**\r
- * This method converts rgb values to hsv values.\r
- * \r
- * @param rgb\r
- * rgb values of the color\r
- * @param hsv\r
- * hsv values of a color (this table contains the result of the transformation)\r
- */\r
- public void rgbToHsv(float r, float g, float b, float[] hsv) {\r
- float cmax = r;\r
- float cmin = r;\r
- cmax = g > cmax ? g : cmax;\r
- cmin = g < cmin ? g : cmin;\r
- cmax = b > cmax ? b : cmax;\r
- cmin = b < cmin ? b : cmin;\r
-\r
- hsv[2] = cmax; /* value */\r
- if (cmax != 0.0) {\r
- hsv[1] = (cmax - cmin) / cmax;\r
- } else {\r
- hsv[1] = 0.0f;\r
- hsv[0] = 0.0f;\r
- }\r
- if (hsv[1] == 0.0) {\r
- hsv[0] = -1.0f;\r
- } else {\r
- float cdelta = cmax - cmin;\r
- float rc = (cmax - r) / cdelta;\r
- float gc = (cmax - g) / cdelta;\r
- float bc = (cmax - b) / cdelta;\r
- if (r == cmax) {\r
- hsv[0] = bc - gc;\r
- } else if (g == cmax) {\r
- hsv[0] = 2.0f + rc - bc;\r
- } else {\r
- hsv[0] = 4.0f + gc - rc;\r
- }\r
- hsv[0] *= 60.0f;\r
- if (hsv[0] < 0.0f) {\r
- hsv[0] += 360.0f;\r
- }\r
- }\r
-\r
- hsv[0] /= 360.0f;\r
- if (hsv[0] < 0.0f) {\r
- hsv[0] = 0.0f;\r
- }\r
- }\r
-\r
- /**\r
- * This method converts rgb values to hsv values.\r
- * \r
- * @param h\r
- * hue\r
- * @param s\r
- * saturation\r
- * @param v\r
- * value\r
- * @param rgb\r
- * rgb result vector (should have 3 elements)\r
- */\r
- public void hsvToRgb(float h, float s, float v, float[] rgb) {\r
- h *= 360.0f;\r
- if (s == 0.0) {\r
- rgb[0] = rgb[1] = rgb[2] = v;\r
- } else {\r
- if (h == 360) {\r
- h = 0;\r
- } else {\r
- h /= 60;\r
- }\r
- int i = (int) Math.floor(h);\r
- float f = h - i;\r
- float p = v * (1.0f - s);\r
- float q = v * (1.0f - s * f);\r
- float t = v * (1.0f - s * (1.0f - f));\r
- switch (i) {\r
- case 0:\r
- rgb[0] = v;\r
- rgb[1] = t;\r
- rgb[2] = p;\r
- break;\r
- case 1:\r
- rgb[0] = q;\r
- rgb[1] = v;\r
- rgb[2] = p;\r
- break;\r
- case 2:\r
- rgb[0] = p;\r
- rgb[1] = v;\r
- rgb[2] = t;\r
- break;\r
- case 3:\r
- rgb[0] = p;\r
- rgb[1] = q;\r
- rgb[2] = v;\r
- break;\r
- case 4:\r
- rgb[0] = t;\r
- rgb[1] = p;\r
- rgb[2] = v;\r
- break;\r
- case 5:\r
- rgb[0] = v;\r
- rgb[1] = p;\r
- rgb[2] = q;\r
- break;\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * An interface used in calculating alpha mask during particles' texture calculations.\r
- * @author Marcin Roguski (Kaelthas)\r
- */\r
- protected static interface AlphaMask {\r
-\r
- /**\r
- * This method sets the size of the texture's image.\r
- * @param width\r
- * the width of the image\r
- * @param height\r
- * the height of the image\r
- */\r
- void setImageSize(int width, int height);\r
-\r
- /**\r
- * This method returns the alpha value for the specified texture position.\r
- * @param x\r
- * the X coordinate of the texture position\r
- * @param y\r
- * the Y coordinate of the texture position\r
- * @return the alpha value for the specified texture position\r
- */\r
- byte getAlpha(float x, float y);\r
- }\r
+ private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());\r
+ protected static final float DEFAULT_SHININESS = 20.0f;\r
+\r
+ public static final String TEXTURE_TYPE_COLOR = "ColorMap";\r
+ public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";\r
+ public static final String TEXTURE_TYPE_NORMAL = "NormalMap";\r
+ public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";\r
+ public static final String TEXTURE_TYPE_GLOW = "GlowMap";\r
+ public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";\r
+\r
+ public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0);\r
+ public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1);\r
+ public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2);\r
+ public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);\r
+ protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>();\r
+\r
+ /**\r
+ * The type of the material's diffuse shader.\r
+ */\r
+ public static enum DiffuseShader {\r
+ LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL\r
+ }\r
+\r
+ /**\r
+ * The type of the material's specular shader.\r
+ */\r
+ public static enum SpecularShader {\r
+ COOKTORRENCE, PHONG, BLINN, TOON, WARDISO\r
+ }\r
+\r
+ /** Face cull mode. Should be excplicitly set before this helper is used. */\r
+ protected FaceCullMode faceCullMode;\r
+\r
+ /**\r
+ * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender\r
+ * versions.\r
+ * \r
+ * @param blenderVersion\r
+ * the version read from the blend file\r
+ */\r
+ public MaterialHelper(String blenderVersion) {\r
+ super(blenderVersion);\r
+ // setting alpha masks\r
+ alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {\r
+ @Override\r
+ public void setImageSize(int width, int height) {}\r
+\r
+ @Override\r
+ public byte getAlpha(float x, float y) {\r
+ return (byte) 255;\r
+ }\r
+ });\r
+ alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {\r
+ private float r;\r
+ private float[] center;\r
+\r
+ @Override\r
+ public void setImageSize(int width, int height) {\r
+ r = Math.min(width, height) * 0.5f;\r
+ center = new float[] { width * 0.5f, height * 0.5f };\r
+ }\r
+\r
+ @Override\r
+ public byte getAlpha(float x, float y) {\r
+ float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));\r
+ return (byte) (d >= r ? 0 : 255);\r
+ }\r
+ });\r
+ alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {\r
+ private float r;\r
+ private float[] center;\r
+\r
+ @Override\r
+ public void setImageSize(int width, int height) {\r
+ r = Math.min(width, height) * 0.5f;\r
+ center = new float[] { width * 0.5f, height * 0.5f };\r
+ }\r
+\r
+ @Override\r
+ public byte getAlpha(float x, float y) {\r
+ float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));\r
+ return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);\r
+ }\r
+ });\r
+ alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {\r
+ private float r;\r
+ private float[] center;\r
+\r
+ @Override\r
+ public void setImageSize(int width, int height) {\r
+ r = Math.min(width, height) * 0.5f;\r
+ center = new float[] { width * 0.5f, height * 0.5f };\r
+ }\r
+\r
+ @Override\r
+ public byte getAlpha(float x, float y) {\r
+ float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;\r
+ return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);\r
+ }\r
+ });\r
+ }\r
+\r
+ /**\r
+ * This method sets the face cull mode to be used with every loaded material.\r
+ * \r
+ * @param faceCullMode\r
+ * the face cull mode\r
+ */\r
+ public void setFaceCullMode(FaceCullMode faceCullMode) {\r
+ this.faceCullMode = faceCullMode;\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public Material toMaterial(Structure structure, DataRepository dataRepository) throws BlenderFileException {\r
+ LOGGER.log(Level.INFO, "Loading material.");\r
+ if (structure == null) {\r
+ return dataRepository.getDefaultMaterial();\r
+ }\r
+ Material result = (Material) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);\r
+ if (result != null) {\r
+ return result;\r
+ }\r
+\r
+ int mode = ((Number) structure.getFieldValue("mode")).intValue();\r
+ boolean shadeless = (mode & 0x4) != 0;\r
+ boolean vertexColor = (mode & 0x16) != 0;\r
+ boolean transparent = (mode & 0x64) != 0;\r
+\r
+ if (shadeless) {\r
+ result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");\r
+ } else {\r
+ result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");\r
+ }\r
+\r
+ result.getAdditionalRenderState().setFaceCullMode(faceCullMode);\r
+\r
+ if (transparent) {\r
+ result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);\r
+ }\r
+\r
+ String name = structure.getName();\r
+ LOGGER.log(Level.INFO, "Material's name: {0}", name);\r
+ if (vertexColor) {\r
+ result.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true);\r
+ }\r
+\r
+ MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
+ ColorRGBA diffuseColor = null;\r
+ if (shadeless) {\r
+ // color of shadeless? doesn't seem to work in blender ..\r
+ } else {\r
+ result.setBoolean("UseMaterialColors", Boolean.TRUE);\r
+\r
+ // setting the colors\r
+ DiffuseShader diffuseShader = materialHelper.getDiffuseShader(structure);\r
+ result.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT);\r
+ diffuseColor = materialHelper.getDiffuseColor(structure, diffuseShader);\r
+ result.setColor("Diffuse", diffuseColor);\r
+\r
+ SpecularShader specularShader = materialHelper.getSpecularShader(structure);\r
+ result.setBoolean("WardIso", specularShader == SpecularShader.WARDISO);\r
+ result.setColor("Specular", materialHelper.getSpecularColor(structure, specularShader));\r
+\r
+ result.setColor("Ambient", materialHelper.getAmbientColor(structure));\r
+ result.setFloat("Shininess", materialHelper.getShininess(structure));\r
+ }\r
+\r
+ // texture\r
+ if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0) {\r
+ TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);\r
+ DynamicArray<Pointer> mtexs = (DynamicArray<Pointer>) structure.getFieldValue("mtex");\r
+ for (int i = 0; i < mtexs.getTotalSize(); ++i) {\r
+ Pointer p = mtexs.get(i);\r
+ if (!p.isNull()) {\r
+ List<Structure> mtex = p.fetchData(dataRepository.getInputStream());\r
+ if (mtex.size() == 1) {\r
+ Structure textureLink = mtex.get(0);\r
+ int texflag = ((Number) textureLink.getFieldValue("texflag")).intValue();\r
+ // int texco = ((Number) textureLink.getFieldValue("texco")).intValue();\r
+ boolean negateTexture = (texflag & 0x04) == 0;\r
+\r
+ // if(texco == 0x10) {//TEXCO_UV (this is only supported now)\r
+ int mapto = ((Number) textureLink.getFieldValue("mapto")).intValue();\r
+ if (mapto != 0) {\r
+ Pointer pTex = (Pointer) textureLink.getFieldValue("tex");\r
+ Structure tex = pTex.fetchData(dataRepository.getInputStream()).get(0);\r
+ Texture texture = textureHelper.getTexture(tex, dataRepository);\r
+ if (texture != null) {\r
+ if ((mapto & 0x01) != 0) {// Col\r
+ result.setBoolean("UseMaterialColors", Boolean.FALSE);\r
+ // blending the texture with material color and texture's defined color\r
+ int blendType = ((Number) textureLink.getFieldValue("blendtype")).intValue();\r
+ float[] color = new float[] { ((Number) textureLink.getFieldValue("r")).floatValue(), ((Number) textureLink.getFieldValue("g")).floatValue(), ((Number) textureLink.getFieldValue("b")).floatValue() };\r
+ float colfac = ((Number) textureLink.getFieldValue("colfac")).floatValue();\r
+ texture = textureHelper.blendTexture(diffuseColor.getColorArray(), texture, color, colfac, blendType, negateTexture, dataRepository);\r
+ texture.setWrap(WrapMode.Repeat);\r
+ if (shadeless) {\r
+ result.setTexture(TEXTURE_TYPE_COLOR, texture);\r
+ } else {\r
+ result.setTexture(TEXTURE_TYPE_DIFFUSE, texture);\r
+ }\r
+ }\r
+ if ((mapto & 0x02) != 0) {// Nor\r
+ result.setTexture(TEXTURE_TYPE_NORMAL, texture);\r
+ if (vertexColor) {\r
+ result.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", false);\r
+ }\r
+ }\r
+ if ((mapto & 0x04) != 0) {// Spec\r
+ result.setTexture(TEXTURE_TYPE_SPECULAR, texture);\r
+ }\r
+ if ((mapto & 0x40) != 0) {// Emit\r
+ result.setTexture(TEXTURE_TYPE_GLOW, texture);\r
+ }\r
+ if ((mapto & 0x80) != 0) {// Alpha\r
+ result.setTexture(TEXTURE_TYPE_ALPHA, texture);\r
+ }\r
+ } else {\r
+ LOGGER.log(Level.WARNING, "Texture not found!");\r
+ }\r
+ }\r
+ // } else {\r
+ // Pointer pTex = (Pointer)textureLink.getFieldValue("tex");\r
+ // List<Structure> texs = pTex.fetchData(dataRepository.getInputStream());\r
+ // Structure tex = texs.get(0);\r
+ // LOGGER.log(Level.WARNING, "Unsupported texture type: " + texco);\r
+ // }\r
+ } else {\r
+ LOGGER.log(Level.WARNING, "Many textures. Not solved yet!");// TODO\r
+ }\r
+ }\r
+ }\r
+ }\r
+ dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but\r
+ * returned itself.\r
+ * \r
+ * @param material\r
+ * a material to be cloned without textures\r
+ * @param imageType\r
+ * type of image defined by blender; the constants are defined in TextureHelper\r
+ * @return material without textures of a specified type\r
+ */\r
+ public Material getNonTexturedMaterial(Material material, int imageType) {\r
+ String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA };\r
+ Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);\r
+ for (String textureParamName : textureParamNames) {\r
+ MatParamTexture matParamTexture = material.getTextureParam(textureParamName);\r
+ if (matParamTexture != null) {\r
+ textures.put(textureParamName, matParamTexture.getTextureValue());\r
+ }\r
+ }\r
+ if (textures.isEmpty()) {\r
+ return material;\r
+ } else {\r
+ // clear all textures first so that wo de not waste resources cloning them\r
+ for (Entry<String, Texture> textureParamName : textures.entrySet()) {\r
+ String name = textureParamName.getValue().getName();\r
+ try {\r
+ int type = Integer.parseInt(name);\r
+ if (type == imageType) {\r
+ material.clearParam(textureParamName.getKey());\r
+ }\r
+ } catch (NumberFormatException e) {\r
+ LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name);\r
+ }\r
+ }\r
+ Material result = material.clone();\r
+ // put the textures back in place\r
+ for (Entry<String, Texture> textureEntry : textures.entrySet()) {\r
+ material.setTexture(textureEntry.getKey(), textureEntry.getValue());\r
+ }\r
+ return result;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * This method converts the given material into particles-usable material.\r
+ * The texture and glow color are being copied.\r
+ * The method assumes it receives the Lighting type of material.\r
+ * @param material\r
+ * the source material\r
+ * @param dataRepository\r
+ * the data repository\r
+ * @return material converted into particles-usable material\r
+ */\r
+ public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, DataRepository dataRepository) {\r
+ Material result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");\r
+\r
+ // copying texture\r
+ MatParam diffuseMap = material.getParam("DiffuseMap");\r
+ if (diffuseMap != null) {\r
+ Texture texture = ((Texture) diffuseMap.getValue()).clone();\r
+\r
+ // applying alpha mask to the texture\r
+ Image image = texture.getImage();\r
+ ByteBuffer sourceBB = image.getData(0);\r
+ sourceBB.rewind();\r
+ int w = image.getWidth();\r
+ int h = image.getHeight();\r
+ ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);\r
+ IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);\r
+ iAlphaMask.setImageSize(w, h);\r
+\r
+ for (int x = 0; x < w; ++x) {\r
+ for (int y = 0; y < h; ++y) {\r
+ bb.put(sourceBB.get());\r
+ bb.put(sourceBB.get());\r
+ bb.put(sourceBB.get());\r
+ bb.put(iAlphaMask.getAlpha(x, y));\r
+ }\r
+ }\r
+\r
+ image = new Image(Format.RGBA8, w, h, bb);\r
+ texture.setImage(image);\r
+\r
+ result.setTextureParam("Texture", VarType.Texture2D, texture);\r
+ }\r
+\r
+ // copying glow color\r
+ MatParam glowColor = material.getParam("GlowColor");\r
+ if (glowColor != null) {\r
+ ColorRGBA color = (ColorRGBA) glowColor.getValue();\r
+ result.setParam("GlowColor", VarType.Vector3, color);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * This method indicates if the material has a texture of a specified type.\r
+ * \r
+ * @param material\r
+ * the material\r
+ * @param textureType\r
+ * the type of the texture\r
+ * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise\r
+ */\r
+ public boolean hasTexture(Material material, String textureType) {\r
+ if (material != null) {\r
+ return material.getTextureParam(textureType) != null;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * This method returns the diffuse color\r
+ * \r
+ * @param materialStructure\r
+ * @param diffuseShader\r
+ * @return\r
+ */\r
+ public ColorRGBA getDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {\r
+ // bitwise 'or' of all textures mappings\r
+ int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();\r
+\r
+ // diffuse color\r
+ float r = ((Number) materialStructure.getFieldValue("r")).floatValue();\r
+ float g = ((Number) materialStructure.getFieldValue("g")).floatValue();\r
+ float b = ((Number) materialStructure.getFieldValue("b")).floatValue();\r
+ float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
+ if ((commonMapto & 0x01) == 0x01) {// Col\r
+ return new ColorRGBA(r, g, b, alpha);\r
+ } else {\r
+ switch (diffuseShader) {\r
+ case FRESNEL:\r
+ case ORENNAYAR:\r
+ case TOON:\r
+ break;// TODO: find what is the proper modification\r
+ case MINNAERT:\r
+ case LAMBERT:// TODO: check if that is correct\r
+ float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();\r
+ r *= ref;\r
+ g *= ref;\r
+ b *= ref;\r
+ break;\r
+ default:\r
+ throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());\r
+ }\r
+ return new ColorRGBA(r, g, b, alpha);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * This method returns an enum describing the type of a diffuse shader used by this material.\r
+ * \r
+ * @param materialStructure\r
+ * the material structure filled with data\r
+ * @return an enum describing the type of a diffuse shader used by this material\r
+ */\r
+ public DiffuseShader getDiffuseShader(Structure materialStructure) {\r
+ int diff_shader = ((Number) materialStructure.getFieldValue("diff_shader")).intValue();\r
+ return DiffuseShader.values()[diff_shader];\r
+ }\r
+\r
+ /**\r
+ * This method returns an ambient color used by the material.\r
+ * \r
+ * @param materialStructure\r
+ * the material structure filled with data\r
+ * @return an ambient color used by the material\r
+ */\r
+ public ColorRGBA getAmbientColor(Structure materialStructure) {\r
+ float r = ((Number) materialStructure.getFieldValue("ambr")).floatValue();\r
+ float g = ((Number) materialStructure.getFieldValue("ambg")).floatValue();\r
+ float b = ((Number) materialStructure.getFieldValue("ambb")).floatValue();\r
+ float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
+ return new ColorRGBA(r, g, b, alpha);\r
+ }\r
+\r
+ /**\r
+ * This method returns an enum describing the type of a specular shader used by this material.\r
+ * \r
+ * @param materialStructure\r
+ * the material structure filled with data\r
+ * @return an enum describing the type of a specular shader used by this material\r
+ */\r
+ public SpecularShader getSpecularShader(Structure materialStructure) {\r
+ int spec_shader = ((Number) materialStructure.getFieldValue("spec_shader")).intValue();\r
+ return SpecularShader.values()[spec_shader];\r
+ }\r
+\r
+ /**\r
+ * This method returns a specular color used by the material.\r
+ * \r
+ * @param materialStructure\r
+ * the material structure filled with data\r
+ * @return a specular color used by the material\r
+ */\r
+ public ColorRGBA getSpecularColor(Structure materialStructure, SpecularShader specularShader) {\r
+ float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();\r
+ float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();\r
+ float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();\r
+ float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();\r
+ switch (specularShader) {\r
+ case BLINN:\r
+ case COOKTORRENCE:\r
+ case TOON:\r
+ case WARDISO:// TODO: find what is the proper modification\r
+ break;\r
+ case PHONG:// TODO: check if that is correct\r
+ float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();\r
+ r *= spec * 0.5f;\r
+ g *= spec * 0.5f;\r
+ b *= spec * 0.5f;\r
+ break;\r
+ default:\r
+ throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());\r
+ }\r
+ return new ColorRGBA(r, g, b, alpha);\r
+ }\r
+\r
+ /**\r
+ * This method returns the sihiness of this material or DEFAULT_SHININESS value if not present.\r
+ * \r
+ * @param materialStructure\r
+ * the material structure filled with data\r
+ * @return the sihiness of this material or DEFAULT_SHININESS value if not present\r
+ */\r
+ public float getShininess(Structure materialStructure) {\r
+ float shininess = ((Number) materialStructure.getFieldValue("emit")).floatValue();\r
+ return shininess > 0.0f ? shininess : DEFAULT_SHININESS;\r
+ }\r
+\r
+ /**\r
+ * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or\r
+ * curve) but needs to have 'mat' field/\r
+ * \r
+ * @param structureWithMaterials\r
+ * the structure containing the mesh data\r
+ * @param dataRepository\r
+ * the data repository\r
+ * @return a list of vertices colors, each color belongs to a single vertex\r
+ * @throws BlenderFileException\r
+ * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
+ */\r
+ public Material[] getMaterials(Structure structureWithMaterials, DataRepository dataRepository) throws BlenderFileException {\r
+ Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");\r
+ Material[] materials = null;\r
+ if (!ppMaterials.isNull()) {\r
+ List<Structure> materialStructures = ppMaterials.fetchData(dataRepository.getInputStream());\r
+ if (materialStructures != null && materialStructures.size() > 0) {\r
+ MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
+ materials = new Material[materialStructures.size()];\r
+ int i = 0;\r
+ for (Structure s : materialStructures) {\r
+ Material material = (Material) dataRepository.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);\r
+ if (material == null) {\r
+ material = materialHelper.toMaterial(s, dataRepository);\r
+ }\r
+ materials[i++] = material;\r
+ }\r
+ }\r
+ }\r
+ return materials;\r
+ }\r
+\r
+ /**\r
+ * This method converts rgb values to hsv values.\r
+ * \r
+ * @param rgb\r
+ * rgb values of the color\r
+ * @param hsv\r
+ * hsv values of a color (this table contains the result of the transformation)\r
+ */\r
+ public void rgbToHsv(float r, float g, float b, float[] hsv) {\r
+ float cmax = r;\r
+ float cmin = r;\r
+ cmax = g > cmax ? g : cmax;\r
+ cmin = g < cmin ? g : cmin;\r
+ cmax = b > cmax ? b : cmax;\r
+ cmin = b < cmin ? b : cmin;\r
+\r
+ hsv[2] = cmax; /* value */\r
+ if (cmax != 0.0) {\r
+ hsv[1] = (cmax - cmin) / cmax;\r
+ } else {\r
+ hsv[1] = 0.0f;\r
+ hsv[0] = 0.0f;\r
+ }\r
+ if (hsv[1] == 0.0) {\r
+ hsv[0] = -1.0f;\r
+ } else {\r
+ float cdelta = cmax - cmin;\r
+ float rc = (cmax - r) / cdelta;\r
+ float gc = (cmax - g) / cdelta;\r
+ float bc = (cmax - b) / cdelta;\r
+ if (r == cmax) {\r
+ hsv[0] = bc - gc;\r
+ } else if (g == cmax) {\r
+ hsv[0] = 2.0f + rc - bc;\r
+ } else {\r
+ hsv[0] = 4.0f + gc - rc;\r
+ }\r
+ hsv[0] *= 60.0f;\r
+ if (hsv[0] < 0.0f) {\r
+ hsv[0] += 360.0f;\r
+ }\r
+ }\r
+\r
+ hsv[0] /= 360.0f;\r
+ if (hsv[0] < 0.0f) {\r
+ hsv[0] = 0.0f;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * This method converts rgb values to hsv values.\r
+ * \r
+ * @param h\r
+ * hue\r
+ * @param s\r
+ * saturation\r
+ * @param v\r
+ * value\r
+ * @param rgb\r
+ * rgb result vector (should have 3 elements)\r
+ */\r
+ public void hsvToRgb(float h, float s, float v, float[] rgb) {\r
+ h *= 360.0f;\r
+ if (s == 0.0) {\r
+ rgb[0] = rgb[1] = rgb[2] = v;\r
+ } else {\r
+ if (h == 360) {\r
+ h = 0;\r
+ } else {\r
+ h /= 60;\r
+ }\r
+ int i = (int) Math.floor(h);\r
+ float f = h - i;\r
+ float p = v * (1.0f - s);\r
+ float q = v * (1.0f - s * f);\r
+ float t = v * (1.0f - s * (1.0f - f));\r
+ switch (i) {\r
+ case 0:\r
+ rgb[0] = v;\r
+ rgb[1] = t;\r
+ rgb[2] = p;\r
+ break;\r
+ case 1:\r
+ rgb[0] = q;\r
+ rgb[1] = v;\r
+ rgb[2] = p;\r
+ break;\r
+ case 2:\r
+ rgb[0] = p;\r
+ rgb[1] = v;\r
+ rgb[2] = t;\r
+ break;\r
+ case 3:\r
+ rgb[0] = p;\r
+ rgb[1] = q;\r
+ rgb[2] = v;\r
+ break;\r
+ case 4:\r
+ rgb[0] = t;\r
+ rgb[1] = p;\r
+ rgb[2] = v;\r
+ break;\r
+ case 5:\r
+ rgb[0] = v;\r
+ rgb[1] = p;\r
+ rgb[2] = q;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * An interface used in calculating alpha mask during particles' texture calculations.\r
+ * @author Marcin Roguski (Kaelthas)\r
+ */\r
+ protected static interface IAlphaMask {\r
+ /**\r
+ * This method sets the size of the texture's image.\r
+ * @param width\r
+ * the width of the image\r
+ * @param height\r
+ * the height of the image\r
+ */\r
+ void setImageSize(int width, int height);\r
+\r
+ /**\r
+ * This method returns the alpha value for the specified texture position.\r
+ * @param x\r
+ * the X coordinate of the texture position\r
+ * @param y\r
+ * the Y coordinate of the texture position\r
+ * @return the alpha value for the specified texture position\r
+ */\r
+ byte getAlpha(float x, float y);\r
+ }\r
}\r
import com.jme3.scene.VertexBuffer.Usage;\r
import com.jme3.scene.plugins.blender.data.Structure;\r
import com.jme3.scene.plugins.blender.exception.BlenderFileException;\r
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;\r
import com.jme3.scene.plugins.blender.utils.DataRepository;\r
import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;\r
-import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;\r
import com.jme3.scene.plugins.blender.utils.DynamicArray;\r
import com.jme3.scene.plugins.blender.utils.Pointer;\r
import com.jme3.texture.Texture;\r
* @author Marcin Roguski (Kaelthas)\r
*/\r
public class MeshHelper extends AbstractBlenderHelper {\r
-\r
- protected static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // have no idea why 4, could someone please explain ?\r
-\r
- /**\r
- * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender\r
- * versions.\r
- * \r
- * @param blenderVersion\r
- * the version read from the blend file\r
- */\r
- public MeshHelper(String blenderVersion) {\r
- super(blenderVersion);\r
- }\r
-\r
- /**\r
- * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.\r
- * \r
- * @param structure\r
- * the structure we read the mesh from\r
- * @return the mesh feature\r
- * @throws BlenderFileException\r
- */\r
- @SuppressWarnings("unchecked")\r
- public List<Geometry> toMesh(Structure structure, DataRepository dataRepository) throws BlenderFileException {\r
- List<Geometry> geometries = (List<Geometry>) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(),\r
- LoadedFeatureDataType.LOADED_FEATURE);\r
- if (geometries != null) {\r
- List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());\r
- for (Geometry geometry : geometries) {\r
- copiedGeometries.add(geometry.clone());\r
- }\r
- return copiedGeometries;\r
- }\r
-\r
- // helpers\r
- TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);\r
-\r
- // reading mesh data\r
- String name = structure.getName();\r
-\r
- // reading vertices\r
- Vector3f[] vertices = this.getVertices(structure, dataRepository);\r
- int verticesAmount = vertices.length;\r
-\r
- // vertices Colors\r
- List<float[]> verticesColors = this.getVerticesColors(structure, dataRepository);\r
-\r
- // reading faces\r
- // the following map sorts faces by material number (because in jme Mesh can have only one material)\r
- Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>();\r
- Pointer pMFace = (Pointer) structure.getFieldValue("mface");\r
- List<Structure> mFaces = pMFace.fetchData(dataRepository.getInputStream());\r
-\r
- Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");\r
- List<Vector2f> uvCoordinates = null;\r
- List<Structure> mtFaces = null;\r
-\r
- if (!pMTFace.isNull()) {\r
- mtFaces = pMTFace.fetchData(dataRepository.getInputStream());\r
- int facesAmount = ((Number) structure.getFieldValue("totface")).intValue();\r
- if (mtFaces.size() != facesAmount) {\r
- throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");\r
- }\r
- uvCoordinates = new ArrayList<Vector2f>();// TODO: calculate the amount of coordinates if possible\r
- }\r
-\r
- // normalMap merges normals of faces that will be rendered smooth\r
- Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount);\r
-\r
- List<Vector3f> normalList = new ArrayList<Vector3f>();\r
- List<Vector3f> vertexList = new ArrayList<Vector3f>();\r
- Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();// indicates if the material with the specified\r
- // number should have a texture attached\r
- // this map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'\r
- // positions (it simply tells which vertex is referenced where in the result list)\r
- Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount);\r
- int vertexColorIndex = 0;\r
- for (int i = 0; i < mFaces.size(); ++i) {\r
- Structure mFace = mFaces.get(i);\r
- boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;\r
- DynamicArray<Number> uvs = null;\r
- boolean materialWithoutTextures = false;\r
- Pointer pImage = null;\r
- if (mtFaces != null) {\r
- Structure mtFace = mtFaces.get(i);\r
- pImage = (Pointer) mtFace.getFieldValue("tpage");\r
- materialWithoutTextures = pImage.isNull();\r
- // uvs always must be added wheater we have texture or not\r
- uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");\r
- uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));\r
- uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()));\r
- uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));\r
- }\r
- int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();\r
- Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);\r
- List<Integer> indexList = meshesMap.get(materialNumber);\r
- if (indexList == null) {\r
- indexList = new ArrayList<Integer>();\r
- meshesMap.put(materialNumber, indexList);\r
- }\r
- if (pImage != null && !pImage.isNull() && !materialNumberToTexture.containsKey(materialNumber)) {// attaching image to texture\r
- // (face can have UV's and\r
- // image whlie its material\r
- // may have no texture\r
- // attached)\r
- Texture texture = textureHelper.getTextureFromImage(pImage.fetchData(dataRepository.getInputStream()).get(0),\r
- dataRepository);\r
- if (texture != null) {\r
- materialNumberToTexture.put(materialNumber, texture);\r
- }\r
- }\r
-\r
- int v1 = ((Number) mFace.getFieldValue("v1")).intValue();\r
- int v2 = ((Number) mFace.getFieldValue("v2")).intValue();\r
- int v3 = ((Number) mFace.getFieldValue("v3")).intValue();\r
- int v4 = ((Number) mFace.getFieldValue("v4")).intValue();\r
-\r
- Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]);\r
- this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);\r
- normalList.add(normalMap.get(vertices[v1]));\r
- normalList.add(normalMap.get(vertices[v2]));\r
- normalList.add(normalMap.get(vertices[v3]));\r
-\r
- this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v1]);\r
-\r
- this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v2]);\r
-\r
- this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v3]);\r
-\r
- if (v4 > 0) {\r
- if (uvs != null) {\r
- uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));\r
- uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));\r
- uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()));\r
- }\r
- this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v1]);\r
-\r
- this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v3]);\r
-\r
- this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap);\r
- indexList.add(vertexList.size());\r
- vertexList.add(vertices[v4]);\r
-\r
- this.addNormal(n, normalMap, smooth, vertices[v4]);\r
- normalList.add(normalMap.get(vertices[v1]));\r
- normalList.add(normalMap.get(vertices[v3]));\r
- normalList.add(normalMap.get(vertices[v4]));\r
-\r
- if (verticesColors != null) {\r
- verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex));\r
- verticesColors.add(vertexColorIndex + 4, verticesColors.get(vertexColorIndex + 2));\r
- }\r
- vertexColorIndex += 6;\r
- } else {\r
- if (verticesColors != null) {\r
- verticesColors.remove(vertexColorIndex + 3);\r
- vertexColorIndex += 3;\r
- }\r
- }\r
- }\r
- Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);\r
-\r
- // reading vertices groups (from the parent)\r
- Structure parent = dataRepository.peekParent();\r
- Structure defbase = (Structure) parent.getFieldValue("defbase");\r
- List<Structure> defs = defbase.evaluateListBase(dataRepository);\r
- String[] verticesGroups = new String[defs.size()];\r
- int defIndex = 0;\r
- for (Structure def : defs) {\r
- verticesGroups[defIndex++] = def.getFieldValue("name").toString();\r
- }\r
-\r
- // vertices bone weights and indices\r
- ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);\r
- Structure defBase = (Structure) parent.getFieldValue("defbase");\r
- Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository);\r
-\r
- VertexBuffer verticesWeights = null, verticesWeightsIndices = null;\r
- int[] bonesGroups = new int[]{0};\r
- VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(structure, vertexList.size(), bonesGroups,\r
- vertexReferenceMap, groupToBoneIndexMap, dataRepository);\r
- verticesWeights = boneWeightsAndIndex[0];\r
- verticesWeightsIndices = boneWeightsAndIndex[1];\r
-\r
- // reading materials\r
- MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
- Material[] materials = null;\r
- Material[] nonTexturedMaterials = null;\r
- if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {\r
- materials = materialHelper.getMaterials(structure, dataRepository);\r
- nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed\r
- }\r
-\r
- // creating the result meshes\r
- geometries = new ArrayList<Geometry>(meshesMap.size());\r
-\r
- VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);\r
- verticesBuffer.setupData(Usage.Stream, 3, Format.Float,\r
- BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()])));\r
-\r
- // initial vertex position (used with animation)\r
- VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);\r
- verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));\r
-\r
- VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);\r
- normalsBuffer.setupData(Usage.Stream, 3, Format.Float, BufferUtils.createFloatBuffer(normals));\r
-\r
- // initial normals position (used with animation)\r
- VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);\r
- normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));\r
-\r
- VertexBuffer uvCoordsBuffer = null;\r
- if (uvCoordinates != null) {\r
- uvCoordsBuffer = new VertexBuffer(Type.TexCoord);\r
- uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,\r
- BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));\r
- }\r
-\r
- // generating meshes\r
- FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);\r
- for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {\r
- Mesh mesh = new Mesh();\r
-\r
- // creating vertices indices for this mesh\r
- List<Integer> indexList = meshEntry.getValue();\r
- short[] indices = new short[indexList.size()];//TODO: check if the model doesn't have more than 32767 vertices\r
- for (int i = 0; i < indexList.size(); ++i) {//if yes then mesh.getVertices method must be changed to accept other than ShortBuffer\r
- indices[i] = indexList.get(i).shortValue();\r
- }\r
-\r
- // setting vertices\r
- mesh.setBuffer(Type.Index, 1, BufferUtils.createShortBuffer(indices));\r
- mesh.setBuffer(verticesBuffer);\r
- mesh.setBuffer(verticesBind);\r
-\r
- // setting vertices colors\r
- if (verticesColorsBuffer != null) {\r
- mesh.setBuffer(Type.Color, 4, verticesColorsBuffer);\r
- }\r
-\r
- // setting weights for bones\r
- if (verticesWeights != null) {\r
- mesh.setMaxNumWeights(bonesGroups[0]);\r
- mesh.setBuffer(verticesWeights);\r
- mesh.setBuffer(verticesWeightsIndices);\r
- }\r
-\r
- // setting faces' normals\r
- mesh.setBuffer(normalsBuffer);\r
- mesh.setBuffer(normalsBind);\r
-\r
- // setting uvCoords\r
- if (uvCoordsBuffer != null) {\r
- mesh.setBuffer(uvCoordsBuffer);\r
- }\r
-\r
- // creating the result\r
- Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);\r
- if (materials != null) {\r
- int materialNumber = meshEntry.getKey().intValue();\r
- Material material;\r
- if (materialNumber >= 0) {\r
- material = materials[materialNumber];\r
- if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) {\r
- if (material.getMaterialDef().getAssetName().contains("Lighting")) {\r
- if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) {\r
- material = material.clone();\r
- material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE,\r
- materialNumberToTexture.get(Integer.valueOf(materialNumber)));\r
- }\r
- } else {\r
- if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) {\r
- material = material.clone();\r
- material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR,\r
- materialNumberToTexture.get(Integer.valueOf(materialNumber)));\r
- }\r
- }\r
- }\r
- } else {\r
- materialNumber = -1 * (materialNumber + 1);\r
- if (nonTexturedMaterials[materialNumber] == null) {\r
- nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber],\r
- TextureHelper.TEX_IMAGE);\r
- }\r
- material = nonTexturedMaterials[materialNumber];\r
- }\r
- geometry.setMaterial(material);\r
- } else {\r
- geometry.setMaterial(dataRepository.getDefaultMaterial());\r
- }\r
- geometries.add(geometry);\r
- }\r
- dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);\r
- return geometries;\r
- }\r
-\r
- /**\r
- * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.\r
- * \r
- * @param normalToAdd\r
- * a normal to be added\r
- * @param normalMap\r
- * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector\r
- * @param smooth\r
- * the variable that indicates wheather to merge normals (creating the smooth mesh) or not\r
- * @param vertices\r
- * a list of vertices read from the blender file\r
- */\r
- protected void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {\r
- for (Vector3f v : vertices) {\r
- Vector3f n = normalMap.get(v);\r
- if (!smooth || n == null) {\r
- normalMap.put(v, normalToAdd.clone());\r
- } else {\r
- n.addLocal(normalToAdd).normalizeLocal();\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created\r
- * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key\r
- * - the reference indices list.\r
- * \r
- * @param basicVertexIndex\r
- * the index of the vertex from its basic table\r
- * @param resultIndex\r
- * the index of the vertex in its result vertex list\r
- * @param vertexReferenceMap\r
- * the reference map\r
- */\r
- protected void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {\r
- List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));\r
- if (referenceList == null) {\r
- referenceList = new ArrayList<Integer>();\r
- vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);\r
- }\r
- referenceList.add(Integer.valueOf(resultIndex));\r
- }\r
-\r
- /**\r
- * This method returns the vertices colors. Each vertex is stored in float[4] array.\r
- * \r
- * @param meshStructure\r
- * the structure containing the mesh data\r
- * @param dataRepository\r
- * the data repository\r
- * @return a list of vertices colors, each color belongs to a single vertex\r
- * @throws BlenderFileException\r
- * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
- */\r
- public List<float[]> getVerticesColors(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {\r
- Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol");\r
- List<float[]> verticesColors = null;\r
- List<Structure> mCol = null;\r
- if (!pMCol.isNull()) {\r
- verticesColors = new LinkedList<float[]>();\r
- mCol = pMCol.fetchData(dataRepository.getInputStream());\r
- for (Structure color : mCol) {\r
- float r = ((Number) color.getFieldValue("r")).byteValue() / 256.0f;\r
- float g = ((Number) color.getFieldValue("g")).byteValue() / 256.0f;\r
- float b = ((Number) color.getFieldValue("b")).byteValue() / 256.0f;\r
- float a = ((Number) color.getFieldValue("a")).byteValue() / 256.0f;\r
- verticesColors.add(new float[]{b, g, r, a});\r
- }\r
- }\r
- return verticesColors;\r
- }\r
-\r
- /**\r
- * This method returns the vertices.\r
- * \r
- * @param meshStructure\r
- * the structure containing the mesh data\r
- * @param dataRepository\r
- * the data repository\r
- * @return a list of vertices colors, each color belongs to a single vertex\r
- * @throws BlenderFileException\r
- * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
- */\r
- @SuppressWarnings("unchecked")\r
- public Vector3f[] getVertices(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {\r
- int verticesAmount = ((Number) meshStructure.getFieldValue("totvert")).intValue();\r
- Vector3f[] vertices = new Vector3f[verticesAmount];\r
- Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");\r
- List<Structure> mVerts = pMVert.fetchData(dataRepository.getInputStream());\r
- for (int i = 0; i < verticesAmount; ++i) {\r
- DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");\r
- vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());\r
- }\r
- return vertices;\r
- }\r
-\r
- /**\r
- * This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The\r
- * second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).\r
- * \r
- * @param meshStructure\r
- * the mesh structure object\r
- * @param vertexListSize\r
- * a number of vertices in the model\r
- * @param bonesGroups\r
- * this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to\r
- * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there\r
- * @param vertexReferenceMap\r
- * this reference map allows to map the original vertices read from blender to vertices that are really in the model; one\r
- * vertex may appear several times in the result model\r
- * @param groupToBoneIndexMap\r
- * this object maps the group index (to which a vertices in blender belong) to bone index of the model\r
- * @param dataRepository\r
- * the data repository\r
- * @return arrays of vertices weights and their bone indices and (as an outpot parameter) the maximum amount of weights for a vertex\r
- * @throws BlenderFileException\r
- * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
- */\r
- public VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups,\r
- Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository)\r
- throws BlenderFileException {\r
- Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices\r
- FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
- ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
- if (!pDvert.isNull()) {// assigning weights and bone indices\r
- List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per\r
- // vertex in blender)\r
- int vertexIndex = 0;\r
- for (Structure dvert : dverts) {\r
- int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex\r
- // (max. 4 in JME)\r
- Pointer pDW = (Pointer) dvert.getFieldValue("dw");\r
- List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here\r
- if (totweight > 0 && !pDW.isNull()) {// pDW should never be null here, but I check it just in case :)\r
- int weightIndex = 0;\r
- List<Structure> dw = pDW.fetchData(dataRepository.getInputStream());\r
- for (Structure deformWeight : dw) {\r
- Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());\r
- if (boneIndex != null) {// null here means that we came accross group that has no bone attached to\r
- float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();\r
- if (weight == 0.0f) {\r
- weight = 1;\r
- boneIndex = Integer.valueOf(0);\r
- }\r
- // we apply the weight to all referenced vertices\r
- for (Integer index : vertexIndices) {\r
- // all indices are always assigned to 0-indexed bone\r
- // weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, 1.0f);\r
- // indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, (byte)0);\r
- // if(weight != 0.0f) {\r
- weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);\r
- indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());\r
- // }\r
- }\r
- }\r
- ++weightIndex;\r
- }\r
- } else {\r
- for (Integer index : vertexIndices) {\r
- weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);\r
- indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
- }\r
- }\r
- ++vertexIndex;\r
- }\r
- } else {\r
- // always bind all vertices to 0-indexed bone\r
- // this bone makes the model look normally if vertices have no bone assigned\r
- // and it is used in object animation, so if we come accross object animation\r
- // we can use the 0-indexed bone for this\r
- for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {\r
- // we apply the weight to all referenced vertices\r
- for (Integer index : vertexIndexList) {\r
- weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);\r
- indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
- }\r
- }\r
- }\r
-\r
- bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);\r
- VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);\r
- verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);\r
-\r
- VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);\r
- verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);\r
- return new VertexBuffer[]{verticesWeights, verticesWeightsIndices};\r
- }\r
-\r
- /**\r
- * Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer.\r
- */\r
- protected int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {\r
- int maxWeightsPerVert = 0;\r
- weightsFloatData.rewind();\r
- for (int v = 0; v < vertCount; ++v) {\r
- float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();\r
-\r
- if (w3 != 0) {\r
- maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);\r
- } else if (w2 != 0) {\r
- maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);\r
- } else if (w1 != 0) {\r
- maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);\r
- } else if (w0 != 0) {\r
- maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);\r
- }\r
-\r
- float sum = w0 + w1 + w2 + w3;\r
- if (sum != 1f && sum != 0.0f) {\r
- weightsFloatData.position(weightsFloatData.position() - 4);\r
- // compute new vals based on sum\r
- float sumToB = 1f / sum;\r
- weightsFloatData.put(w0 * sumToB);\r
- weightsFloatData.put(w1 * sumToB);\r
- weightsFloatData.put(w2 * sumToB);\r
- weightsFloatData.put(w3 * sumToB);\r
- }\r
- }\r
- weightsFloatData.rewind();\r
-\r
- // mesh.setMaxNumWeights(maxWeightsPerVert);\r
- return maxWeightsPerVert;\r
- }\r
+ protected static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // have no idea why 4, could someone please explain ?\r
+\r
+ /**\r
+ * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender\r
+ * versions.\r
+ * \r
+ * @param blenderVersion\r
+ * the version read from the blend file\r
+ */\r
+ public MeshHelper(String blenderVersion) {\r
+ super(blenderVersion);\r
+ }\r
+\r
+ /**\r
+ * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.\r
+ * \r
+ * @param structure\r
+ * the structure we read the mesh from\r
+ * @return the mesh feature\r
+ * @throws BlenderFileException\r
+ */\r
+ @SuppressWarnings("unchecked")\r
+ public List<Geometry> toMesh(Structure structure, DataRepository dataRepository) throws BlenderFileException {\r
+ List<Geometry> geometries = (List<Geometry>) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(),\r
+ LoadedFeatureDataType.LOADED_FEATURE);\r
+ if (geometries != null) {\r
+ List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());\r
+ for (Geometry geometry : geometries) {\r
+ copiedGeometries.add(geometry.clone());\r
+ }\r
+ return copiedGeometries;\r
+ }\r
+\r
+ // helpers\r
+ TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);\r
+\r
+ // reading mesh data\r
+ String name = structure.getName();\r
+\r
+ // reading vertices\r
+ Vector3f[] vertices = this.getVertices(structure, dataRepository);\r
+ int verticesAmount = vertices.length;\r
+\r
+ // vertices Colors\r
+ List<float[]> verticesColors = this.getVerticesColors(structure, dataRepository);\r
+\r
+ // reading faces\r
+ // the following map sorts faces by material number (because in jme Mesh can have only one material)\r
+ Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>();\r
+ Pointer pMFace = (Pointer) structure.getFieldValue("mface");\r
+ List<Structure> mFaces = pMFace.fetchData(dataRepository.getInputStream());\r
+\r
+ Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");\r
+ List<Vector2f> uvCoordinates = null;\r
+ List<Structure> mtFaces = null;\r
+\r
+ if (!pMTFace.isNull()) {\r
+ mtFaces = pMTFace.fetchData(dataRepository.getInputStream());\r
+ int facesAmount = ((Number) structure.getFieldValue("totface")).intValue();\r
+ if (mtFaces.size() != facesAmount) {\r
+ throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");\r
+ }\r
+ uvCoordinates = new ArrayList<Vector2f>();// TODO: calculate the amount of coordinates if possible\r
+ }\r
+\r
+ // normalMap merges normals of faces that will be rendered smooth\r
+ Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount);\r
+\r
+ List<Vector3f> normalList = new ArrayList<Vector3f>();\r
+ List<Vector3f> vertexList = new ArrayList<Vector3f>();\r
+ // indicates if the material with the specified number should have a texture attached\r
+ Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();\r
+ // this map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'\r
+ // positions (it simply tells which vertex is referenced where in the result list)\r
+ Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount);\r
+ int vertexColorIndex = 0;\r
+ for (int i = 0; i < mFaces.size(); ++i) {\r
+ Structure mFace = mFaces.get(i);\r
+ boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;\r
+ DynamicArray<Number> uvs = null;\r
+ boolean materialWithoutTextures = false;\r
+ Pointer pImage = null;\r
+ if (mtFaces != null) {\r
+ Structure mtFace = mtFaces.get(i);\r
+ pImage = (Pointer) mtFace.getFieldValue("tpage");\r
+ materialWithoutTextures = pImage.isNull();\r
+ // uvs always must be added wheater we have texture or not\r
+ uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");\r
+ uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));\r
+ uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()));\r
+ uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));\r
+ }\r
+ int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();\r
+ Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);\r
+ List<Integer> indexList = meshesMap.get(materialNumber);\r
+ if (indexList == null) {\r
+ indexList = new ArrayList<Integer>();\r
+ meshesMap.put(materialNumber, indexList);\r
+ }\r
+ \r
+ // attaching image to texture (face can have UV's and image whlie its material may have no texture attached)\r
+ if (pImage != null && !pImage.isNull() && !materialNumberToTexture.containsKey(materialNumber)) {\r
+ Texture texture = textureHelper.getTextureFromImage(pImage.fetchData(dataRepository.getInputStream()).get(0),\r
+ dataRepository);\r
+ if (texture != null) {\r
+ materialNumberToTexture.put(materialNumber, texture);\r
+ }\r
+ }\r
+\r
+ int v1 = ((Number) mFace.getFieldValue("v1")).intValue();\r
+ int v2 = ((Number) mFace.getFieldValue("v2")).intValue();\r
+ int v3 = ((Number) mFace.getFieldValue("v3")).intValue();\r
+ int v4 = ((Number) mFace.getFieldValue("v4")).intValue();\r
+\r
+ Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]);\r
+ this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);\r
+ normalList.add(normalMap.get(vertices[v1]));\r
+ normalList.add(normalMap.get(vertices[v2]));\r
+ normalList.add(normalMap.get(vertices[v3]));\r
+\r
+ this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v1]);\r
+\r
+ this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v2]);\r
+\r
+ this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v3]);\r
+\r
+ if (v4 > 0) {\r
+ if (uvs != null) {\r
+ uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));\r
+ uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));\r
+ uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()));\r
+ }\r
+ this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v1]);\r
+\r
+ this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v3]);\r
+\r
+ this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap);\r
+ indexList.add(vertexList.size());\r
+ vertexList.add(vertices[v4]);\r
+\r
+ this.addNormal(n, normalMap, smooth, vertices[v4]);\r
+ normalList.add(normalMap.get(vertices[v1]));\r
+ normalList.add(normalMap.get(vertices[v3]));\r
+ normalList.add(normalMap.get(vertices[v4]));\r
+\r
+ if (verticesColors != null) {\r
+ verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex));\r
+ verticesColors.add(vertexColorIndex + 4, verticesColors.get(vertexColorIndex + 2));\r
+ }\r
+ vertexColorIndex += 6;\r
+ } else {\r
+ if (verticesColors != null) {\r
+ verticesColors.remove(vertexColorIndex + 3);\r
+ vertexColorIndex += 3;\r
+ }\r
+ }\r
+ }\r
+ Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);\r
+\r
+ // reading vertices groups (from the parent)\r
+ Structure parent = dataRepository.peekParent();\r
+ Structure defbase = (Structure) parent.getFieldValue("defbase");\r
+ List<Structure> defs = defbase.evaluateListBase(dataRepository);\r
+ String[] verticesGroups = new String[defs.size()];\r
+ int defIndex = 0;\r
+ for (Structure def : defs) {\r
+ verticesGroups[defIndex++] = def.getFieldValue("name").toString();\r
+ }\r
+\r
+ // vertices bone weights and indices\r
+ ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);\r
+ Structure defBase = (Structure) parent.getFieldValue("defbase");\r
+ Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository);\r
+\r
+ VertexBuffer verticesWeights = null, verticesWeightsIndices = null;\r
+ int[] bonesGroups = new int[] { 0 };\r
+ VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(structure, vertexList.size(), bonesGroups,\r
+ vertexReferenceMap, groupToBoneIndexMap, dataRepository);\r
+ verticesWeights = boneWeightsAndIndex[0];\r
+ verticesWeightsIndices = boneWeightsAndIndex[1];\r
+\r
+ // reading materials\r
+ MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);\r
+ Material[] materials = null;\r
+ Material[] nonTexturedMaterials = null;\r
+ if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {\r
+ materials = materialHelper.getMaterials(structure, dataRepository);\r
+ nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed\r
+ }\r
+\r
+ // creating the result meshes\r
+ geometries = new ArrayList<Geometry>(meshesMap.size());\r
+\r
+ VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);\r
+ verticesBuffer.setupData(Usage.Stream, 3, Format.Float,\r
+ BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()])));\r
+\r
+ // initial vertex position (used with animation)\r
+ VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);\r
+ verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));\r
+\r
+ VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);\r
+ normalsBuffer.setupData(Usage.Stream, 3, Format.Float, BufferUtils.createFloatBuffer(normals));\r
+\r
+ // initial normals position (used with animation)\r
+ VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);\r
+ normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));\r
+\r
+ VertexBuffer uvCoordsBuffer = null;\r
+ if (uvCoordinates != null) {\r
+ uvCoordsBuffer = new VertexBuffer(Type.TexCoord);\r
+ uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,\r
+ BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));\r
+ }\r
+\r
+ // generating meshes\r
+ FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);\r
+ for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {\r
+ Mesh mesh = new Mesh();\r
+\r
+ // creating vertices indices for this mesh\r
+ List<Integer> indexList = meshEntry.getValue();\r
+ short[] indices = new short[indexList.size()];//TODO: check if the model doesn't have more than 32767 vertices\r
+ for (int i = 0; i < indexList.size(); ++i) {//if yes then mesh.getVertices method must be changed to accept other than ShortBuffer\r
+ indices[i] = indexList.get(i).shortValue();\r
+ }\r
+\r
+ // setting vertices\r
+ mesh.setBuffer(Type.Index, 1, BufferUtils.createShortBuffer(indices));\r
+ mesh.setBuffer(verticesBuffer);\r
+ mesh.setBuffer(verticesBind);\r
+\r
+ // setting vertices colors\r
+ if (verticesColorsBuffer != null) {\r
+ mesh.setBuffer(Type.Color, 4, verticesColorsBuffer);\r
+ }\r
+\r
+ // setting weights for bones\r
+ if (verticesWeights != null) {\r
+ mesh.setMaxNumWeights(bonesGroups[0]);\r
+ mesh.setBuffer(verticesWeights);\r
+ mesh.setBuffer(verticesWeightsIndices);\r
+ }\r
+\r
+ // setting faces' normals\r
+ mesh.setBuffer(normalsBuffer);\r
+ mesh.setBuffer(normalsBind);\r
+\r
+ // setting uvCoords\r
+ if (uvCoordsBuffer != null) {\r
+ mesh.setBuffer(uvCoordsBuffer);\r
+ }\r
+\r
+ // creating the result\r
+ Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);\r
+ if (materials != null) {\r
+ int materialNumber = meshEntry.getKey().intValue();\r
+ Material material;\r
+ if (materialNumber >= 0) {\r
+ material = materials[materialNumber];\r
+ if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) {\r
+ if (material.getMaterialDef().getAssetName().contains("Lighting")) {\r
+ if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) {\r
+ material = material.clone();\r
+ material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE,\r
+ materialNumberToTexture.get(Integer.valueOf(materialNumber)));\r
+ }\r
+ } else {\r
+ if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) {\r
+ material = material.clone();\r
+ material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR,\r
+ materialNumberToTexture.get(Integer.valueOf(materialNumber)));\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ materialNumber = -1 * (materialNumber + 1);\r
+ if (nonTexturedMaterials[materialNumber] == null) {\r
+ nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber],\r
+ TextureHelper.TEX_IMAGE);\r
+ }\r
+ material = nonTexturedMaterials[materialNumber];\r
+ }\r
+ geometry.setMaterial(material);\r
+ } else {\r
+ geometry.setMaterial(dataRepository.getDefaultMaterial());\r
+ }\r
+ geometries.add(geometry);\r
+ }\r
+ dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);\r
+ return geometries;\r
+ }\r
+\r
+ /**\r
+ * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.\r
+ * \r
+ * @param normalToAdd\r
+ * a normal to be added\r
+ * @param normalMap\r
+ * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector\r
+ * @param smooth\r
+ * the variable that indicates wheather to merge normals (creating the smooth mesh) or not\r
+ * @param vertices\r
+ * a list of vertices read from the blender file\r
+ */\r
+ protected void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {\r
+ for (Vector3f v : vertices) {\r
+ Vector3f n = normalMap.get(v);\r
+ if (!smooth || n == null) {\r
+ normalMap.put(v, normalToAdd.clone());\r
+ } else {\r
+ n.addLocal(normalToAdd).normalizeLocal();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created\r
+ * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key\r
+ * - the reference indices list.\r
+ * \r
+ * @param basicVertexIndex\r
+ * the index of the vertex from its basic table\r
+ * @param resultIndex\r
+ * the index of the vertex in its result vertex list\r
+ * @param vertexReferenceMap\r
+ * the reference map\r
+ */\r
+ protected void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {\r
+ List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));\r
+ if (referenceList == null) {\r
+ referenceList = new ArrayList<Integer>();\r
+ vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);\r
+ }\r
+ referenceList.add(Integer.valueOf(resultIndex));\r
+ }\r
+\r
+ /**\r
+ * This method returns the vertices colors. Each vertex is stored in float[4] array.\r
+ * \r
+ * @param meshStructure\r
+ * the structure containing the mesh data\r
+ * @param dataRepository\r
+ * the data repository\r
+ * @return a list of vertices colors, each color belongs to a single vertex\r
+ * @throws BlenderFileException\r
+ * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
+ */\r
+ public List<float[]> getVerticesColors(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {\r
+ Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol");\r
+ List<float[]> verticesColors = null;\r
+ List<Structure> mCol = null;\r
+ if (!pMCol.isNull()) {\r
+ verticesColors = new LinkedList<float[]>();\r
+ mCol = pMCol.fetchData(dataRepository.getInputStream());\r
+ for (Structure color : mCol) {\r
+ float r = ((Number) color.getFieldValue("r")).byteValue() / 256.0f;\r
+ float g = ((Number) color.getFieldValue("g")).byteValue() / 256.0f;\r
+ float b = ((Number) color.getFieldValue("b")).byteValue() / 256.0f;\r
+ float a = ((Number) color.getFieldValue("a")).byteValue() / 256.0f;\r
+ verticesColors.add(new float[] { b, g, r, a });\r
+ }\r
+ }\r
+ return verticesColors;\r
+ }\r
+\r
+ /**\r
+ * This method returns the vertices.\r
+ * \r
+ * @param meshStructure\r
+ * the structure containing the mesh data\r
+ * @param dataRepository\r
+ * the data repository\r
+ * @return a list of vertices colors, each color belongs to a single vertex\r
+ * @throws BlenderFileException\r
+ * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
+ */\r
+ @SuppressWarnings("unchecked")\r
+ public Vector3f[] getVertices(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {\r
+ int verticesAmount = ((Number) meshStructure.getFieldValue("totvert")).intValue();\r
+ Vector3f[] vertices = new Vector3f[verticesAmount];\r
+ Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");\r
+ List<Structure> mVerts = pMVert.fetchData(dataRepository.getInputStream());\r
+ for (int i = 0; i < verticesAmount; ++i) {\r
+ DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");\r
+ vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());\r
+ }\r
+ return vertices;\r
+ }\r
+\r
+ /**\r
+ * This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The\r
+ * second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).\r
+ * \r
+ * @param meshStructure\r
+ * the mesh structure object\r
+ * @param vertexListSize\r
+ * a number of vertices in the model\r
+ * @param bonesGroups\r
+ * this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to\r
+ * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there\r
+ * @param vertexReferenceMap\r
+ * this reference map allows to map the original vertices read from blender to vertices that are really in the model; one\r
+ * vertex may appear several times in the result model\r
+ * @param groupToBoneIndexMap\r
+ * this object maps the group index (to which a vertices in blender belong) to bone index of the model\r
+ * @param dataRepository\r
+ * the data repository\r
+ * @return arrays of vertices weights and their bone indices and (as an outpot parameter) the maximum amount of weights for a vertex\r
+ * @throws BlenderFileException\r
+ * this exception is thrown when the blend file structure is somehow invalid or corrupted\r
+ */\r
+ public VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups,\r
+ Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository)\r
+ throws BlenderFileException {\r
+ Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices\r
+ FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
+ ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
+ if (!pDvert.isNull()) {// assigning weights and bone indices\r
+ List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per\r
+ // vertex in blender)\r
+ int vertexIndex = 0;\r
+ for (Structure dvert : dverts) {\r
+ int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex\r
+ // (max. 4 in JME)\r
+ Pointer pDW = (Pointer) dvert.getFieldValue("dw");\r
+ List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here\r
+ if (totweight > 0 && !pDW.isNull()) {// pDW should never be null here, but I check it just in case :)\r
+ int weightIndex = 0;\r
+ List<Structure> dw = pDW.fetchData(dataRepository.getInputStream());\r
+ for (Structure deformWeight : dw) {\r
+ Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());\r
+ if (boneIndex != null) {// null here means that we came accross group that has no bone attached to\r
+ float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();\r
+ if (weight == 0.0f) {\r
+ weight = 1;\r
+ boneIndex = Integer.valueOf(0);\r
+ }\r
+ // we apply the weight to all referenced vertices\r
+ for (Integer index : vertexIndices) {\r
+ // all indices are always assigned to 0-indexed bone\r
+ // weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, 1.0f);\r
+ // indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, (byte)0);\r
+ // if(weight != 0.0f) {\r
+ weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);\r
+ indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());\r
+ // }\r
+ }\r
+ }\r
+ ++weightIndex;\r
+ }\r
+ } else {\r
+ for (Integer index : vertexIndices) {\r
+ weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);\r
+ indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
+ }\r
+ }\r
+ ++vertexIndex;\r
+ }\r
+ } else {\r
+ // always bind all vertices to 0-indexed bone\r
+ // this bone makes the model look normally if vertices have no bone assigned\r
+ // and it is used in object animation, so if we come accross object animation\r
+ // we can use the 0-indexed bone for this\r
+ for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {\r
+ // we apply the weight to all referenced vertices\r
+ for (Integer index : vertexIndexList) {\r
+ weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);\r
+ indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
+ }\r
+ }\r
+ }\r
+\r
+ bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);\r
+ VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);\r
+ verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);\r
+\r
+ VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);\r
+ verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);\r
+ return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };\r
+ }\r
+\r
+ /**\r
+ * Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer.\r
+ */\r
+ protected int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {\r
+ int maxWeightsPerVert = 0;\r
+ weightsFloatData.rewind();\r
+ for (int v = 0; v < vertCount; ++v) {\r
+ float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();\r
+\r
+ if (w3 != 0) {\r
+ maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);\r
+ } else if (w2 != 0) {\r
+ maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);\r
+ } else if (w1 != 0) {\r
+ maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);\r
+ } else if (w0 != 0) {\r
+ maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);\r
+ }\r
+\r
+ float sum = w0 + w1 + w2 + w3;\r
+ if (sum != 1f && sum != 0.0f) {\r
+ weightsFloatData.position(weightsFloatData.position() - 4);\r
+ // compute new vals based on sum\r
+ float sumToB = 1f / sum;\r
+ weightsFloatData.put(w0 * sumToB);\r
+ weightsFloatData.put(w1 * sumToB);\r
+ weightsFloatData.put(w2 * sumToB);\r
+ weightsFloatData.put(w3 * sumToB);\r
+ }\r
+ }\r
+ weightsFloatData.rewind();\r
+\r
+ // mesh.setMaxNumWeights(maxWeightsPerVert);\r
+ return maxWeightsPerVert;\r
+ }\r
}\r