Procházet zdrojové kódy

Feature: added support for mask modifier.

jmekaelthas před 11 roky
rodič
revize
e18ffccf8a

+ 1 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java

@@ -23,6 +23,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
  */
 public class BoneContext {
     // the flags of the bone
+    public static final int      SELECTED                            = 0x0001;
     public static final int      CONNECTED_TO_PARENT                 = 0x0010;
     public static final int      DEFORM                              = 0x1000;
 

+ 11 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java

@@ -11,6 +11,7 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
 
 /**
  * A class that represents a single edge between two vertices.
@@ -82,10 +83,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
      * Shifts indexes by a given amount.
      * @param shift
      *            how much the indexes should be shifted
+     * @param predicate
+     *            the predicate that verifies which indexes should be shifted; if null then all will be shifted
      */
-    public void shiftIndexes(int shift) {
-        index1 += shift;
-        index2 += shift;
+    public void shiftIndexes(int shift, IndexPredicate predicate) {
+        if (predicate == null) {
+            index1 += shift;
+            index2 += shift;
+        } else {
+            index1 += predicate.execute(index1) ? shift : 0;
+            index2 += predicate.execute(index2) ? shift : 0;
+        }
     }
 
     /**

+ 12 - 24
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java

@@ -116,12 +116,19 @@ import com.jme3.scene.plugins.blender.file.Structure;
         }
         return indexes.get(indexPosition);
     }
+    
+    /**
+     * @return the original indexes of the face
+     */
+    public IndexesLoop getIndexes() {
+        return indexes;
+    }
 
     /**
-     * @return all indexes
+     * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
      */
     @SuppressWarnings("unchecked")
-    public List<List<Integer>> getIndexes() {
+    public List<List<Integer>> getCurrentIndexes() {
         if(triangulatedFaces == null) {
             return Arrays.asList(indexes.getAll());
         }
@@ -178,25 +185,6 @@ import com.jme3.scene.plugins.blender.file.Structure;
         return detachedFaces;
     }
 
-    /**
-     * The method returns the position of the given index in the indexes loop.
-     * @param index
-     *            the index whose position will be queried
-     * @return position of the given index or -1 if such index is not in the index loop
-     */
-    public int indexOf(Integer index) {
-        return indexes.indexOf(index);
-    }
-
-    /**
-     * The method shifts all indexes by a given value.
-     * @param shift
-     *            the value to shift all indexes
-     */
-    public void shiftIndexes(int shift) {
-        indexes.shiftIndexes(shift);
-    }
-
     /**
      * Sets the temporal mesh for the face. The given mesh cannot be null.
      * @param temporalMesh
@@ -273,7 +261,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
                 Face face = facesToTriangulate.remove(0);
                 int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
                 while (face.vertexCount() > 0) {
-                    indexes[0] = face.getIndex(0);  
+                    indexes[0] = face.getIndex(0);
                     indexes[1] = face.findClosestVertex(indexes[0], -1);
                     indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
                     
@@ -394,7 +382,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
             // with the one creaded by vertices: [index1 - 1, index1, index2]
             // if the latter is greater than it means that the edge is outside the face
             // IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face)
-            int indexOfIndex1 = this.indexOf(index1);
+            int indexOfIndex1 = indexes.indexOf(index1);
             int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1);
             int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1);
 
@@ -525,6 +513,6 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     @Override
     public int compare(Integer index1, Integer index2) {
-        return this.indexOf(index1) - this.indexOf(index2);
+        return indexes.indexOf(index1) - indexes.indexOf(index2);
     }
 }

+ 20 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java

@@ -18,10 +18,17 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
  * @author Marcin Roguski (Kaelthas)
  */
 public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
+    public static final IndexPredicate  INDEX_PREDICATE_USE_ALL = new IndexPredicate() {
+                                                                    @Override
+                                                                    public boolean execute(Integer index) {
+                                                                        return true;
+                                                                    }
+                                                                };
+
     /** The indexes. */
     private List<Integer>               nodes;
     /** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */
-    private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
+    private Map<Integer, List<Integer>> edges                   = new HashMap<Integer, List<Integer>>();
 
     /**
      * The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge.
@@ -124,18 +131,23 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
      * The method shifts all indexes by a given value.
      * @param shift
      *            the value to shift all indexes
+     * @param predicate
+     *            the predicate that verifies which indexes should be shifted; if null then all will be shifted
      */
-    public void shiftIndexes(int shift) {
+    public void shiftIndexes(int shift, IndexPredicate predicate) {
+        if (predicate == null) {
+            predicate = INDEX_PREDICATE_USE_ALL;
+        }
         List<Integer> nodes = new ArrayList<Integer>(this.nodes.size());
         for (Integer node : this.nodes) {
-            nodes.add(node + shift);
+            nodes.add(node + (predicate.execute(node) ? shift : 0));
         }
 
         Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
         for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) {
             List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size());
             for (Integer neighbour : entry.getValue()) {
-                neighbours.add(neighbour + shift);
+                neighbours.add(neighbour + (predicate.execute(neighbour) ? shift : 0));
             }
             edges.put(entry.getKey() + shift, neighbours);
         }
@@ -264,4 +276,8 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
     public Iterator<Integer> iterator() {
         return nodes.iterator();
     }
+
+    public static interface IndexPredicate {
+        boolean execute(Integer index);
+    }
 }

+ 7 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java

@@ -10,6 +10,7 @@ import java.util.logging.Logger;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
 
 /**
  * A class that represents a single point on the scene that is not a part of an edge.
@@ -47,9 +48,13 @@ import com.jme3.scene.plugins.blender.file.Structure;
      * The method shifts the index by a given value.
      * @param shift
      *            the value to shift the index
+     * @param predicate
+     *            the predicate that verifies which indexes should be shifted; if null then all will be shifted
      */
-    public void shiftIndexes(int shift) {
-        index += shift;
+    public void shiftIndexes(int shift, IndexPredicate predicate) {
+        if (predicate == null || predicate.execute(index)) {
+            index += shift;
+        }
     }
 
     /**

+ 102 - 10
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java

@@ -4,12 +4,15 @@ import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 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;
 
@@ -29,6 +32,7 @@ import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
 import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData;
 import com.jme3.scene.plugins.blender.modifiers.Modifier;
 import com.jme3.scene.plugins.blender.objects.Properties;
@@ -222,14 +226,14 @@ public class TemporalMesh extends Geometry {
         int shift = vertices.size();
         if (shift > 0) {
             for (Face face : mesh.faces) {
-                face.shiftIndexes(shift);
+                face.getIndexes().shiftIndexes(shift, null);
                 face.setTemporalMesh(this);
             }
             for (Edge edge : mesh.edges) {
-                edge.shiftIndexes(shift);
+                edge.shiftIndexes(shift, null);
             }
             for (Point point : mesh.points) {
-                point.shiftIndexes(shift);
+                point.shiftIndexes(shift, null);
             }
         }
 
@@ -320,6 +324,94 @@ public class TemporalMesh extends Geometry {
         return normals.get(i);
     }
 
+    /**
+     * Returns the vertex groups at the given vertex index.
+     * @param i
+     *            the vertex groups for vertex with a given index
+     * @return the vertex groups at the given vertex index
+     */
+    public Map<String, Float> getVertexGroups(int i) {
+        return vertexGroups.size() > i ? vertexGroups.get(i) : null;
+    }
+
+    /**
+     * @return a collection of vertex group names for this mesh
+     */
+    public Collection<String> getVertexGroupNames() {
+        Set<String> result = new HashSet<String>();
+        for (Map<String, Float> groups : vertexGroups) {
+            result.addAll(groups.keySet());
+        }
+        return result;
+    }
+
+    /**
+     * Removes all vertices from the mesh.
+     */
+    public void clear() {
+        vertices.clear();
+        normals.clear();
+        vertexGroups.clear();
+        verticesColors.clear();
+        faces.clear();
+        edges.clear();
+        points.clear();
+    }
+
+    /**
+     * Every face, edge and point that contains
+     * the vertex will be removed.
+     * @param index
+     *            the index of a vertex to be removed
+     * @throws IndexOutOfBoundsException
+     *             thrown when given index is negative or beyond the count of vertices
+     */
+    public void removeVertexAt(final int index) {
+        if (index < 0 || index >= vertices.size()) {
+            throw new IndexOutOfBoundsException("The given index is out of bounds: " + index);
+        }
+
+        vertices.remove(index);
+        normals.remove(index);
+        if(vertexGroups.size() > 0) {
+            vertexGroups.remove(index);
+        }
+        if(verticesColors.size() > 0) {
+            verticesColors.remove(index);
+        }
+
+        IndexPredicate shiftPredicate = new IndexPredicate() {
+            @Override
+            public boolean execute(Integer i) {
+                return i > index;
+            }
+        };
+        for (int i = faces.size() - 1; i >= 0; --i) {
+            Face face = faces.get(i);
+            if (face.getIndexes().indexOf(index) >= 0) {
+                faces.remove(i);
+            } else {
+                face.getIndexes().shiftIndexes(-1, shiftPredicate);
+            }
+        }
+        for (int i = edges.size() - 1; i >= 0; --i) {
+            Edge edge = edges.get(i);
+            if (edge.getFirstIndex() == index || edge.getSecondIndex() == index) {
+                edges.remove(i);
+            } else {
+                edge.shiftIndexes(-1, shiftPredicate);
+            }
+        }
+        for (int i = points.size() - 1; i >= 0; --i) {
+            Point point = points.get(i);
+            if (point.getIndex() == index) {
+                points.remove(i);
+            } else {
+                point.shiftIndexes(-1, shiftPredicate);
+            }
+        }
+    }
+
     /**
      * Flips the order of the mesh's indexes.
      */
@@ -397,10 +489,10 @@ public class TemporalMesh extends Geometry {
                 faceMeshes.put(face.getMaterialNumber(), meshBuffers);
             }
 
-            List<List<Integer>> triangulatedIndexes = face.getIndexes();
+            List<List<Integer>> triangulatedIndexes = face.getCurrentIndexes();
             List<byte[]> vertexColors = face.getVertexColors();
-            
-            for(List<Integer> indexes : triangulatedIndexes) {
+
+            for (List<Integer> indexes : triangulatedIndexes) {
                 assert indexes.size() == 3 : "The mesh has not been properly triangulated!";
                 boneBuffers.clear();
                 for (int i = 0; i < 3; ++i) {
@@ -408,7 +500,7 @@ public class TemporalMesh extends Geometry {
                     tempVerts[i] = vertices.get(vertIndex);
                     tempNormals[i] = normals.get(vertIndex);
                     tempVertColors[i] = vertexColors != null ? vertexColors.get(i) : null;
-                    
+
                     if (boneIndexes.size() > 0) {
                         Map<Float, Integer> boneBuffersForVertex = new HashMap<Float, Integer>();
                         Map<String, Float> vertexGroupsForVertex = vertexGroups.get(vertIndex);
@@ -420,7 +512,7 @@ public class TemporalMesh extends Geometry {
                         boneBuffers.add(boneBuffersForVertex);
                     }
                 }
-    
+
                 meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, face.getUvSets(), tempVertColors, boneBuffers);
             }
         }
@@ -495,7 +587,7 @@ public class TemporalMesh extends Geometry {
     protected void prepareLinesGeometry(List<Geometry> result, MeshHelper meshHelper) {
         if (edges.size() > 0) {
             LOGGER.fine("Preparing lines geometries.");
-            
+
             List<List<Integer>> separateEdges = new ArrayList<List<Integer>>();
             List<Edge> edges = new ArrayList<Edge>(this.edges);
             while (edges.size() > 0) {
@@ -568,7 +660,7 @@ public class TemporalMesh extends Geometry {
     protected void preparePointsGeometry(List<Geometry> result, MeshHelper meshHelper) {
         if (points.size() > 0) {
             LOGGER.fine("Preparing point geometries.");
-            
+
             MeshBuffers pointBuffers = new MeshBuffers(0);
             for (Point point : points) {
                 pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex()));

+ 138 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MaskModifier.java

@@ -0,0 +1,138 @@
+package com.jme3.scene.plugins.blender.modifiers;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.animations.BoneContext;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
+
+/**
+ * This modifier allows to use mask modifier on the object.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class MaskModifier extends Modifier {
+    private static final Logger LOGGER             = Logger.getLogger(MaskModifier.class.getName());
+
+    private static final int    FLAG_INVERT_MASK   = 0x01;
+
+    private static final int    MODE_VERTEX_GROUP  = 0;
+    private static final int    MODE_ARMATURE      = 1;
+
+    private Pointer             pArmatureObject;
+    private String              vertexGroupName;
+    private boolean             invertMask;
+
+    public MaskModifier(Structure modifierStructure, BlenderContext blenderContext) {
+        if (this.validate(modifierStructure, blenderContext)) {
+            int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue();
+            invertMask = (flag & FLAG_INVERT_MASK) != 0;
+            
+            int mode = ((Number) modifierStructure.getFieldValue("mode")).intValue();
+            if (mode == MODE_VERTEX_GROUP) {
+                vertexGroupName = modifierStructure.getFieldValue("vgroup").toString();
+                if (vertexGroupName != null && vertexGroupName.length() == 0) {
+                    vertexGroupName = null;
+                }
+            } else if (mode == MODE_ARMATURE) {
+                pArmatureObject = (Pointer) modifierStructure.getFieldValue("ob_arm");
+            } else {
+                LOGGER.log(Level.SEVERE, "Unknown mode type: {0}. Cannot apply modifier: {1}.", new Object[] { mode, modifierStructure.getName() });
+                invalid = true;
+            }
+        }
+    }
+
+    @Override
+    public void apply(Node node, BlenderContext blenderContext) {
+        if (invalid) {
+            LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
+        } else {
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if (temporalMesh != null) {
+                List<String> vertexGroupsToRemove = new ArrayList<String>();
+                if (vertexGroupName != null) {
+                    vertexGroupsToRemove.add(vertexGroupName);
+                } else if (pArmatureObject != null && pArmatureObject.isNotNull()) {
+                    try {
+                        Structure armatureObject = pArmatureObject.fetchData().get(0);
+
+                        Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
+                        List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
+                        vertexGroupsToRemove.addAll(this.readBoneNames(bonebase));
+                    } catch (BlenderFileException e) {
+                        LOGGER.log(Level.SEVERE, "Cannot load armature object for the mask modifier. Cause: {0}", e.getLocalizedMessage());
+                        LOGGER.log(Level.SEVERE, "Mask modifier will NOT be applied to node named: {0}", node.getName());
+                    }
+                } else {
+                    // if the mesh has no vertex groups then remove all verts
+                    // if the mesh has at least one vertex group - then do nothing
+                    // I have no idea why we should do that, but blender works this way
+                    Collection<String> vertexGroupNames = temporalMesh.getVertexGroupNames();
+                    if (vertexGroupNames.size() == 0 && !invertMask || vertexGroupNames.size() > 0 && invertMask) {
+                        temporalMesh.clear();
+                    }
+                }
+
+                if (vertexGroupsToRemove.size() > 0) {
+                    List<Integer> vertsToBeRemoved = new ArrayList<Integer>();
+                    for (int i = 0; i < temporalMesh.getVertexCount(); ++i) {
+                        Map<String, Float> vertexGroups = temporalMesh.getVertexGroups(i);
+                        boolean hasVertexGroup = false;
+                        if(vertexGroups != null) {
+                            for (String groupName : vertexGroupsToRemove) {
+                                Float weight = vertexGroups.get(groupName);
+                                if (weight != null && weight > 0) {
+                                    hasVertexGroup = true;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (!hasVertexGroup && !invertMask || hasVertexGroup && invertMask) {
+                            vertsToBeRemoved.add(i);
+                        }
+                    }
+
+                    Collections.reverse(vertsToBeRemoved);
+                    for (Integer vertexIndex : vertsToBeRemoved) {
+                        temporalMesh.removeVertexAt(vertexIndex);
+                    }
+                }
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
+            }
+        }
+    }
+
+    /**
+     * Reads the names of the bones from the given bone base.
+     * @param boneBase
+     *            the list of bone base structures
+     * @return a list of bones' names
+     * @throws BlenderFileException
+     *             is thrown if problems with reading the child bones' bases occur
+     */
+    private List<String> readBoneNames(List<Structure> boneBase) throws BlenderFileException {
+        List<String> result = new ArrayList<String>();
+        for (Structure boneStructure : boneBase) {
+            int flag = ((Number) boneStructure.getFieldValue("flag")).intValue();
+            if ((flag & BoneContext.SELECTED) != 0) {
+                result.add(boneStructure.getFieldValue("name").toString());
+            }
+            List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
+            result.addAll(this.readBoneNames(childbase));
+        }
+        return result;
+    }
+}