Procházet zdrojové kódy

Feature: added support for subdivision surface modifier.

jmekaelthas před 11 roky
rodič
revize
f364d66640

+ 25 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java

@@ -94,6 +94,22 @@ public class Edge extends Line {
         return index2;
     }
 
+    /**
+     * Returns the index other than the given.
+     * @param index
+     *            index of the edge
+     * @return the remaining index number
+     */
+    public int getOtherIndex(int index) {
+        if (index == index1) {
+            return index2;
+        }
+        if (index == index2) {
+            return index1;
+        }
+        throw new IllegalArgumentException("Cannot give the other index for [" + index + "] because this index does not exist in edge: " + this);
+    }
+
     /**
      * @return the crease value of the edge (its weight)
      */
@@ -108,6 +124,15 @@ public class Edge extends Line {
         return inFace;
     }
 
+    /**
+     * @return the centroid of the edge
+     */
+    public Vector3f computeCentroid() {
+        Vector3f v1 = this.getOrigin();
+        Vector3f v2 = v1.add(this.getDirection());
+        return v2.addLocal(v1).divideLocal(2);
+    }
+
     /**
      * Shifts indexes by a given amount.
      * @param shift

+ 36 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java

@@ -131,6 +131,18 @@ public class Face implements Comparator<Integer> {
         return indexes;
     }
 
+    /**
+     * @return the centroid of the face
+     */
+    public Vector3f computeCentroid() {
+        Vector3f result = new Vector3f();
+        List<Vector3f> vertices = temporalMesh.getVertices();
+        for (Integer index : indexes) {
+            result.addLocal(vertices.get(index));
+        }
+        return result.divideLocal(indexes.size());
+    }
+
     /**
      * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
      */
