ソースを参照

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
Kae..pl 11 年 前
コミット
69e4392ba9

+ 0 - 342
engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java

@@ -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<byte[]>                              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<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
-    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Vector3f>>              normalMap        = new HashMap<Integer, List<Vector3f>>();
-    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Vector3f>>              vertexMap        = new HashMap<Integer, List<Vector3f>>();
-    /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<byte[]>>                vertexColorsMap  = new HashMap<Integer, List<byte[]>>();
-    /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Integer>>               indexMap         = new HashMap<Integer, List<Integer>>();
-    /** 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<byte[]> verticesColors, boolean usesGeneratedTextures) {
-        this.verticesAndNormals = verticesAndNormals;
-        this.verticesColors = verticesColors;
-        this.usesGeneratedTextures = usesGeneratedTextures;
-        globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(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<String, Vector2f[]> uvsForFace, boolean quad, int faceIndex) {
-        if (uvsForFace != null && uvsForFace.size() > 0) {
-            for (Entry<String, Vector2f[]> 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<Integer> indexList = indexMap.get(materialNumber);
-        if (indexList == null) {
-            indexList = new ArrayList<Integer>();
-            indexMap.put(materialNumber, indexList);
-        }
-        List<Vector3f> vertexList = vertexMap.get(materialNumber);
-        if (vertexList == null) {
-            vertexList = new ArrayList<Vector3f>();
-            vertexMap.put(materialNumber, vertexList);
-        }
-        List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
-        int[] vertexColorIndex = new int[] { 0, 1, 2 };
-        if (vertexColorsList == null && vertexColorsMap != null) {
-            vertexColorsList = new ArrayList<byte[]>();
-            vertexColorsMap.put(materialNumber, vertexColorsList);
-        }
-        List<Vector3f> normalList = normalMap.get(materialNumber);
-        if (normalList == null) {
-            normalList = new ArrayList<Vector3f>();
-            normalMap.put(materialNumber, normalList);
-        }
-        Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
-        if (vertexReferenceMap == null) {
-            vertexReferenceMap = new HashMap<Integer, List<Integer>>();
-            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<String, Vector2f[]> 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<String, Vector2f[]> 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<String, Vector2f[]> 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<String, Vector2f[]> 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<Integer, Map<Integer, List<Integer>>> 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<byte[]> 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<Integer, List<Integer>> 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<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
-        return userUVCollection.getUVCoordinates(materialNumber);
-    }
-
-    /**
-     * @return indicates if the mesh has UV coordinates
-     */
-    public boolean hasUVCoordinates() {
-        return userUVCollection.hasUVCoordinates();
-    }
-
-    /**
-     * @return <b>true</b> if the mesh has no vertices and <b>false</b> 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<Integer, List<Integer>> vertexReferenceMap) {
-        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
-        if (referenceList == null) {
-            referenceList = new ArrayList<Integer>();
-            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
-        }
-        referenceList.add(Integer.valueOf(resultIndex));
-    }
-}

+ 17 - 348
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<byte[]> 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<Geometry>(0);
@@ -154,52 +141,19 @@ public class MeshHelper extends AbstractBlenderHelper {
         Properties properties = this.loadProperties(structure, blenderContext);
 
         LOGGER.fine("Generating meshes.");
-        geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount());
-        for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) {
+        Map<Integer, List<Mesh>> meshes = meshBuilder.buildMeshes();
+        geometries = new ArrayList<Geometry>(meshes.size());
+        for(Entry<Integer, List<Mesh>> 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<Integer> 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 <b>true</b> if BMesh is supported and <b>false</b> 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<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure, boolean useBMesh, BlenderContext blenderContext) throws BlenderFileException {
-        Map<String, List<Vector2f>> result = new HashMap<String, List<Vector2f>>();
-        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<Structure> 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<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
-                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
-                    for (Structure uvStructure : uvsStructures) {
-                        DynamicArray<Number> loopUVS = (DynamicArray<Number>) 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<Structure> 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<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
-                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
-                    for (Structure uvStructure : uvsStructures) {
-                        DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) 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<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
-
-        if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
-            Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext);
-            int faceIndex = 0;
-            List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
-            List<Structure> 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<String, List<Vector2f>> 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<Structure> 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<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, false, blenderContext);
-            Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
-            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<String, List<Vector2f>> 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<String, List<Vector2f>> 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<Structure> 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 <b>true</b> if the material has at least one generated component and <b>false</b> 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<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
-        Pointer pMCol = (Pointer) meshStructure.getFieldValue(this.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
-        List<byte[]> 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<Structure> mCol = pMCol.fetchData(blenderContext.getInputStream());
-            verticesColors = new ArrayList<byte[]>(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<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream());
-        if (fixUpAxis) {
-            for (int i = 0; i < count; ++i) {
-                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
-                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
-
-                DynamicArray<Number> normals = (DynamicArray<Number>) 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<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
-                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
-
-                DynamicArray<Number> normals = (DynamicArray<Number>) 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;
-    }
 }

+ 591 - 0
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<byte[]>                              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<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
+    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
+    private Map<Integer, List<Vector3f>>              normalMap                = new HashMap<Integer, List<Vector3f>>();
+    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
+    private Map<Integer, List<Vector3f>>              vertexMap                = new HashMap<Integer, List<Vector3f>>();
+    /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */
+    private Map<Integer, List<byte[]>>                vertexColorsMap          = new HashMap<Integer, List<byte[]>>();
+    /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */
+    private Map<Integer, List<Integer>>               indexMap                 = new HashMap<Integer, List<Integer>>();
+    /** 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<Integer, Map<Integer, List<Integer>>>(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<Integer, Mesh> buildMeshes() {
+        Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(indexMap.size());
+
+        for (Entry<Integer, List<Integer>> 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<Integer> 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<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
+
+        if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
+            Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext);
+            List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
+            List<Structure> 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<String, List<Vector2f>> 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<Structure> 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<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, false, blenderContext);
+            Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
+            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<String, List<Vector2f>> 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<String, List<Vector2f>> 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<String, Vector2f[]> uvsForFace, int[] vertexColorIndex) {
+        if (uvsForFace != null && uvsForFace.size() > 0) {
+            for (Entry<String, Vector2f[]> 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<Integer> indexList = indexMap.get(materialNumber);
+        if (indexList == null) {
+            indexList = new ArrayList<Integer>();
+            indexMap.put(materialNumber, indexList);
+        }
+        List<Vector3f> vertexList = vertexMap.get(materialNumber);
+        if (vertexList == null) {
+            vertexList = new ArrayList<Vector3f>();
+            vertexMap.put(materialNumber, vertexList);
+        }
+        List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
+        if (vertexColorsList == null && vertexColorsMap != null) {
+            vertexColorsList = new ArrayList<byte[]>();
+            vertexColorsMap.put(materialNumber, vertexColorsList);
+        }
+        List<Vector3f> normalList = normalMap.get(materialNumber);
+        if (normalList == null) {
+            normalList = new ArrayList<Vector3f>();
+            normalMap.put(materialNumber, normalList);
+        }
+        Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
+        if (vertexReferenceMap == null) {
+            vertexReferenceMap = new HashMap<Integer, List<Integer>>();
+            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<String, Vector2f[]> 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<String, Vector2f[]> 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<String, Vector2f[]> 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<String, Vector2f[]> 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<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure, boolean useBMesh, BlenderContext blenderContext) throws BlenderFileException {
+        Map<String, List<Vector2f>> result = new HashMap<String, List<Vector2f>>();
+        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<Structure> 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<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
+                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
+                    for (Structure uvStructure : uvsStructures) {
+                        DynamicArray<Number> loopUVS = (DynamicArray<Number>) 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<Structure> 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<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
+                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
+                    for (Structure uvStructure : uvsStructures) {
+                        DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) 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<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
+        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+        Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
+        List<byte[]> 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<Structure> mCol = pMCol.fetchData(blenderContext.getInputStream());
+            verticesColors = new ArrayList<byte[]>(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<Integer, Map<Integer, List<Integer>>> 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<byte[]> 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<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
+        return userUVCollection.getUVCoordinates(materialNumber);
+    }
+
+    /**
+     * @return indicates if the mesh has UV coordinates
+     */
+    public boolean hasUVCoordinates() {
+        return userUVCollection.hasUVCoordinates();
+    }
+
+    /**
+     * @return <b>true</b> if the mesh has no vertices and <b>false</b> 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<Integer, List<Integer>> vertexReferenceMap) {
+        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
+        if (referenceList == null) {
+            referenceList = new ArrayList<Integer>();
+            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
+        }
+        referenceList.add(Integer.valueOf(resultIndex));
+    }
+}

+ 5 - 0
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
+}

+ 129 - 0
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<Integer, List<Mesh>> buildMeshes() {
+        Map<Integer, List<Mesh>> result = new HashMap<Integer, List<Mesh>>();
+        
+        Map<Integer, Mesh> meshes = faceMeshBuilder.buildMeshes();
+        for(Entry<Integer, Mesh> entry : meshes.entrySet()) {
+            List<Mesh> meshList = new ArrayList<Mesh>();
+            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<Integer, Map<Integer, List<Integer>>> 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<String, List<Vector2f>> 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<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream());
+        if (fixUpAxis) {
+            for (int i = 0; i < count; ++i) {
+                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
+                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
+
+                DynamicArray<Number> normals = (DynamicArray<Number>) 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<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
+                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
+
+                DynamicArray<Number> normals = (DynamicArray<Number>) 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 <b>true</b> if the material has at least one generated component and <b>false</b> otherwise
+     */
+    private boolean areGeneratedTexturesPresent(MaterialContext[] materials) {
+        if (materials != null) {
+            for (MaterialContext material : materials) {
+                if (material != null && material.hasGeneratedTextures()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}

+ 5 - 0
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
+}