Feature: added support for mask modifier.

experimental
jmekaelthas 11 years ago
parent b39772c401
commit e18ffccf8a
  1. 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  2. 10
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  3. 34
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  4. 22
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java
  5. 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java
  6. 100
      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) {
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;
}
}
/**

@ -118,10 +118,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
}
/**
* @return all indexes
* @return the original indexes of the face
*/
public IndexesLoop getIndexes() {
return 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
@ -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,6 +18,13 @@ 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. */
@ -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,10 +48,14 @@ 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) {
public void shiftIndexes(int shift, IndexPredicate predicate) {
if (predicate == null || predicate.execute(index)) {
index += shift;
}
}
/**
* Loads all points of the mesh that do not belong to any edge.

@ -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,7 +489,7 @@ 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) {

@ -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