@@ -408,6 +420,30 @@ public class Face implements Comparator<Integer> {
         return true;
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + indexes.hashCode();
+        result = prime * result + temporalMesh.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Face)) {
+            return false;
+        }
+        Face other = (Face) obj;
+        if (!indexes.equals(other.indexes)) {
+            return false;
+        }
+        return temporalMesh.equals(other.temporalMesh);
+    }
+
     /**
      * Loads all faces of a given mesh.
      * @param meshStructure

+ 24 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java

@@ -121,12 +121,34 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
      * @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
      */
     public boolean areNeighbours(Integer index1, Integer index2) {
-        if (index1.equals(index2)) {
+        if (index1.equals(index2) || !edges.containsKey(index1) || !edges.containsKey(index2)) {
             return false;
         }
         return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
     }
 
+    /**
+     * Returns the value of the index located after the given one. Pointint the last index will return the first one.
+     * @param index
+     *            the index value
+     * @return the value of 'next' index
+     */
+    public Integer getNextIndex(Integer index) {
+        int i = nodes.indexOf(index);
+        return i == nodes.size() - 1 ? nodes.get(0) : nodes.get(i + 1);
+    }
+
+    /**
+     * Returns the value of the index located before the given one. Pointint the first index will return the last one.
+     * @param index
+     *            the index value
+     * @return the value of 'previous' index
+     */
+    public Integer getPreviousIndex(Integer index) {
+        int i = nodes.indexOf(index);
+        return i == 0 ? nodes.get(nodes.size() - 1) : nodes.get(i - 1);
+    }
+
     /**
      * The method shifts all indexes by a given value.
      * @param shift
@@ -171,7 +193,7 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
      *            the index whose neighbour count will be checked
      * @return the count of neighbours of the given index
      */
-    public int getNeighbourCount(Integer index) {
+    private int getNeighbourCount(Integer index) {
         int result = 0;
         if (edges.containsKey(index)) {
             result = edges.get(index).size();

+ 120 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java

@@ -72,6 +72,9 @@ public class TemporalMesh extends Geometry {
     /** The points of the mesh. */
     protected List<Point>              points                    = new ArrayList<Point>();
 
+    protected Map<Integer, List<Face>> indexToFaceMapping        = new HashMap<Integer, List<Face>>();
+    protected Map<Integer, List<Edge>> indexToEdgeMapping        = new HashMap<Integer, List<Edge>>();
+
     /** The bounding box of the temporal mesh. */
     protected BoundingBox              boundingBox;
 
@@ -116,6 +119,8 @@ public class TemporalMesh extends Geometry {
             faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
             edges = Edge.loadAll(meshStructure);
             points = Point.loadAll(meshStructure);
+
+            this.rebuildIndexesMappings();
         }
     }
 
@@ -175,6 +180,61 @@ public class TemporalMesh extends Geometry {
         return vertexGroups;
     }
 
+    /**
+     * @return the faces that contain the given index or null if none contain it
+     */
+    public List<Face> getAdjacentFaces(Integer index) {
+        return indexToFaceMapping.get(index);
+    }
+
+    /**
+     * @param the
+     *            edge of the mesh
+     * @return a list of faces that contain the given edge or an empty list
+     */
+    public List<Face> getAdjacentFaces(Edge edge) {
+        List<Face> result = new ArrayList<Face>(indexToFaceMapping.get(edge.getFirstIndex()));
+        result.retainAll(indexToFaceMapping.get(edge.getSecondIndex()));
+        return result;
+    }
+
+    /**
+     * @param the
+     *            index of the mesh
+     * @return a list of edges that contain the index
+     */
+    public List<Edge> getAdjacentEdges(Integer index) {
+        return indexToEdgeMapping.get(index);
+    }
+
+    /**
+     * Tells if the given edge is a boundary edge. The boundary edge means that it belongs to a single
+     * face or to none.
+     * @param the
+     *            edge of the mesh
+     * @return <b>true</b> if the edge is a boundary one and <b>false</b> otherwise
+     */
+    public boolean isBoundary(Edge edge) {
+        return this.getAdjacentFaces(edge).size() <= 1;
+    }
+
+    /**
+     * The method tells if the given index is a boundary index. A boundary index belongs to at least
+     * one boundary edge.
+     * @param index
+     *            the index of the mesh
+     * @return <b>true</b> if the index is a boundary one and <b>false</b> otherwise
+     */
+    public boolean isBoundary(Integer index) {
+        List<Edge> adjacentEdges = indexToEdgeMapping.get(index);
+        for (Edge edge : adjacentEdges) {
+            if (this.isBoundary(edge)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public TemporalMesh clone() {
         try {
@@ -205,6 +265,7 @@ public class TemporalMesh extends Geometry {
             for (Point point : points) {
                 result.points.add(point.clone());
             }
+            result.rebuildIndexesMappings();
             return result;
         } catch (BlenderFileException e) {
             LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage());
@@ -212,6 +273,41 @@ public class TemporalMesh extends Geometry {
         return null;
     }
 
+    /**
+     * The method rebuilds the mappings between faces and edges. Should be called after
+     * every major change of the temporal mesh done outside it.
+     * @note I will remove this method soon and make the mappings to be done automatically
+     *       when the mesh is modified.
+     */
+    public void rebuildIndexesMappings() {
+        indexToEdgeMapping.clear();
+        indexToFaceMapping.clear();
+        for (Face face : faces) {
+            for (Integer index : face.getIndexes()) {
+                List<Face> faces = indexToFaceMapping.get(index);
+                if (faces == null) {
+                    faces = new ArrayList<Face>();
+                    indexToFaceMapping.put(index, faces);
+                }
+                faces.add(face);
+            }
+        }
+        for (Edge edge : edges) {
+            List<Edge> edges = indexToEdgeMapping.get(edge.getFirstIndex());
+            if (edges == null) {
+                edges = new ArrayList<Edge>();
+                indexToEdgeMapping.put(edge.getFirstIndex(), edges);
+            }
+            edges.add(edge);
+            edges = indexToEdgeMapping.get(edge.getSecondIndex());
+            if (edges == null) {
+                edges = new ArrayList<Edge>();
+                indexToEdgeMapping.put(edge.getSecondIndex(), edges);
+            }
+            edges.add(edge);
+        }
+    }
+
     @Override
     public void updateModelBound() {
         if (boundingBox == null) {
@@ -285,6 +381,8 @@ public class TemporalMesh extends Geometry {
         vertexGroups.addAll(mesh.vertexGroups);
         verticesColors.addAll(mesh.verticesColors);
         boneIndexes.putAll(mesh.boneIndexes);
+
+        this.rebuildIndexesMappings();
     }
 
     /**
@@ -341,6 +439,8 @@ public class TemporalMesh extends Geometry {
         faces.clear();
         edges.clear();
         points.clear();
+        indexToEdgeMapping.clear();
+        indexToFaceMapping.clear();
     }
 
     /**
@@ -600,4 +700,24 @@ public class TemporalMesh extends Geometry {
     public String toString() {
         return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]";
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (meshStructure == null ? 0 : meshStructure.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof TemporalMesh)) {
+            return false;
+        }
+        TemporalMesh other = (TemporalMesh) obj;
+        return meshStructure.getOldMemoryAddress().equals(other.meshStructure.getOldMemoryAddress());
+    }
 }

+ 2 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java

@@ -96,6 +96,8 @@ public class ModifierHelper extends AbstractBlenderHelper {
                     modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
                 } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
                     modifier = new ParticlesModifier(modifierStructure, blenderContext);
+                } else if(Modifier.SUBSURF_MODIFIER_DATA.equals(modifierStructure.getType())) {
+                    modifier = new SubdivisionSurfaceModifier(modifierStructure, blenderContext);
                 }
 
                 if (modifier != null) {

+ 530 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java

@@ -0,0 +1,530 @@
+package com.jme3.scene.plugins.blender.modifiers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.Edge;
+import com.jme3.scene.plugins.blender.meshes.Face;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
+import com.jme3.scene.plugins.blender.textures.TexturePixel;
+
+/**
+ * A modifier that subdivides the mesh using either simple or catmull-clark subdivision.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class SubdivisionSurfaceModifier extends Modifier {
+    private static final Logger LOGGER             = Logger.getLogger(SubdivisionSurfaceModifier.class.getName());
+
+    private static final int    TYPE_CATMULLCLARK  = 0;
+    private static final int    TYPE_SIMPLE        = 1;
+
+    private static final int    FLAG_SUBDIVIDE_UVS = 0x8;
+
+    /** The subdivision type. */
+    private int                 subdivType;
+    /** The amount of subdivision levels. */
+    private int                 levels;
+    /** Indicates if the UV's should also be subdivided. */
+    private boolean             subdivideUVS;
+
+    /**
+     * Constructor loads all neccessary modifier data.
+     * @param modifierStructure
+     *            the modifier structure
+     * @param blenderContext
+     *            the blender context
+     */
+    public SubdivisionSurfaceModifier(Structure modifierStructure, BlenderContext blenderContext) {
+        if (this.validate(modifierStructure, blenderContext)) {
+            subdivType = ((Number) modifierStructure.getFieldValue("subdivType")).intValue();
+            levels = ((Number) modifierStructure.getFieldValue("levels")).intValue();
+            int flag = ((Number) modifierStructure.getFieldValue("flags")).intValue();
+            subdivideUVS = (flag & FLAG_SUBDIVIDE_UVS) != 0 && subdivType == TYPE_CATMULLCLARK;
+
+            if (subdivType != TYPE_CATMULLCLARK && subdivType != TYPE_SIMPLE) {
+                LOGGER.log(Level.SEVERE, "Unknown subdivision type: {0}.", subdivType);
+                invalid = true;
+            }
+            if (levels < 0) {
+                LOGGER.severe("The amount of subdivision levels cannot be negative.");
+                invalid = true;
+            }
+        }
+    }
+
+    @Override
+    public void apply(Node node, BlenderContext blenderContext) {
+        if (invalid) {
+            LOGGER.log(Level.WARNING, "Subdivision surface modifier is invalid! Cannot be applied to: {0}", node.getName());
+        } else if (levels > 0) {// no need to do anything if the levels is set to zero
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if (temporalMesh != null) {
+                LOGGER.log(Level.FINE, "Applying subdivision surface modifier to: {0}", temporalMesh);
+                if (subdivType == TYPE_CATMULLCLARK) {
+                    for (int i = 0; i < levels; ++i) {
+                        this.subdivideSimple(temporalMesh);// first do simple subdivision ...
+                        this.subdivideCatmullClark(temporalMesh);// ... and then apply Catmull-Clark algorithm
+                        if (subdivideUVS) {// UV's can be subdivided only for Catmull-Clark subdivision algorithm
+                            this.subdivideUVs(temporalMesh);
+                        }
+                    }
+                } else {
+                    for (int i = 0; i < levels; ++i) {
+                        this.subdivideSimple(temporalMesh);
+                    }
+                }
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
+            }
+        }
+    }
+
+    /**
+     * Catmull-Clark subdivision. It assumes that the mesh was already simple-subdivided.
+     * @param temporalMesh
+     *            the mesh whose vertices will be transformed to form Catmull-Clark subdivision
+     */
+    private void subdivideCatmullClark(TemporalMesh temporalMesh) {
+        Set<Integer> boundaryVertices = new HashSet<Integer>();
+        for (Face face : temporalMesh.getFaces()) {
+            for (Integer index : face.getIndexes()) {
+                if (temporalMesh.isBoundary(index)) {
+                    boundaryVertices.add(index);
+                }
+            }
+        }
+
+        Vector3f[] averageVert = new Vector3f[temporalMesh.getVertices().size()];
+        int[] averageCount = new int[temporalMesh.getVertices().size()];
+
+        for (Face face : temporalMesh.getFaces()) {
+            Vector3f centroid = face.computeCentroid();
+
+            for (Integer index : face.getIndexes()) {
+                if (boundaryVertices.contains(index)) {
+                    Edge edge = this.findEdge(temporalMesh, index, face.getIndexes().getNextIndex(index));
+                    if (temporalMesh.isBoundary(edge)) {
+                        averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid());
+                        averageCount[index] += 1;
+                    }
+                    edge = this.findEdge(temporalMesh, face.getIndexes().getPreviousIndex(index), index);
+                    if (temporalMesh.isBoundary(edge)) {
+                        averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid());
+                        averageCount[index] += 1;
+                    }
+                } else {
+                    averageVert[index] = averageVert[index] == null ? centroid.clone() : averageVert[index].addLocal(centroid);
+                    averageCount[index] += 1;
+                }
+            }
+        }
+        for (Edge edge : temporalMesh.getEdges()) {
+            if (!edge.isInFace()) {
+                Vector3f centroid = temporalMesh.getVertices().get(edge.getFirstIndex()).add(temporalMesh.getVertices().get(edge.getSecondIndex())).divideLocal(2);
+
+                averageVert[edge.getFirstIndex()] = averageVert[edge.getFirstIndex()] == null ? centroid.clone() : averageVert[edge.getFirstIndex()].addLocal(centroid);
+                averageVert[edge.getSecondIndex()] = averageVert[edge.getSecondIndex()] == null ? centroid.clone() : averageVert[edge.getSecondIndex()].addLocal(centroid);
+                averageCount[edge.getSecondIndex()] += 2;
+            }
+        }
+
+        for (int i = 0; i < averageVert.length; ++i) {
+            averageVert[i].divideLocal(averageCount[i]);
+            if (!boundaryVertices.contains(i)) {
+                temporalMesh.getVertices().get(i).addLocal(averageVert[i].subtract(temporalMesh.getVertices().get(i)).multLocal(4 / (float) averageCount[i]));
+            } else {
+                temporalMesh.getVertices().get(i).set(averageVert[i]);
+            }
+        }
+    }
+
+    /**
+     * The method performs a simple subdivision of the mesh.
+     * 
+     * @param temporalMesh
+     *            the mesh to be subdivided
+     */
+    @SuppressWarnings("unchecked")
+    private void subdivideSimple(TemporalMesh temporalMesh) {
+        Map<Edge, Integer> edgePoints = new HashMap<Edge, Integer>();
+        Map<Face, Integer> facePoints = new HashMap<Face, Integer>();
+        List<Face> newFaces = new ArrayList<Face>();
+
+        int originalFacesCount = temporalMesh.getFaces().size();
+
+        List<Map<String, Float>> vertexGroups = temporalMesh.getVertexGroups();
+        // the result vertex array will have verts in the following order [[original_verts], [face_verts], [edge_verts]]
+        List<Vector3f> vertices = temporalMesh.getVertices();
+        List<Vector3f> edgeVertices = new ArrayList<Vector3f>();
+        List<Vector3f> faceVertices = new ArrayList<Vector3f>();
+        // the same goes for normals
+        List<Vector3f> normals = temporalMesh.getNormals();
+        List<Vector3f> edgeNormals = new ArrayList<Vector3f>();
+        List<Vector3f> faceNormals = new ArrayList<Vector3f>();
+
+        List<Face> faces = temporalMesh.getFaces();
+        for (Face face : faces) {
+            Map<String, List<Vector2f>> uvSets = face.getUvSets();
+
+            Vector3f facePoint = face.computeCentroid();
+            Integer facePointIndex = vertices.size() + faceVertices.size();
+            facePoints.put(face, facePointIndex);
+            faceVertices.add(facePoint);
+            faceNormals.add(this.computeFaceNormal(face));
+            Map<String, Vector2f> faceUV = this.computeFaceUVs(face);
+            byte[] faceVertexColor = this.computeFaceVertexColor(face);
+            Map<String, Float> faceVertexGroups = this.computeFaceVertexGroups(face);
+            if (vertexGroups.size() > 0) {
+                vertexGroups.add(faceVertexGroups);
+            }
+
+            for (int i = 0; i < face.getIndexes().size(); ++i) {
+                int vIndex = face.getIndexes().get(i);
+                int vPrevIndex = i == 0 ? face.getIndexes().get(face.getIndexes().size() - 1) : face.getIndexes().get(i - 1);
+                int vNextIndex = i == face.getIndexes().size() - 1 ? face.getIndexes().get(0) : face.getIndexes().get(i + 1);
+
+                Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex);// new Edge(vPrevIndex, vIndex, 0, true, temporalMesh.getVertices());
+                Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex);// new Edge(vIndex, vNextIndex, 0, true, temporalMesh.getVertices());
+                int vPrevEdgeVertIndex = edgePoints.containsKey(prevEdge) ? edgePoints.get(prevEdge) : -1;
+                int vNextEdgeVertIndex = edgePoints.containsKey(nextEdge) ? edgePoints.get(nextEdge) : -1;
+
+                Vector3f v = temporalMesh.getVertices().get(vIndex);
+                if (vPrevEdgeVertIndex < 0) {
+                    vPrevEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size();
+                    edgeVertices.add(vertices.get(vPrevIndex).add(v).divideLocal(2));
+                    edgeNormals.add(normals.get(vPrevIndex).add(normals.get(vIndex)).normalizeLocal());
+                    edgePoints.put(prevEdge, vPrevEdgeVertIndex);
+                    if (vertexGroups.size() > 0) {
+                        vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vPrevIndex), vertexGroups.get(vIndex))));
+                    }
+                }
+                if (vNextEdgeVertIndex < 0) {
+                    vNextEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size();
+                    edgeVertices.add(vertices.get(vNextIndex).add(v).divideLocal(2));
+                    edgeNormals.add(normals.get(vNextIndex).add(normals.get(vIndex)).normalizeLocal());
+                    edgePoints.put(nextEdge, vNextEdgeVertIndex);
+                    if (vertexGroups.size() > 0) {
+                        vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vNextIndex), vertexGroups.get(vIndex))));
+                    }
+                }
+
+                Integer[] indexes = new Integer[] { vIndex, vNextEdgeVertIndex, facePointIndex, vPrevEdgeVertIndex };
+
+                Map<String, List<Vector2f>> newUVSets = null;
+                if (uvSets != null) {
+                    newUVSets = new HashMap<String, List<Vector2f>>(uvSets.size());
+                    for (Entry<String, List<Vector2f>> uvset : uvSets.entrySet()) {
+                        int indexOfvIndex = i;
+                        int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex);
+                        int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex);
+
+                        Vector2f uv1 = uvset.getValue().get(indexOfvIndex);
+                        Vector2f uv2 = uvset.getValue().get(indexOfvNextIndex).add(uv1).divideLocal(2);
+                        Vector2f uv3 = faceUV.get(uvset.getKey());
+                        Vector2f uv4 = uvset.getValue().get(indexOfvPrevIndex).add(uv1).divideLocal(2);
+                        List<Vector2f> uvList = Arrays.asList(uv1, uv2, uv3, uv4);
+                        newUVSets.put(uvset.getKey(), new ArrayList<Vector2f>(uvList));
+                    }
+                }
+
+                List<byte[]> vertexColors = null;
+                if (face.getVertexColors() != null) {
+
+                    int indexOfvIndex = i;
+                    int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex);
+                    int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex);
+
+                    byte[] vCol1 = face.getVertexColors().get(indexOfvIndex);
+                    byte[] vCol2 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvNextIndex), vCol1);
+                    byte[] vCol3 = faceVertexColor;
+                    byte[] vCol4 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvPrevIndex), vCol1);
+                    vertexColors = new ArrayList<byte[]>(Arrays.asList(vCol1, vCol2, vCol3, vCol4));
+                }
+
+                newFaces.add(new Face(indexes, face.isSmooth(), face.getMaterialNumber(), newUVSets, vertexColors, temporalMesh));
+            }
+        }
+
+        vertices.addAll(faceVertices);
+        vertices.addAll(edgeVertices);
+        normals.addAll(faceNormals);
+        normals.addAll(edgeNormals);
+
+        List<Edge> newEdges = new ArrayList<Edge>(temporalMesh.getEdges().size() * 2);
+        for (Edge edge : temporalMesh.getEdges()) {
+            if (!edge.isInFace()) {
+                int newVertexIndex = vertices.size();
+                vertices.add(vertices.get(edge.getFirstIndex()).add(vertices.get(edge.getSecondIndex())).divideLocal(2));
+                normals.add(normals.get(edge.getFirstIndex()).add(normals.get(edge.getSecondIndex())).normalizeLocal());
+
+                newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, 0, false, vertices));
+                newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), 0, false, vertices));
+            } else {
+                Integer edgePoint = edgePoints.get(edge);
+                newEdges.add(new Edge(edge.getFirstIndex(), edgePoint, edge.getCrease(), true, vertices));
+                newEdges.add(new Edge(edgePoint, edge.getSecondIndex(), edge.getCrease(), true, vertices));
+                // adding edges between face points and edge points
+                List<Face> facesContainingTheEdge = temporalMesh.getAdjacentFaces(edge);
+                for (Face f : facesContainingTheEdge) {
+                    newEdges.add(new Edge(facePoints.get(f), edgePoint, 0, true, vertices));
+                }
+            }
+        }
+
+        temporalMesh.getFaces().clear();
+        temporalMesh.getFaces().addAll(newFaces);
+        temporalMesh.getEdges().clear();
+        temporalMesh.getEdges().addAll(newEdges);
+
+        temporalMesh.rebuildIndexesMappings();
+    }
+
+    /**
+     * The method subdivides mesh's UV coordinates. It actually performs only Catmull-Clark modifications because if any UV's are present then they are
+     * automatically subdivided by the simple algorithm.
+     * @param temporalMesh
+     *            the mesh whose UV coordinates will be applied Catmull-Clark algorithm
+     */
+    private void subdivideUVs(TemporalMesh temporalMesh) {
+        List<Face> faces = temporalMesh.getFaces();
+        Map<String, UvCoordsSubdivideTemporalMesh> subdividedUVS = new HashMap<String, UvCoordsSubdivideTemporalMesh>();
+        for (Face face : faces) {
+            if (face.getUvSets() != null) {
+                for (Entry<String, List<Vector2f>> uvset : face.getUvSets().entrySet()) {
+                    UvCoordsSubdivideTemporalMesh uvCoordsSubdivideTemporalMesh = subdividedUVS.get(uvset.getKey());
+                    if (uvCoordsSubdivideTemporalMesh == null) {
+                        try {
+                            uvCoordsSubdivideTemporalMesh = new UvCoordsSubdivideTemporalMesh(temporalMesh.getBlenderContext());
+                        } catch (BlenderFileException e) {
+                            assert false : "Something went really wrong! The UvCoordsSubdivideTemporalMesh class should NOT throw exceptions here!";
+                        }
+                        subdividedUVS.put(uvset.getKey(), uvCoordsSubdivideTemporalMesh);
+                    }
+                    uvCoordsSubdivideTemporalMesh.addFace(uvset.getValue());
+                }
+            }
+        }
+
+        for (Entry<String, UvCoordsSubdivideTemporalMesh> entry : subdividedUVS.entrySet()) {
+            entry.getValue().rebuildIndexesMappings();
+            this.subdivideCatmullClark(entry.getValue());
+
+            for (int i = 0; i < faces.size(); ++i) {
+                List<Vector2f> uvs = faces.get(i).getUvSets().get(entry.getKey());
+                if (uvs != null) {
+                    uvs.clear();
+                    uvs.addAll(entry.getValue().faceToUVs(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * The method computes the face's normal vector.
+     * @param face
+     *            the face of the mesh
+     * @return face's normal vector
+     */
+    private Vector3f computeFaceNormal(Face face) {
+        Vector3f result = new Vector3f();
+        for (Integer index : face.getIndexes()) {
+            result.addLocal(face.getTemporalMesh().getNormals().get(index));
+        }
+        result.divideLocal(face.getIndexes().size());
+        return result;
+    }
+
+    /**
+     * The method computes the UV coordinates of the face middle point.
+     * @param face
+     *            the face of the mesh
+     * @return a map whose key is the name of the UV set and value is the UV coordinate of the face's middle point
+     */
+    private Map<String, Vector2f> computeFaceUVs(Face face) {
+        Map<String, Vector2f> result = null;
+
+        Map<String, List<Vector2f>> uvSets = face.getUvSets();
+        if (uvSets != null && uvSets.size() > 0) {
+            result = new HashMap<String, Vector2f>(uvSets.size());
+
+            for (Entry<String, List<Vector2f>> entry : uvSets.entrySet()) {
+                Vector2f faceUV = new Vector2f();
+                for (Vector2f uv : entry.getValue()) {
+                    faceUV.addLocal(uv);
+                }
+                faceUV.divideLocal(entry.getValue().size());
+                result.put(entry.getKey(), faceUV);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * The mesh interpolates the values of vertex groups weights for new vertices.
+     * @param vertexGroups
+     *            the vertex groups
+     * @return interpolated weights of given vertex groups' weights
+     */
+    private Map<String, Float> interpolateVertexGroups(List<Map<String, Float>> vertexGroups) {
+        Map<String, Float> weightSums = new HashMap<String, Float>();
+        if (vertexGroups.size() > 0) {
+            for (Map<String, Float> vGroup : vertexGroups) {
+                for (Entry<String, Float> entry : vGroup.entrySet()) {
+                    if (weightSums.containsKey(entry.getKey())) {
+                        weightSums.put(entry.getKey(), weightSums.get(entry.getKey()) + entry.getValue());
+                    } else {
+                        weightSums.put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+
+        Map<String, Float> result = new HashMap<String, Float>(weightSums.size());
+        for (Entry<String, Float> entry : weightSums.entrySet()) {
+            result.put(entry.getKey(), entry.getValue() / vertexGroups.size());
+        }
+
+        return result;
+    }
+
+    /**
+     * The method computes the vertex groups values for face's middle point.
+     * @param face
+     *            the face of the mesh
+     * @return face's middle point interpolated vertex groups' weights
+     */
+    private Map<String, Float> computeFaceVertexGroups(Face face) {
+        if (face.getTemporalMesh().getVertexGroups().size() > 0) {
+            List<Map<String, Float>> vertexGroups = new ArrayList<Map<String, Float>>(face.getIndexes().size());
+            for (Integer index : face.getIndexes()) {
+                vertexGroups.add(face.getTemporalMesh().getVertexGroups().get(index));
+            }
+            return this.interpolateVertexGroups(vertexGroups);
+        }
+        return new HashMap<String, Float>();
+    }
+
+    /**
+     * The method computes face's middle point vertex color.
+     * @param face
+     *            the face of the mesh
+     * @return face's middle point vertex color
+     */
+    private byte[] computeFaceVertexColor(Face face) {
+        if (face.getVertexColors() != null) {
+            return this.interpolateVertexColors(face.getVertexColors().toArray(new byte[face.getVertexColors().size()][]));
+        }
+        return null;
+    }
+
+    /**
+     * The method computes the average value for the given vertex colors.
+     * @param colors
+     *            the vertex colors
+     * @return vertex colors' average value
+     */
+    private byte[] interpolateVertexColors(byte[]... colors) {
+        TexturePixel pixel = new TexturePixel();
+        TexturePixel temp = new TexturePixel();
+        for (int i = 0; i < colors.length; ++i) {
+            temp.fromARGB8(colors[i][3], colors[i][0], colors[i][1], colors[i][2]);
+            pixel.add(temp);
+        }
+        pixel.divide(colors.length);
+        byte[] result = new byte[4];
+        pixel.toRGBA8(result);
+        return result;
+    }
+
+    /**
+     * The method finds an edge between the given vertices in the mesh.
+     * @param temporalMesh
+     *            the mesh
+     * @param index1
+     *            first index of the edge
+     * @param index2
+     *            second index of the edge
+     * @return found edge or null
+     */
+    private Edge findEdge(TemporalMesh temporalMesh, int index1, int index2) {
+        for (Edge edge : temporalMesh.getEdges()) {
+            if (edge.getFirstIndex() == index1 && edge.getSecondIndex() == index2 || edge.getFirstIndex() == index2 && edge.getSecondIndex() == index1) {
+                return edge;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This is a helper class for UV coordinates subdivision. UV's form a mesh that is being applied the same algorithms as a regular mesh.
+     * This way one code handles two issues. After applying Catmull-Clark algorithm the UV-mesh is transformed back into UV coordinates.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class UvCoordsSubdivideTemporalMesh extends TemporalMesh {
+        private static final Vector3f NORMAL = new Vector3f(0, 0, 1);
+
+        public UvCoordsSubdivideTemporalMesh(BlenderContext blenderContext) throws BlenderFileException {
+            super(null, blenderContext, false);
+        }
+
+        /**
+         * Adds a UV-face to the mesh.
+         * @param uvs
+         *            the UV coordinates
+         */
+        public void addFace(List<Vector2f> uvs) {
+            Integer[] indexes = new Integer[uvs.size()];
+            int i = 0;
+
+            for (Vector2f uv : uvs) {
+                Vector3f v = new Vector3f(uv.x, uv.y, 0);
+                int index = vertices.indexOf(v);
+                if (index >= 0) {
+                    indexes[i++] = index;
+                } else {
+                    indexes[i++] = vertices.size();
+                    vertices.add(v);
+                    normals.add(NORMAL);
+                }
+            }
+            faces.add(new Face(indexes, false, 0, null, null, this));
+            for (i = 1; i < indexes.length; ++i) {
+                edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, vertices));
+            }
+            edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, vertices));
+        }
+
+        /**
+         * Converts the mesh back into UV coordinates for the given face.
+         * @param faceIndex
+         *            the index of the face
+         * @return UV coordinates
+         */
+        public List<Vector2f> faceToUVs(int faceIndex) {
+            Face face = faces.get(faceIndex);
+            List<Vector2f> result = new ArrayList<Vector2f>(face.getIndexes().size());
+            for (Integer index : face.getIndexes()) {
+                Vector3f v = vertices.get(index);
+                result.add(new Vector2f(v.x, v.y));
+            }
+            return result;
+        }
+    }
+}