Feature: added support for mask modifier.

experimental
jmekaelthas 10 years ago
parent b39772c401
commit e18ffccf8a
  1. 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  2. 14
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  3. 36
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  4. 24
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java
  5. 9
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java
  6. 112
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java
  7. 138
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MaskModifier.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,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;
}
}
/**

@ -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);
}
}

@ -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);
}
}

@ -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;
}
}
/**

@ -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()));

@ -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;
}
}
Loading…
Cancel
Save