From 69e4392ba97044485c33efbc86c902068bf8744a Mon Sep 17 00:00:00 2001 From: "Kae..pl" Date: Sun, 24 Nov 2013 09:27:37 +0000 Subject: [PATCH] Bugfix: fixed an issue with wrong vertrex colors applying (along with classes prepared to implement line and point mesh loading). git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10913 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../plugins/blender/meshes/MeshBuilder.java | 342 ---------- .../plugins/blender/meshes/MeshHelper.java | 365 +---------- .../meshes/builders/FaceMeshBuilder.java | 591 ++++++++++++++++++ .../meshes/builders/LineMeshBuilder.java | 5 + .../blender/meshes/builders/MeshBuilder.java | 129 ++++ .../meshes/builders/PointMeshBuilder.java | 5 + 6 files changed, 747 insertions(+), 690 deletions(-) delete mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java deleted file mode 100644 index 487007fbc..000000000 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java +++ /dev/null @@ -1,342 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.textures.UserUVCollection; -import com.jme3.util.BufferUtils; - -/** - * A builder class for meshes. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class MeshBuilder { - private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName()); - - /** An array of reference vertices. */ - private Vector3f[][] verticesAndNormals; - /** An list of vertices colors. */ - private List verticesColors; - /** A variable that indicates if the model uses generated textures. */ - private boolean usesGeneratedTextures; - /** - * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' - * positions (it simply tells which vertex is referenced where in the result list). - */ - private Map>> globalVertexReferenceMap; - /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> normalMap = new HashMap>(); - /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> vertexMap = new HashMap>(); - /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ - private Map> vertexColorsMap = new HashMap>(); - /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ - private Map> indexMap = new HashMap>(); - /** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */ - private UserUVCollection userUVCollection = new UserUVCollection(); - - /** - * Constructor. Stores the given array (not copying it). - * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. - * The amount of vertices is always faceCount * 3. - * @param verticesAndNormals - * the reference vertices and normals array - * @param usesGeneratedTextures - * a variable that indicates if the model uses generated textures or not - */ - public MeshBuilder(Vector3f[][] verticesAndNormals, List verticesColors, boolean usesGeneratedTextures) { - this.verticesAndNormals = verticesAndNormals; - this.verticesColors = verticesColors; - this.usesGeneratedTextures = usesGeneratedTextures; - globalVertexReferenceMap = new HashMap>>(verticesAndNormals.length); - } - - /** - * This method adds a point to the mesh. - * @param coordinates - * the coordinates of the point - * @param normal - * the point's normal vector - * @param materialNumber - * the material number for this point - */ - public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) { - LOGGER.warning("Appending single point not yet supported!");// TODO - } - - /** - * This method adds a line to the mesh. - * @param v1 - * index of the 1'st vertex from the reference vertex table - * @param v2 - * index of the 2'nd vertex from the reference vertex table - * @param smooth - * indicates if this face should have smooth shading or flat shading - */ - public void appendEdge(int v1, int v2, boolean smooth) { - LOGGER.warning("Appending single line not yet supported!");// TODO - } - - /** - * This method adds a face to the mesh. - * @param v1 - * index of the 1'st vertex from the reference vertex table - * @param v2 - * index of the 2'nd vertex from the reference vertex table - * @param v3 - * index of the 3'rd vertex from the reference vertex table - * @param smooth - * indicates if this face should have smooth shading or flat shading - * @param materialNumber - * the material number for this face - * @param uvsForFace - * a 3-element array of vertices UV coordinates mapped to the UV's set name - * @param quad - * indicates if the appended face is a part of a quad face (used for creating vertex colors buffer) - * @param faceIndex - * the face index (used for creating vertex colors buffer) - */ - public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map uvsForFace, boolean quad, int faceIndex) { - if (uvsForFace != null && uvsForFace.size() > 0) { - for (Entry entry : uvsForFace.entrySet()) { - if (entry.getValue().length != 3) { - throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : "")); - } - } - } - - // getting the required lists - List indexList = indexMap.get(materialNumber); - if (indexList == null) { - indexList = new ArrayList(); - indexMap.put(materialNumber, indexList); - } - List vertexList = vertexMap.get(materialNumber); - if (vertexList == null) { - vertexList = new ArrayList(); - vertexMap.put(materialNumber, vertexList); - } - List vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; - int[] vertexColorIndex = new int[] { 0, 1, 2 }; - if (vertexColorsList == null && vertexColorsMap != null) { - vertexColorsList = new ArrayList(); - vertexColorsMap.put(materialNumber, vertexColorsList); - } - List normalList = normalMap.get(materialNumber); - if (normalList == null) { - normalList = new ArrayList(); - normalMap.put(materialNumber, normalList); - } - Map> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); - if (vertexReferenceMap == null) { - vertexReferenceMap = new HashMap>(); - globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); - } - - faceIndex *= 3; - if (quad) { - vertexColorIndex[1] = 2; - vertexColorIndex[2] = 3; - } - - // creating faces - Integer[] index = new Integer[] { v1, v2, v3 }; - if (smooth && !usesGeneratedTextures) { - for (int i = 0; i < 3; ++i) { - if (!vertexReferenceMap.containsKey(index[i])) { - //if this index is not yet used then create another face - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - if (uvsForFace != null) { - for (Entry entry : uvsForFace.entrySet()) { - userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); - } - } - - vertexList.add(verticesAndNormals[index[i]][0]); - if (verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(verticesAndNormals[index[i]][1]); - - index[i] = vertexList.size() - 1; - } else if (uvsForFace != null) { - //if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's - //in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert - //because in jme one vertex can have only on UV coordinate - boolean vertexAlreadyUsed = false; - for (Integer vertexIndex : vertexReferenceMap.get(index[i])) { - int vertexUseCounter = 0; - for (Entry entry : uvsForFace.entrySet()) { - if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) { - ++vertexUseCounter; - } - } - if (vertexUseCounter == uvsForFace.size()) { - vertexAlreadyUsed = true; - index[i] = vertexIndex; - break; - } - } - - if (!vertexAlreadyUsed) { - // treat this face as a new one because its vertices have separate UV's - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - for (Entry entry : uvsForFace.entrySet()) { - userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); - } - vertexList.add(verticesAndNormals[index[i]][0]); - if (verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(verticesAndNormals[index[i]][1]); - index[i] = vertexList.size() - 1; - } - } else { - //use this index again - index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); - } - indexList.add(index[i]); - } - } else { - Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); - for (int i = 0; i < 3; ++i) { - indexList.add(vertexList.size()); - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - if (uvsForFace != null) { - for (Entry entry : uvsForFace.entrySet()) { - userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); - } - } - vertexList.add(verticesAndNormals[index[i]][0]); - if (verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); - } - } - } - - /** - * @return a map that maps vertex index from reference array to its indices in the result list - */ - public Map>> getVertexReferenceMap() { - return globalVertexReferenceMap; - } - - /** - * @param materialNumber - * the material index - * @return result vertices array - */ - public Vector3f[] getVertices(int materialNumber) { - return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); - } - - /** - * @param materialNumber - * the material index - * @return the amount of result vertices - */ - public int getVerticesAmount(int materialNumber) { - return vertexMap.get(materialNumber).size(); - } - - /** - * @param materialNumber - * the material index - * @return normals result array - */ - public Vector3f[] getNormals(int materialNumber) { - return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); - } - - /** - * @param materialNumber - * the material index - * @return the vertices colors buffer or null if no vertex colors is set - */ - public ByteBuffer getVertexColorsBuffer(int materialNumber) { - ByteBuffer result = null; - if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { - List data = vertexColorsMap.get(materialNumber); - result = BufferUtils.createByteBuffer(4 * data.size()); - for (byte[] v : data) { - if (v != null) { - result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); - } else { - result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); - } - } - result.flip(); - } - return result; - } - - /** - * @return a map between material number and the mesh part vertices indices - */ - public Map> getMeshesMap() { - return indexMap; - } - - /** - * @return the amount of meshes the source mesh was split into (depends on the applied materials count) - */ - public int getMeshesPartAmount() { - return indexMap.size(); - } - - /** - * @param materialNumber - * the material number that is appied to the mesh - * @return UV coordinates of vertices that belong to the required mesh part - */ - public LinkedHashMap> getUVCoordinates(int materialNumber) { - return userUVCollection.getUVCoordinates(materialNumber); - } - - /** - * @return indicates if the mesh has UV coordinates - */ - public boolean hasUVCoordinates() { - return userUVCollection.hasUVCoordinates(); - } - - /** - * @return true if the mesh has no vertices and false otherwise - */ - public boolean isEmpty() { - return vertexMap.size() == 0; - } - - /** - * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created - * 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 - * - the reference indices list. - * - * @param basicVertexIndex - * the index of the vertex from its basic table - * @param resultIndex - * the index of the vertex in its result vertex list - * @param vertexReferenceMap - * the reference map - */ - private void appendVertexReference(int basicVertexIndex, int resultIndex, Map> vertexReferenceMap) { - List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); - if (referenceList == null) { - referenceList = new ArrayList(); - vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); - } - referenceList.add(Integer.valueOf(resultIndex)); - } -} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index a2fd878e8..b8678e641 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -32,7 +32,6 @@ package com.jme3.scene.plugins.blender.meshes; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,22 +41,20 @@ import java.util.logging.Logger; import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.materials.MaterialContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.meshes.builders.MeshBuilder; import com.jme3.scene.plugins.blender.objects.Properties; import com.jme3.scene.plugins.blender.textures.TextureHelper; import com.jme3.util.BufferUtils; @@ -71,10 +68,10 @@ public class MeshHelper extends AbstractBlenderHelper { private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); /** A type of UV data layer in traditional faced mesh (triangles or quads). */ - private static final int UV_DATA_LAYER_TYPE_FMESH = 5; + public static final int UV_DATA_LAYER_TYPE_FMESH = 5; /** A type of UV data layer in bmesh type. */ - private static final int UV_DATA_LAYER_TYPE_BMESH = 16; - + public static final int UV_DATA_LAYER_TYPE_BMESH = 16; + /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * versions. @@ -119,17 +116,7 @@ public class MeshHelper extends AbstractBlenderHelper { } LOGGER.fine("Reading vertices and their colors."); - Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext); - List verticesColors = this.getVerticesColors(structure, blenderContext); - - MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials)); - - if (this.isBMeshCompatible(structure)) { - this.readBMesh(meshBuilder, structure, blenderContext); - } else { - this.readTraditionalFaces(meshBuilder, structure, blenderContext); - } - + MeshBuilder meshBuilder = new MeshBuilder(structure, materials, blenderContext); if (meshBuilder.isEmpty()) { LOGGER.fine("The geometry is empty."); geometries = new ArrayList(0); @@ -154,52 +141,19 @@ public class MeshHelper extends AbstractBlenderHelper { Properties properties = this.loadProperties(structure, blenderContext); LOGGER.fine("Generating meshes."); - geometries = new ArrayList(meshBuilder.getMeshesPartAmount()); - for (Entry> meshEntry : meshBuilder.getMeshesMap().entrySet()) { + Map> meshes = meshBuilder.buildMeshes(); + geometries = new ArrayList(meshes.size()); + for(Entry> meshEntry : meshes.entrySet()) { int materialIndex = meshEntry.getKey(); - // key is the material index (or -1 if the material has no texture) - // value is a list of vertex indices - Mesh mesh = new Mesh(); - - // creating vertices indices for this mesh - List indexList = meshEntry.getValue(); - if (meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { - short[] indices = new short[indexList.size()]; - for (int i = 0; i < indexList.size(); ++i) { - indices[i] = indexList.get(i).shortValue(); - } - mesh.setBuffer(Type.Index, 1, indices); - } else { - int[] indices = new int[indexList.size()]; - for (int i = 0; i < indexList.size(); ++i) { - indices[i] = indexList.get(i).intValue(); + for(Mesh mesh : meshEntry.getValue()) { + LOGGER.fine("Preparing the result part."); + Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); + if (properties != null && properties.getValue() != null) { + this.applyProperties(geometry, properties); } - mesh.setBuffer(Type.Index, 1, indices); + geometries.add(geometry); + meshContext.putGeometry(materialIndex, geometry); } - - LOGGER.fine("Creating vertices buffer."); - VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); - verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getVertices(materialIndex))); - mesh.setBuffer(verticesBuffer); - - LOGGER.fine("Creating normals buffer."); - VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); - normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex))); - mesh.setBuffer(normalsBuffer); - - if (verticesColors != null) { - LOGGER.fine("Setting vertices colors."); - mesh.setBuffer(Type.Color, 4, meshBuilder.getVertexColorsBuffer(materialIndex)); - mesh.getBuffer(Type.Color).setNormalized(true); - } - - LOGGER.fine("Preparing the result part."); - Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); - if (properties != null && properties.getValue() != null) { - this.applyProperties(geometry, properties); - } - geometries.add(geometry); - meshContext.putGeometry(materialIndex, geometry); } // store the data in blender context before applying the material @@ -247,7 +201,7 @@ public class MeshHelper extends AbstractBlenderHelper { return geometries; } - + /** * Tells if the given mesh structure supports BMesh. * @@ -255,294 +209,9 @@ public class MeshHelper extends AbstractBlenderHelper { * the mesh structure * @return true if BMesh is supported and false otherwise */ - private boolean isBMeshCompatible(Structure meshStructure) { + public boolean isBMeshCompatible(Structure meshStructure) { Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); } - - /** - * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates. - * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning. - * For bmesh they are enlisted just like they are stored in the blend file (in loops). - * For traditional faces every 4 UV's should be assigned for a single face. - * @param meshStructure - * the mesh structure - * @param useBMesh - * tells if we should load the coordinates from loops of from faces - * @param blenderContext - * the blender context - * @return a map that sorts UV coordinates between different UV sets - * @throws BlenderFileException - * an exception is thrown when problems with blend file occur - */ - @SuppressWarnings("unchecked") - private Map> loadUVCoordinates(Structure meshStructure, boolean useBMesh, BlenderContext blenderContext) throws BlenderFileException { - Map> result = new HashMap>(); - if (useBMesh) { - // in this case the UV's are assigned to vertices (an array is the same length as the vertex array) - Structure loopData = (Structure) meshStructure.getFieldValue("ldata"); - Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers"); - List loopDataLayers = pLoopDataLayers.fetchData(blenderContext.getInputStream()); - for (Structure structure : loopDataLayers) { - Pointer p = (Pointer) structure.getFieldValue("data"); - if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == UV_DATA_LAYER_TYPE_BMESH) { - String uvSetName = structure.getFieldValue("name").toString(); - List uvsStructures = p.fetchData(blenderContext.getInputStream()); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray loopUVS = (DynamicArray) uvStructure.getFieldValue("uv"); - uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue())); - } - result.put(uvSetName, uvs); - } - } - } else { - // in this case UV's are assigned to faces (the array has the same legnth as the faces count) - Structure facesData = (Structure) meshStructure.getFieldValue("fdata"); - Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers"); - List facesDataLayers = pFacesDataLayers.fetchData(blenderContext.getInputStream()); - for (Structure structure : facesDataLayers) { - Pointer p = (Pointer) structure.getFieldValue("data"); - if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == UV_DATA_LAYER_TYPE_FMESH) { - String uvSetName = structure.getFieldValue("name").toString(); - List uvsStructures = p.fetchData(blenderContext.getInputStream()); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray mFaceUVs = (DynamicArray) uvStructure.getFieldValue("uv"); - uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue())); - } - result.put(uvSetName, uvs); - } - } - } - return result; - } - - /** - * This method reads the mesh from the new BMesh system. - * - * @param meshBuilder - * the mesh builder - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Reading BMesh."); - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - Map uvCoordinatesForFace = new HashMap(); - - if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) { - Map> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext); - int faceIndex = 0; - List polys = pMPoly.fetchData(blenderContext.getInputStream()); - List loops = pMLoop.fetchData(blenderContext.getInputStream()); - for (Structure poly : polys) { - int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); - int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); - int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); - boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - int[] vertexIndexes = new int[totLoop]; - - for (int i = loopStart; i < loopStart + totLoop; ++i) { - vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); - } - - int i = 0; - while (i < totLoop - 2) { - int v1 = vertexIndexes[0]; - int v2 = vertexIndexes[i + 1]; - int v3 = vertexIndexes[i + 2]; - - if (uvs != null) { - // uvs always must be added wheater we have texture or not - for (Entry> entry : uvs.entrySet()) { - Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; - uvCoordsForASingleFace[0] = entry.getValue().get(loopStart); - uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1); - uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2); - uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); - } - } - - meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex); - uvCoordinatesForFace.clear(); - ++i; - } - ++faceIndex; - } - } - } - - /** - * This method reads the mesh from traditional triangle/quad storing - * structures. - * - * @param meshBuilder - * the mesh builder - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Reading traditional faces."); - Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); - List mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null; - if (mFaces != null && mFaces.size() > 0) { - // indicates if the material with the specified number should have a texture attached - Map> uvs = this.loadUVCoordinates(meshStructure, false, blenderContext); - Map uvCoordinatesForFace = new HashMap(); - for (int i = 0; i < mFaces.size(); ++i) { - Structure mFace = mFaces.get(i); - int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); - boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - if (uvs != null) { - // uvs always must be added wheater we have texture or not - for (Entry> entry : uvs.entrySet()) { - Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; - uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); - uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1); - uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2); - uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); - } - } - - int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); - int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); - int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); - int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); - - meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i); - uvCoordinatesForFace.clear(); - if (v4 > 0) { - if (uvs != null) { - // uvs always must be added wheater we have texture or not - for (Entry> entry : uvs.entrySet()) { - Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; - uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); - uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2); - uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3); - uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); - } - } - meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i); - uvCoordinatesForFace.clear(); - } - } - } else { - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - List mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null; - if (mEdges != null && mEdges.size() > 0) { - for (int i = 0; i < mEdges.size(); ++i) { - Structure mEdge = mEdges.get(i); - boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - - int v1 = ((Number) mEdge.getFieldValue("v1")).intValue(); - int v2 = ((Number) mEdge.getFieldValue("v2")).intValue(); - - meshBuilder.appendEdge(v1, v2, smooth); - } - } - } - } - - /** - * @return true if the material has at least one generated component and false otherwise - */ - private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { - if (materials != null) { - for (MaterialContext material : materials) { - if (material != null && material.hasGeneratedTextures()) { - return true; - } - } - } - return false; - } - - /** - * This method returns the vertices colors. Each vertex is stored in byte[4] array. - * - * @param meshStructure - * the structure containing the mesh data - * @param blenderContext - * the blender context - * @return a list of vertices colors, each color belongs to a single vertex - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - private List getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - Pointer pMCol = (Pointer) meshStructure.getFieldValue(this.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); - List verticesColors = null; - //it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure) - //so we need to put them right - boolean useBGRA = blenderContext.getBlenderVersion() < 263; - if (pMCol.isNotNull()) { - List mCol = pMCol.fetchData(blenderContext.getInputStream()); - verticesColors = new ArrayList(mCol.size()); - for (Structure color : mCol) { - byte r = ((Number) color.getFieldValue("r")).byteValue(); - byte g = ((Number) color.getFieldValue("g")).byteValue(); - byte b = ((Number) color.getFieldValue("b")).byteValue(); - byte a = ((Number) color.getFieldValue("a")).byteValue(); - verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a }); - } - } - return verticesColors; - } - - /** - * This method returns the vertices. - * - * @param meshStructure - * the structure containing the mesh data - * @param blenderContext - * the blender context - * @return a list of two - element arrays, the first element is the vertex and the second - its normal - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - @SuppressWarnings("unchecked") - private Vector3f[][] getVerticesAndNormals(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); - Vector3f[][] result = new Vector3f[count][2]; - if (count == 0) { - return result; - } - - Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); - List mVerts = pMVert.fetchData(blenderContext.getInputStream()); - if (fixUpAxis) { - for (int i = 0; i < count; ++i) { - DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); - - DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); - result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f); - } - } else { - for (int i = 0; i < count; ++i) { - DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); - - DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); - result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f); - } - } - return result; - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java new file mode 100644 index 000000000..1c1a4c6e5 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java @@ -0,0 +1,591 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.textures.UserUVCollection; +import com.jme3.util.BufferUtils; + +/** + * A builder class for meshes made of triangles (faces). + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class FaceMeshBuilder { + private static final Logger LOGGER = Logger.getLogger(FaceMeshBuilder.class.getName()); + + /** An array of reference vertices. */ + private Vector3f[][] verticesAndNormals; + /** An list of vertices colors. */ + private List verticesColors; + /** A variable that indicates if the model uses generated textures. */ + private boolean usesGeneratedTextures; + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ + private Map>> globalVertexReferenceMap; + /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ + private Map> normalMap = new HashMap>(); + /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ + private Map> vertexMap = new HashMap>(); + /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ + private Map> vertexColorsMap = new HashMap>(); + /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ + private Map> indexMap = new HashMap>(); + /** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */ + private UserUVCollection userUVCollection = new UserUVCollection(); + + /** + * Constructor. Stores the given array (not copying it). + * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. + * The amount of vertices is always faceCount * 3. + * @param verticesAndNormals + * the reference vertices and normals array + * @param usesGeneratedTextures + * a variable that indicates if the model uses generated textures or not + */ + public FaceMeshBuilder(Vector3f[][] verticesAndNormals, boolean usesGeneratedTextures) { + this.verticesAndNormals = verticesAndNormals; + this.usesGeneratedTextures = usesGeneratedTextures; + globalVertexReferenceMap = new HashMap>>(verticesAndNormals.length); + } + + public void readMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + verticesColors = this.getVerticesColors(structure, blenderContext); + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + + if (meshHelper.isBMeshCompatible(structure)) { + this.readBMesh(structure, blenderContext); + } else { + this.readTraditionalFaces(structure, blenderContext); + } + } + + /** + * Builds the meshes. + * @return a map between material index and the mesh + */ + public Map buildMeshes() { + Map result = new HashMap(indexMap.size()); + + for (Entry> meshEntry : indexMap.entrySet()) { + int materialIndex = meshEntry.getKey(); + // key is the material index (or -1 if the material has no texture) + // value is a list of vertex indices + Mesh mesh = new Mesh(); + + // creating vertices indices for this mesh + List indexList = meshEntry.getValue(); + if (this.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { + short[] indices = new short[indexList.size()]; + for (int i = 0; i < indexList.size(); ++i) { + indices[i] = indexList.get(i).shortValue(); + } + mesh.setBuffer(Type.Index, 1, indices); + } else { + int[] indices = new int[indexList.size()]; + for (int i = 0; i < indexList.size(); ++i) { + indices[i] = indexList.get(i).intValue(); + } + mesh.setBuffer(Type.Index, 1, indices); + } + + LOGGER.fine("Creating vertices buffer."); + VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); + verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getVertices(materialIndex))); + mesh.setBuffer(verticesBuffer); + + LOGGER.fine("Creating normals buffer."); + VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); + normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getNormals(materialIndex))); + mesh.setBuffer(normalsBuffer); + + if (verticesColors != null) { + LOGGER.fine("Setting vertices colors."); + mesh.setBuffer(Type.Color, 4, this.getVertexColorsBuffer(materialIndex)); + mesh.getBuffer(Type.Color).setNormalized(true); + } + + result.put(materialIndex, mesh); + } + + return result; + } + + /** + * This method reads the mesh from the new BMesh system. + * + * @param meshStructure + * the mesh structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private void readBMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.fine("Reading BMesh."); + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + Map uvCoordinatesForFace = new HashMap(); + + if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) { + Map> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext); + List polys = pMPoly.fetchData(blenderContext.getInputStream()); + List loops = pMLoop.fetchData(blenderContext.getInputStream()); + int[] vertexColorIndex = verticesColors == null ? null : new int[3]; + for (Structure poly : polys) { + int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); + int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); + int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); + boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + int[] vertexIndexes = new int[totLoop]; + + for (int i = loopStart; i < loopStart + totLoop; ++i) { + vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); + } + + int i = 0; + while (i < totLoop - 2) { + int v1 = vertexIndexes[0]; + int v2 = vertexIndexes[i + 1]; + int v3 = vertexIndexes[i + 2]; + if(vertexColorIndex != null) { + vertexColorIndex[0] = loopStart; + vertexColorIndex[1] = loopStart + i + 1; + vertexColorIndex[2] = loopStart + i + 2; + } + + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(loopStart); + uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1); + uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + + this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + ++i; + } + } + } + } + + /** + * This method reads the mesh from traditional triangle/quad storing + * structures. + * + * @param meshStructure + * the mesh structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private void readTraditionalFaces(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.fine("Reading traditional faces."); + Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); + List mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null; + if (mFaces != null && mFaces.size() > 0) { + // indicates if the material with the specified number should have a texture attached + Map> uvs = this.loadUVCoordinates(meshStructure, false, blenderContext); + Map uvCoordinatesForFace = new HashMap(); + int[] vertexColorIndex = verticesColors == null ? null : new int[3]; + for (int i = 0; i < mFaces.size(); ++i) { + Structure mFace = mFaces.get(i); + int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); + boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); + uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1); + uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + + int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); + int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); + int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); + int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); + if(vertexColorIndex != null) { + vertexColorIndex[0] = i * 4; + vertexColorIndex[1] = i * 4 + 1; + vertexColorIndex[2] = i * 4 + 2; + } + + this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + if (v4 > 0) { + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); + uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2); + uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + if(vertexColorIndex != null) { + vertexColorIndex[0] = i * 4; + vertexColorIndex[1] = i * 4 + 2; + vertexColorIndex[2] = i * 4 + 3; + } + this.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + } + } + } + } + + /** + * This method adds a face to the mesh. + * @param v1 + * index of the 1'st vertex from the reference vertex table + * @param v2 + * index of the 2'nd vertex from the reference vertex table + * @param v3 + * index of the 3'rd vertex from the reference vertex table + * @param smooth + * indicates if this face should have smooth shading or flat shading + * @param materialNumber + * the material number for this face + * @param uvsForFace + * a 3-element array of vertices UV coordinates mapped to the UV's set name + */ + private void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map uvsForFace, int[] vertexColorIndex) { + if (uvsForFace != null && uvsForFace.size() > 0) { + for (Entry entry : uvsForFace.entrySet()) { + if (entry.getValue().length != 3) { + throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : "")); + } + } + } + + // getting the required lists + List indexList = indexMap.get(materialNumber); + if (indexList == null) { + indexList = new ArrayList(); + indexMap.put(materialNumber, indexList); + } + List vertexList = vertexMap.get(materialNumber); + if (vertexList == null) { + vertexList = new ArrayList(); + vertexMap.put(materialNumber, vertexList); + } + List vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; + if (vertexColorsList == null && vertexColorsMap != null) { + vertexColorsList = new ArrayList(); + vertexColorsMap.put(materialNumber, vertexColorsList); + } + List normalList = normalMap.get(materialNumber); + if (normalList == null) { + normalList = new ArrayList(); + normalMap.put(materialNumber, normalList); + } + Map> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); + if (vertexReferenceMap == null) { + vertexReferenceMap = new HashMap>(); + globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); + } + + // creating faces + Integer[] index = new Integer[] { v1, v2, v3 }; + if (smooth && !usesGeneratedTextures) { + for (int i = 0; i < 3; ++i) { + if (!vertexReferenceMap.containsKey(index[i])) { + // if this index is not yet used then create another face + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + if (uvsForFace != null) { + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + } + + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + + index[i] = vertexList.size() - 1; + } else if (uvsForFace != null) { + // if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's + // in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert + // because in jme one vertex can have only on UV coordinate + boolean vertexAlreadyUsed = false; + for (Integer vertexIndex : vertexReferenceMap.get(index[i])) { + int vertexUseCounter = 0; + for (Entry entry : uvsForFace.entrySet()) { + if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) { + ++vertexUseCounter; + } + } + if (vertexUseCounter == uvsForFace.size()) { + vertexAlreadyUsed = true; + index[i] = vertexIndex; + break; + } + } + + if (!vertexAlreadyUsed) { + // treat this face as a new one because its vertices have separate UV's + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + index[i] = vertexList.size() - 1; + } + } else { + // use this index again + index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); + } + indexList.add(index[i]); + } + } else { + Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); + for (int i = 0; i < 3; ++i) { + indexList.add(vertexList.size()); + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + if (uvsForFace != null) { + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + } + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); + } + } + } + + /** + * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates. + * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning. + * For bmesh they are enlisted just like they are stored in the blend file (in loops). + * For traditional faces every 4 UV's should be assigned for a single face. + * @param meshStructure + * the mesh structure + * @param useBMesh + * tells if we should load the coordinates from loops of from faces + * @param blenderContext + * the blender context + * @return a map that sorts UV coordinates between different UV sets + * @throws BlenderFileException + * an exception is thrown when problems with blend file occur + */ + @SuppressWarnings("unchecked") + private Map> loadUVCoordinates(Structure meshStructure, boolean useBMesh, BlenderContext blenderContext) throws BlenderFileException { + Map> result = new HashMap>(); + if (useBMesh) { + // in this case the UV's are assigned to vertices (an array is the same length as the vertex array) + Structure loopData = (Structure) meshStructure.getFieldValue("ldata"); + Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers"); + List loopDataLayers = pLoopDataLayers.fetchData(blenderContext.getInputStream()); + for (Structure structure : loopDataLayers) { + Pointer p = (Pointer) structure.getFieldValue("data"); + if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) { + String uvSetName = structure.getFieldValue("name").toString(); + List uvsStructures = p.fetchData(blenderContext.getInputStream()); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray loopUVS = (DynamicArray) uvStructure.getFieldValue("uv"); + uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue())); + } + result.put(uvSetName, uvs); + } + } + } else { + // in this case UV's are assigned to faces (the array has the same legnth as the faces count) + Structure facesData = (Structure) meshStructure.getFieldValue("fdata"); + Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers"); + List facesDataLayers = pFacesDataLayers.fetchData(blenderContext.getInputStream()); + for (Structure structure : facesDataLayers) { + Pointer p = (Pointer) structure.getFieldValue("data"); + if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) { + String uvSetName = structure.getFieldValue("name").toString(); + List uvsStructures = p.fetchData(blenderContext.getInputStream()); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray mFaceUVs = (DynamicArray) uvStructure.getFieldValue("uv"); + uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue())); + } + result.put(uvSetName, uvs); + } + } + } + return result; + } + + /** + * This method returns the vertices colors. Each vertex is stored in byte[4] array. + * + * @param meshStructure + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of vertices colors, each color belongs to a single vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + private List getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); + List verticesColors = null; + // it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure) + // so we need to put them right + boolean useBGRA = blenderContext.getBlenderVersion() < 263; + if (pMCol.isNotNull()) { + List mCol = pMCol.fetchData(blenderContext.getInputStream()); + verticesColors = new ArrayList(mCol.size()); + for (Structure color : mCol) { + byte r = ((Number) color.getFieldValue("r")).byteValue(); + byte g = ((Number) color.getFieldValue("g")).byteValue(); + byte b = ((Number) color.getFieldValue("b")).byteValue(); + byte a = ((Number) color.getFieldValue("a")).byteValue(); + verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a }); + } + } + return verticesColors; + } + + /** + * @return a map that maps vertex index from reference array to its indices in the result list + */ + public Map>> getVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * @param materialNumber + * the material index + * @return result vertices array + */ + private Vector3f[] getVertices(int materialNumber) { + return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the amount of result vertices + */ + private int getVerticesAmount(int materialNumber) { + return vertexMap.get(materialNumber).size(); + } + + /** + * @param materialNumber + * the material index + * @return normals result array + */ + private Vector3f[] getNormals(int materialNumber) { + return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the vertices colors buffer or null if no vertex colors is set + */ + private ByteBuffer getVertexColorsBuffer(int materialNumber) { + ByteBuffer result = null; + if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { + List data = vertexColorsMap.get(materialNumber); + result = BufferUtils.createByteBuffer(4 * data.size()); + for (byte[] v : data) { + if (v != null) { + result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); + } else { + result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); + } + } + result.flip(); + } + return result; + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public LinkedHashMap> getUVCoordinates(int materialNumber) { + return userUVCollection.getUVCoordinates(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return userUVCollection.hasUVCoordinates(); + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return vertexMap.size() == 0; + } + + /** + * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created + * 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 + * - the reference indices list. + * + * @param basicVertexIndex + * the index of the vertex from its basic table + * @param resultIndex + * the index of the vertex in its result vertex list + * @param vertexReferenceMap + * the reference map + */ + private void appendVertexReference(int basicVertexIndex, int resultIndex, Map> vertexReferenceMap) { + List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); + if (referenceList == null) { + referenceList = new ArrayList(); + vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); + } + referenceList.add(Integer.valueOf(resultIndex)); + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java new file mode 100644 index 000000000..4ecaa09d4 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java @@ -0,0 +1,5 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +/*package*/ class LineMeshBuilder { + //TODO: this will be implemented soon +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java new file mode 100644 index 000000000..38aa431d3 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java @@ -0,0 +1,129 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; + +public class MeshBuilder { + private boolean fixUpAxis; + //TODO: these will be added soon +// private PointMeshBuilder pointMeshBuilder; +// private LineMeshBuilder lineMeshBuilder; + private FaceMeshBuilder faceMeshBuilder; + + public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException { + fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); + Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure, blenderContext); + boolean generatedTexturesPresent = this.areGeneratedTexturesPresent(materials); + + faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, generatedTexturesPresent); + faceMeshBuilder.readMesh(meshStructure, blenderContext); + } + + public Map> buildMeshes() { + Map> result = new HashMap>(); + + Map meshes = faceMeshBuilder.buildMeshes(); + for(Entry entry : meshes.entrySet()) { + List meshList = new ArrayList(); + meshList.add(entry.getValue()); + result.put(entry.getKey(), meshList); + } + return result; + } + + public boolean isEmpty() { + return faceMeshBuilder.isEmpty(); + } + + /** + * @return a map that maps vertex index from reference array to its indices in the result list + */ + public Map>> getVertexReferenceMap() { + return faceMeshBuilder.getVertexReferenceMap(); + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public LinkedHashMap> getUVCoordinates(int materialNumber) { + return faceMeshBuilder.getUVCoordinates(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return faceMeshBuilder.hasUVCoordinates(); + } + + /** + * This method returns the vertices. + * + * @param meshStructure + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of two - element arrays, the first element is the vertex and the second - its normal + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + @SuppressWarnings("unchecked") + private Vector3f[][] getVerticesAndNormals(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); + Vector3f[][] result = new Vector3f[count][2]; + if (count == 0) { + return result; + } + + Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); + List mVerts = pMVert.fetchData(blenderContext.getInputStream()); + if (fixUpAxis) { + for (int i = 0; i < count; ++i) { + DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); + + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f); + } + } else { + for (int i = 0; i < count; ++i) { + DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); + + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f); + } + } + return result; + } + + /** + * @return true if the material has at least one generated component and false otherwise + */ + private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { + if (materials != null) { + for (MaterialContext material : materials) { + if (material != null && material.hasGeneratedTextures()) { + return true; + } + } + } + return false; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java new file mode 100644 index 000000000..b85a8a71e --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java @@ -0,0 +1,5 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +/*package*/ class PointMeshBuilder { + //TODO: this will be implemented soon +}