Feature: added support for bone envelopes.
This commit is contained in:
parent
4a69eee64f
commit
a2855c1cf0
@ -560,7 +560,7 @@ public class BlenderContext {
|
|||||||
*/
|
*/
|
||||||
public BoneContext getBoneContext(Bone bone) {
|
public BoneContext getBoneContext(Bone bone) {
|
||||||
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
||||||
if (entry.getValue().getBone().equals(bone)) {
|
if (entry.getValue().getBone().getName().equals(bone.getName())) {
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
|||||||
*/
|
*/
|
||||||
public class BoneContext {
|
public class BoneContext {
|
||||||
// the flags of the bone
|
// the flags of the bone
|
||||||
public static final int CONNECTED_TO_PARENT = 0x10;
|
public static final int CONNECTED_TO_PARENT = 0x0010;
|
||||||
|
public static final int DEFORM = 0x1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
|
* The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
|
||||||
@ -54,6 +55,8 @@ public class BoneContext {
|
|||||||
private Bone bone;
|
private Bone bone;
|
||||||
/** The length of the bone. */
|
/** The length of the bone. */
|
||||||
private float length;
|
private float length;
|
||||||
|
/** The bone's deform envelope. */
|
||||||
|
private BoneEnvelope boneEnvelope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Creates the basic set of bone's data.
|
* Constructor. Creates the basic set of bone's data.
|
||||||
@ -99,7 +102,7 @@ public class BoneContext {
|
|||||||
|
|
||||||
// first get the bone matrix in its armature space
|
// first get the bone matrix in its armature space
|
||||||
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
|
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
|
||||||
if(blenderContext.getBlenderKey().isFixUpAxis()) {
|
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||||
// then make sure it is rotated in a proper way to fit the jme bone transformation conventions
|
// then make sure it is rotated in a proper way to fit the jme bone transformation conventions
|
||||||
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
||||||
}
|
}
|
||||||
@ -111,6 +114,11 @@ public class BoneContext {
|
|||||||
// and now compute the final bone matrix in world space
|
// and now compute the final bone matrix in world space
|
||||||
globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix);
|
globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix);
|
||||||
|
|
||||||
|
// load the bone deformation envelope if necessary
|
||||||
|
if ((flag & DEFORM) == 0) {// if the flag is NOT set then the DEFORM is in use
|
||||||
|
boneEnvelope = new BoneEnvelope(boneStructure, armatureWorldMatrix, blenderContext.getBlenderKey().isFixUpAxis());
|
||||||
|
}
|
||||||
|
|
||||||
// create the children
|
// create the children
|
||||||
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
|
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
|
||||||
for (Structure child : childbase) {
|
for (Structure child : childbase) {
|
||||||
@ -217,6 +225,13 @@ public class BoneContext {
|
|||||||
return boneMatrixInModelSpace;
|
return boneMatrixInModelSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the vertex assigning envelope of the bone
|
||||||
|
*/
|
||||||
|
public BoneEnvelope getBoneEnvelope() {
|
||||||
|
return boneEnvelope;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if the bone is of specified property defined by its flag.
|
* Tells if the bone is of specified property defined by its flag.
|
||||||
* @param flagMask
|
* @param flagMask
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
package com.jme3.scene.plugins.blender.animations;
|
||||||
|
|
||||||
|
import com.jme3.math.Matrix4f;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||||
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of bone envelope. Used when assigning bones to the mesh by envelopes.
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski
|
||||||
|
*/
|
||||||
|
public class BoneEnvelope {
|
||||||
|
/** A defined distance that will be included in the envelope space. */
|
||||||
|
private float distance;
|
||||||
|
/** The bone's weight. */
|
||||||
|
private float weight;
|
||||||
|
/** The radius of the bone's head. */
|
||||||
|
private float boneHeadRadius;
|
||||||
|
/** The radius of the bone's tail. */
|
||||||
|
private float boneTailRadius;
|
||||||
|
/** Head position in rest pose in world space. */
|
||||||
|
private Vector3f head;
|
||||||
|
/** Tail position in rest pose in world space. */
|
||||||
|
private Vector3f tail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor of bone envelope. It reads all the needed data. Take notice that the positions of head and tail
|
||||||
|
* are computed in the world space and that the points' positions given for computations should be in world space as well.
|
||||||
|
*
|
||||||
|
* @param boneStructure
|
||||||
|
* the blender bone structure
|
||||||
|
* @param armatureWorldMatrix
|
||||||
|
* the world matrix of the armature object
|
||||||
|
* @param fixUpAxis
|
||||||
|
* a variable that tells if we use the Y-is up axis orientation
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public BoneEnvelope(Structure boneStructure, Matrix4f armatureWorldMatrix, boolean fixUpAxis) {
|
||||||
|
distance = ((Number) boneStructure.getFieldValue("dist")).floatValue();
|
||||||
|
weight = ((Number) boneStructure.getFieldValue("weight")).floatValue();
|
||||||
|
boneHeadRadius = ((Number) boneStructure.getFieldValue("rad_head")).floatValue();
|
||||||
|
boneTailRadius = ((Number) boneStructure.getFieldValue("rad_tail")).floatValue();
|
||||||
|
|
||||||
|
DynamicArray<Number> headArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_head");
|
||||||
|
head = new Vector3f(headArray.get(0).floatValue(), headArray.get(1).floatValue(), headArray.get(2).floatValue());
|
||||||
|
if (fixUpAxis) {
|
||||||
|
float z = head.z;
|
||||||
|
head.z = -head.y;
|
||||||
|
head.y = z;
|
||||||
|
}
|
||||||
|
armatureWorldMatrix.mult(head, head);// move the head point to global space
|
||||||
|
|
||||||
|
DynamicArray<Number> tailArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_tail");
|
||||||
|
tail = new Vector3f(tailArray.get(0).floatValue(), tailArray.get(1).floatValue(), tailArray.get(2).floatValue());
|
||||||
|
if (fixUpAxis) {
|
||||||
|
float z = tail.z;
|
||||||
|
tail.z = -tail.y;
|
||||||
|
tail.y = z;
|
||||||
|
}
|
||||||
|
armatureWorldMatrix.mult(tail, tail);// move the tail point to global space
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method verifies if the given point is inside the envelope.
|
||||||
|
* @param point
|
||||||
|
* the point in 3D space (MUST be in a world coordinate space)
|
||||||
|
* @return <b>true</b> if the point is inside the envelope and <b>false</b> otherwise
|
||||||
|
*/
|
||||||
|
public boolean isInEnvelope(Vector3f point) {
|
||||||
|
Vector3f v = tail.subtract(head);
|
||||||
|
float boneLength = v.length();
|
||||||
|
v.normalizeLocal();
|
||||||
|
|
||||||
|
// computing a plane that contains 'point' and v is its normal vector
|
||||||
|
// the plane's equation is: Ax + By + Cz + D = 0, where v = [A, B, C]
|
||||||
|
float D = -v.dot(point);
|
||||||
|
|
||||||
|
// computing a point where a line that contains head and tail crosses the plane
|
||||||
|
float temp = -(v.dot(head) + D) / v.dot(v);
|
||||||
|
Vector3f p = head.add(v.x * temp, v.y * temp, v.z * temp);
|
||||||
|
|
||||||
|
// determining if the point p is on the same or other side of head than the tail point
|
||||||
|
Vector3f headToPointOnLineVector = p.subtract(head);
|
||||||
|
float headToPointLength = headToPointOnLineVector.length();
|
||||||
|
float cosinus = headToPointOnLineVector.dot(v) / headToPointLength;// the length of v is already = 1; cosinus should be either 1, 0 or -1
|
||||||
|
if (cosinus < 0 && headToPointLength > boneHeadRadius || headToPointLength > boneLength + boneTailRadius) {
|
||||||
|
return false;// the point is outside the anvelope
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check if the point is inside and envelope
|
||||||
|
float pointDistanceFromLine = point.subtract(p).length(), maximumDistance = 0;
|
||||||
|
if (cosinus < 0) {
|
||||||
|
// checking if the distance from p to point is inside the half sphere defined by head envelope
|
||||||
|
// compute the distance from the line to the half sphere border
|
||||||
|
maximumDistance = boneHeadRadius;
|
||||||
|
} else if (headToPointLength < boneLength) {
|
||||||
|
// compute the maximum available distance
|
||||||
|
if (boneTailRadius > boneHeadRadius) {
|
||||||
|
// compute the distance from head to p
|
||||||
|
float headToPDistance = p.subtract(head).length();
|
||||||
|
// from tangens function we have
|
||||||
|
float x = headToPDistance * ((boneTailRadius - boneHeadRadius) / boneLength);
|
||||||
|
maximumDistance = x + boneHeadRadius;
|
||||||
|
} else if (boneTailRadius < boneHeadRadius) {
|
||||||
|
// compute the distance from head to p
|
||||||
|
float tailToPDistance = p.subtract(tail).length();
|
||||||
|
// from tangens function we have
|
||||||
|
float x = tailToPDistance * ((boneHeadRadius - boneTailRadius) / boneLength);
|
||||||
|
maximumDistance = x + boneTailRadius;
|
||||||
|
} else {
|
||||||
|
maximumDistance = boneTailRadius;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// checking if the distance from p to point is inside the half sphere defined by tail envelope
|
||||||
|
maximumDistance = boneTailRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointDistanceFromLine <= maximumDistance + distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the weight of the bone
|
||||||
|
*/
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BoneEnvelope [d=" + distance + ", w=" + weight + ", hr=" + boneHeadRadius + ", tr=" + boneTailRadius + ", (" + head + ") -> (" + tail + ")]";
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,12 @@ package com.jme3.scene.plugins.blender.meshes;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
|
|
||||||
@ -14,10 +17,17 @@ import com.jme3.scene.Geometry;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
public class MeshContext {
|
public class MeshContext {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(MeshContext.class.getName());
|
||||||
|
|
||||||
/** A map between material index and the geometry. */
|
/** A map between material index and the geometry. */
|
||||||
private Map<Integer, List<Geometry>> geometries = new HashMap<Integer, List<Geometry>>();
|
private Map<Integer, List<Geometry>> geometries = new HashMap<Integer, List<Geometry>>();
|
||||||
/** The vertex reference map. */
|
/** The vertex reference map. */
|
||||||
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
|
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
|
||||||
|
/**
|
||||||
|
* A vertex group map. The key is the vertex group name and the value is the set of vertex groups.
|
||||||
|
* Linked hash map is used because the insertion order is important.
|
||||||
|
*/
|
||||||
|
private LinkedHashMap<String, VertexGroup> vertexGroups = new LinkedHashMap<String, VertexGroup>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a geometry for the specified material index.
|
* Adds a geometry for the specified material index.
|
||||||
@ -85,4 +95,91 @@ public class MeshContext {
|
|||||||
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
|
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
|
||||||
this.vertexReferenceMap = vertexReferenceMap;
|
this.vertexReferenceMap = vertexReferenceMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new empty vertex group to the mesh context.
|
||||||
|
* @param name
|
||||||
|
* the name of the vertex group
|
||||||
|
*/
|
||||||
|
public void addVertexGroup(String name) {
|
||||||
|
if (!vertexGroups.containsKey(name)) {
|
||||||
|
vertexGroups.put(name, new VertexGroup());
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.WARNING, "Vertex group already added: {0}", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a vertex to the vertex group with specified index (the index is the order of adding a group).
|
||||||
|
* @param vertexIndex
|
||||||
|
* the vertex index
|
||||||
|
* @param weight
|
||||||
|
* the vertex weight
|
||||||
|
* @param vertexGroupIndex
|
||||||
|
* the index of a vertex group
|
||||||
|
*/
|
||||||
|
public void addVertexToGroup(int vertexIndex, float weight, int vertexGroupIndex) {
|
||||||
|
if (vertexGroupIndex < 0 || vertexGroupIndex >= vertexGroups.size()) {
|
||||||
|
throw new IllegalArgumentException("Invalid group index: " + vertexGroupIndex);
|
||||||
|
}
|
||||||
|
int counter = 0;
|
||||||
|
for (Entry<String, VertexGroup> vg : vertexGroups.entrySet()) {
|
||||||
|
if (vertexGroupIndex == counter) {
|
||||||
|
vg.getValue().addVertex(vertexIndex, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a group with given name of null if such group does not exist.
|
||||||
|
* @param groupName
|
||||||
|
* the name of a vertex group
|
||||||
|
* @return vertex group with the given name or null
|
||||||
|
*/
|
||||||
|
public VertexGroup getGroup(String groupName) {
|
||||||
|
return vertexGroups.get(groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A vertex group class that maps vertex index to its weight in a single group.
|
||||||
|
* The group will need to be set a bone index in order to prepare proper buffers for the jme mesh.
|
||||||
|
* But that information is available after the skeleton is loaded.
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
*/
|
||||||
|
public static class VertexGroup extends HashMap<Integer, Float> {
|
||||||
|
private static final long serialVersionUID = 5601646768279643957L;
|
||||||
|
|
||||||
|
/** The index of the bone f the vertex group is to be used for attaching vertices to bones. */
|
||||||
|
private int boneIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mapping between vertex index and its weight.
|
||||||
|
* @param index
|
||||||
|
* the index of the vertex (in JME mesh)
|
||||||
|
* @param weight
|
||||||
|
* the weight of the vertex
|
||||||
|
*/
|
||||||
|
public void addVertex(int index, float weight) {
|
||||||
|
this.put(index, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method sets the bone index for the current vertex group.
|
||||||
|
* @param boneIndex
|
||||||
|
* the index of the bone
|
||||||
|
*/
|
||||||
|
public void setBoneIndex(int boneIndex) {
|
||||||
|
this.boneIndex = boneIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the index of the bone
|
||||||
|
*/
|
||||||
|
public int getBoneIndex() {
|
||||||
|
return boneIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,14 +94,14 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
/**
|
/**
|
||||||
* This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.
|
* This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.
|
||||||
*
|
*
|
||||||
* @param structure
|
* @param meshStructure
|
||||||
* the structure we read the mesh from
|
* the structure we read the mesh from
|
||||||
* @return the mesh feature
|
* @return the mesh feature
|
||||||
* @throws BlenderFileException
|
* @throws BlenderFileException
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public List<Geometry> toMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
public List<Geometry> toMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||||
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
|
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
if (geometries != null) {
|
if (geometries != null) {
|
||||||
List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());
|
List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());
|
||||||
for (Geometry geometry : geometries) {
|
for (Geometry geometry : geometries) {
|
||||||
@ -110,7 +110,7 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
return copiedGeometries;
|
return copiedGeometries;
|
||||||
}
|
}
|
||||||
|
|
||||||
String name = structure.getName();
|
String name = meshStructure.getName();
|
||||||
MeshContext meshContext = new MeshContext();
|
MeshContext meshContext = new MeshContext();
|
||||||
LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
|
LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
|
||||||
|
|
||||||
@ -118,33 +118,22 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
||||||
MaterialContext[] materials = null;
|
MaterialContext[] materials = null;
|
||||||
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
|
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
|
||||||
materials = materialHelper.getMaterials(structure, blenderContext);
|
materials = materialHelper.getMaterials(meshStructure, blenderContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.fine("Reading vertices.");
|
LOGGER.fine("Reading vertices.");
|
||||||
MeshBuilder meshBuilder = new MeshBuilder(structure, materials, blenderContext);
|
MeshBuilder meshBuilder = new MeshBuilder(meshStructure, materials, blenderContext);
|
||||||
if (meshBuilder.isEmpty()) {
|
if (meshBuilder.isEmpty()) {
|
||||||
LOGGER.fine("The geometry is empty.");
|
LOGGER.fine("The geometry is empty.");
|
||||||
geometries = new ArrayList<Geometry>(0);
|
geometries = new ArrayList<Geometry>(0);
|
||||||
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
|
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
|
||||||
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
|
blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
|
||||||
return geometries;
|
return geometries;
|
||||||
}
|
}
|
||||||
|
|
||||||
meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
|
meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
|
||||||
|
|
||||||
LOGGER.fine("Reading vertices groups (from the Object structure).");
|
|
||||||
Structure parent = blenderContext.peekParent();
|
|
||||||
Structure defbase = (Structure) parent.getFieldValue("defbase");
|
|
||||||
List<Structure> defs = defbase.evaluateListBase();
|
|
||||||
String[] verticesGroups = new String[defs.size()];
|
|
||||||
int defIndex = 0;
|
|
||||||
for (Structure def : defs) {
|
|
||||||
verticesGroups[defIndex++] = def.getFieldValue("name").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.fine("Reading custom properties.");
|
LOGGER.fine("Reading custom properties.");
|
||||||
Properties properties = this.loadProperties(structure, blenderContext);
|
Properties properties = this.loadProperties(meshStructure, blenderContext);
|
||||||
|
|
||||||
LOGGER.fine("Generating meshes.");
|
LOGGER.fine("Generating meshes.");
|
||||||
Map<Integer, List<Mesh>> meshes = meshBuilder.buildMeshes();
|
Map<Integer, List<Mesh>> meshes = meshBuilder.buildMeshes();
|
||||||
@ -162,9 +151,45 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Reading vertices groups.");// this MUST be done AFTER meshes are built, because otherwise we have no vertex references maps
|
||||||
|
Structure parent = blenderContext.peekParent();
|
||||||
|
Structure defbase = (Structure) parent.getFieldValue("defbase");
|
||||||
|
List<Structure> defs = defbase.evaluateListBase();
|
||||||
|
for (Structure def : defs) {
|
||||||
|
meshContext.addVertexGroup(def.getFieldValue("name").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
|
||||||
|
if (pDvert.isNotNull()) {// assigning weights and bone indices
|
||||||
|
List<Structure> dverts = pDvert.fetchData();
|
||||||
|
int blenderVertexIndex = 0;
|
||||||
|
for (Structure dvert : dverts) {
|
||||||
|
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
|
||||||
|
if (pDW.isNotNull()) {
|
||||||
|
List<Structure> dw = pDW.fetchData();
|
||||||
|
for (Structure deformWeight : dw) {
|
||||||
|
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
|
||||||
|
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
|
||||||
|
|
||||||
|
// we need to use JME vertex index here and NOT blender vertex index
|
||||||
|
for (Entry<Integer, Map<Integer, List<Integer>>> vertexReferenceMap : meshBuilder.getVertexReferenceMap().entrySet()) {// iterate through the meshes [key is the material index]
|
||||||
|
for (Entry<Integer, List<Integer>> vertexEntry : vertexReferenceMap.getValue().entrySet()) {// iterate through the vertex references for the specified material
|
||||||
|
if (vertexEntry.getKey().intValue() == blenderVertexIndex) {// if the indexes match then ...
|
||||||
|
for (Integer jmeVertexIndex : vertexEntry.getValue()) {// ... add all jme vertices to the specified group
|
||||||
|
meshContext.addVertexToGroup(jmeVertexIndex, weight, groupIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++blenderVertexIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// store the data in blender context before applying the material
|
// store the data in blender context before applying the material
|
||||||
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
|
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
|
||||||
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
|
blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
|
||||||
|
|
||||||
// apply materials only when all geometries are in place
|
// apply materials only when all geometries are in place
|
||||||
if (materials != null) {
|
if (materials != null) {
|
||||||
@ -175,7 +200,7 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
} else if (materials[materialNumber] != null) {
|
} else if (materials[materialNumber] != null) {
|
||||||
LinkedHashMap<String, List<Vector2f>> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
|
LinkedHashMap<String, List<Vector2f>> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
|
||||||
MaterialContext materialContext = materials[materialNumber];
|
MaterialContext materialContext = materials[materialNumber];
|
||||||
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext);
|
materialContext.applyMaterial(geometry, meshStructure.getOldMemoryAddress(), uvCoordinates, blenderContext);
|
||||||
} else {
|
} else {
|
||||||
geometry.setMaterial(blenderContext.getDefaultMaterial());
|
geometry.setMaterial(blenderContext.getDefaultMaterial());
|
||||||
LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces.");
|
LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces.");
|
||||||
@ -203,7 +228,7 @@ public class MeshHelper extends AbstractBlenderHelper {
|
|||||||
geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
|
geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
|
||||||
} else {
|
} else {
|
||||||
Material defaultMaterial = blenderContext.getDefaultMaterial();
|
Material defaultMaterial = blenderContext.getDefaultMaterial();
|
||||||
if(geometry.getMesh().getBuffer(Type.Color) != null) {
|
if (geometry.getMesh().getBuffer(Type.Color) != null) {
|
||||||
defaultMaterial = defaultMaterial.clone();
|
defaultMaterial = defaultMaterial.clone();
|
||||||
defaultMaterial.setBoolean("VertexColor", true);
|
defaultMaterial.setBoolean("VertexColor", true);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
package com.jme3.scene.plugins.blender.modifiers;
|
package com.jme3.scene.plugins.blender.modifiers;
|
||||||
|
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.jme3.animation.Bone;
|
import com.jme3.animation.Bone;
|
||||||
import com.jme3.animation.Skeleton;
|
import com.jme3.animation.Skeleton;
|
||||||
|
import com.jme3.math.Matrix4f;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
import com.jme3.scene.Mesh;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.VertexBuffer;
|
import com.jme3.scene.VertexBuffer;
|
||||||
import com.jme3.scene.VertexBuffer.Format;
|
import com.jme3.scene.VertexBuffer.Format;
|
||||||
import com.jme3.scene.VertexBuffer.Type;
|
import com.jme3.scene.VertexBuffer.Type;
|
||||||
@ -24,10 +29,13 @@ import com.jme3.scene.plugins.blender.BlenderContext;
|
|||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
||||||
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||||
|
import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
|
||||||
|
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshContext;
|
import com.jme3.scene.plugins.blender.meshes.MeshContext;
|
||||||
|
import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,13 +47,20 @@ import com.jme3.util.BufferUtils;
|
|||||||
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
|
||||||
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME
|
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME
|
||||||
|
|
||||||
|
private static final int FLAG_VERTEX_GROUPS = 0x01;
|
||||||
|
private static final int FLAG_BONE_ENVELOPES = 0x02;
|
||||||
|
|
||||||
private Structure armatureObject;
|
private Structure armatureObject;
|
||||||
private Skeleton skeleton;
|
private Skeleton skeleton;
|
||||||
private Structure objectStructure;
|
|
||||||
private Structure meshStructure;
|
private Structure meshStructure;
|
||||||
|
/** The wold transform matrix of the armature object. */
|
||||||
|
private Matrix4f objectWorldMatrix;
|
||||||
/** Old memory address of the mesh that will have the skeleton applied. */
|
/** Old memory address of the mesh that will have the skeleton applied. */
|
||||||
private Long meshOMA;
|
private Long meshOMA;
|
||||||
|
/** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
|
||||||
|
private boolean useVertexGroups;
|
||||||
|
/** The variable tells if the bones' envelopes should be used to assign verts to bones. */
|
||||||
|
private boolean useBoneEnvelopes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor reads animation data from the object structore. The
|
* This constructor reads animation data from the object structore. The
|
||||||
@ -66,6 +81,11 @@ import com.jme3.util.BufferUtils;
|
|||||||
if (this.validate(modifierStructure, blenderContext)) {
|
if (this.validate(modifierStructure, blenderContext)) {
|
||||||
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
||||||
if (pArmatureObject.isNotNull()) {
|
if (pArmatureObject.isNotNull()) {
|
||||||
|
int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
|
||||||
|
useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
|
||||||
|
useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
|
||||||
|
modifying = useBoneEnvelopes || useVertexGroups;
|
||||||
|
if (modifying) {// if neither option is used the modifier will not modify anything anyway
|
||||||
armatureObject = pArmatureObject.fetchData().get(0);
|
armatureObject = pArmatureObject.fetchData().get(0);
|
||||||
|
|
||||||
// load skeleton
|
// load skeleton
|
||||||
@ -79,11 +99,17 @@ import com.jme3.util.BufferUtils;
|
|||||||
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
||||||
skeleton = new Skeleton(bones);
|
skeleton = new Skeleton(bones);
|
||||||
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
|
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
|
||||||
this.objectStructure = objectStructure;
|
|
||||||
this.meshStructure = meshStructure;
|
this.meshStructure = meshStructure;
|
||||||
|
|
||||||
// read mesh indexes
|
// read mesh indexes
|
||||||
meshOMA = meshStructure.getOldMemoryAddress();
|
meshOMA = meshStructure.getOldMemoryAddress();
|
||||||
|
|
||||||
|
if (useBoneEnvelopes) {
|
||||||
|
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||||
|
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
|
objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
modifying = false;
|
modifying = false;
|
||||||
}
|
}
|
||||||
@ -114,35 +140,6 @@ import com.jme3.util.BufferUtils;
|
|||||||
bc.buildBone(result, spatialOMA, blenderContext);
|
bc.buildBone(result, spatialOMA, blenderContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns a map where the key is the object's group index that
|
|
||||||
* is used by a bone and the key is the bone index in the armature.
|
|
||||||
*
|
|
||||||
* @param defBaseStructure
|
|
||||||
* a bPose structure of the object
|
|
||||||
* @return bone group-to-index map
|
|
||||||
* @throws BlenderFileException
|
|
||||||
* this exception is thrown when the blender file is somehow
|
|
||||||
* corrupted
|
|
||||||
*/
|
|
||||||
public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException {
|
|
||||||
Map<Integer, Integer> result = null;
|
|
||||||
if (skeleton.getBoneCount() != 0) {
|
|
||||||
result = new HashMap<Integer, Integer>();
|
|
||||||
List<Structure> deformGroups = defBaseStructure.evaluateListBase();// bDeformGroup
|
|
||||||
int groupIndex = 0;
|
|
||||||
for (Structure deformGroup : deformGroups) {
|
|
||||||
String deformGroupName = deformGroup.getFieldValue("name").toString();
|
|
||||||
int boneIndex = skeleton.getBoneIndex(deformGroupName);
|
|
||||||
if (boneIndex >= 0) {
|
|
||||||
result.put(groupIndex, boneIndex);
|
|
||||||
}
|
|
||||||
++groupIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void apply(Node node, BlenderContext blenderContext) {
|
public void apply(Node node, BlenderContext blenderContext) {
|
||||||
@ -153,17 +150,15 @@ import com.jme3.util.BufferUtils;
|
|||||||
// setting weights for bones
|
// setting weights for bones
|
||||||
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
||||||
int[] bonesGroups = new int[] { 0 };
|
|
||||||
for (Geometry geom : geomList) {
|
for (Geometry geom : geomList) {
|
||||||
int materialIndex = meshContext.getMaterialIndex(geom);
|
int materialIndex = meshContext.getMaterialIndex(geom);
|
||||||
Mesh mesh = geom.getMesh();
|
Mesh mesh = geom.getMesh();
|
||||||
|
|
||||||
try {
|
MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
|
||||||
VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext);
|
|
||||||
if (buffers != null) {
|
if (buffers != null) {
|
||||||
mesh.setMaxNumWeights(bonesGroups[0]);
|
mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
|
||||||
mesh.setBuffer(buffers[0]);
|
mesh.setBuffer(buffers.verticesWeights);
|
||||||
mesh.setBuffer(buffers[1]);
|
mesh.setBuffer(buffers.verticesWeightsIndices);
|
||||||
|
|
||||||
LOGGER.fine("Generating bind pose and normal buffers.");
|
LOGGER.fine("Generating bind pose and normal buffers.");
|
||||||
mesh.generateBindPose(true);
|
mesh.generateBindPose(true);
|
||||||
@ -180,10 +175,6 @@ import com.jme3.util.BufferUtils;
|
|||||||
mesh.setBuffer(verticesWeightsHW);
|
mesh.setBuffer(verticesWeightsHW);
|
||||||
mesh.setBuffer(verticesWeightsIndicesHW);
|
mesh.setBuffer(verticesWeightsIndicesHW);
|
||||||
}
|
}
|
||||||
} catch (BlenderFileException e) {
|
|
||||||
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
|
|
||||||
invalid = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
@ -193,235 +184,208 @@ import com.jme3.util.BufferUtils;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method reads mesh indexes
|
* Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
|
||||||
*
|
*
|
||||||
* @param objectStructure
|
* @param meshContext
|
||||||
* structure of the object that has the armature modifier applied
|
* the mesh context
|
||||||
* @param meshStructure
|
* @param skeleton
|
||||||
* the structure of the object's mesh
|
* the current skeleton
|
||||||
|
* @param materialIndex
|
||||||
|
* the material index
|
||||||
|
* @param mesh
|
||||||
|
* the mesh we create the buffers for
|
||||||
* @param blenderContext
|
* @param blenderContext
|
||||||
* the blender context
|
* the blender context
|
||||||
* @throws BlenderFileException
|
* @return an instance that aggregates all needed data for the mesh
|
||||||
* this exception is thrown when the blend file structure is
|
|
||||||
* somehow invalid or corrupted
|
|
||||||
*/
|
*/
|
||||||
private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException {
|
private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
|
||||||
Structure defBase = (Structure) objectStructure.getFieldValue("defbase");
|
int vertexListSize = meshContext.getVertexCount(materialIndex);
|
||||||
Map<Integer, Integer> groupToBoneIndexMap = this.getGroupToBoneIndexMap(defBase, skeleton);
|
Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
|
||||||
|
|
||||||
MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());
|
Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
|
||||||
|
Buffer indexes = mesh.getBuffer(Type.Index).getData();
|
||||||
|
FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
|
||||||
|
|
||||||
return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap);
|
int maximumWeightsPerVertex = 0;
|
||||||
|
if (useVertexGroups) {
|
||||||
|
LOGGER.fine("Attaching verts to bones using vertex groups.");
|
||||||
|
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
||||||
|
Bone bone = skeleton.getBone(boneIndex);
|
||||||
|
VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
|
||||||
|
if (vertexGroup != null) {
|
||||||
|
vertexGroup.setBoneIndex(boneIndex);
|
||||||
|
vertexGroups.put(bone.getName(), vertexGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (useBoneEnvelopes) {
|
||||||
* This method returns an array of size 2. The first element is a vertex
|
LOGGER.fine("Attaching verts to bones using bone envelopes.");
|
||||||
* buffer holding bone weights for every vertex in the model. The second
|
Vector3f pos = new Vector3f();
|
||||||
* element is a vertex buffer holding bone indices for vertices (the indices
|
|
||||||
* of bones the vertices are assigned to).
|
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
||||||
*
|
Bone bone = skeleton.getBone(boneIndex);
|
||||||
* @param meshStructure
|
BoneContext boneContext = blenderContext.getBoneContext(bone);
|
||||||
* the mesh structure object
|
BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
|
||||||
* @param vertexListSize
|
if (boneEnvelope != null) {
|
||||||
* a number of vertices in the model
|
VertexGroup vertexGroup = vertexGroups.get(bone.getName());
|
||||||
* @param bonesGroups
|
if (vertexGroup == null) {
|
||||||
* this is an output parameter, it should be a one-sized array;
|
vertexGroup = new VertexGroup();
|
||||||
* the maximum amount of weights per vertex (up to
|
vertexGroups.put(bone.getName(), vertexGroup);
|
||||||
* MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
|
}
|
||||||
* @param vertexReferenceMap
|
vertexGroup.setBoneIndex(boneIndex);
|
||||||
* this reference map allows to map the original vertices read
|
|
||||||
* from blender to vertices that are really in the model; one
|
for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
|
||||||
* vertex may appear several times in the result model
|
List<Integer> vertexIndices = entry.getValue();
|
||||||
* @param groupToBoneIndexMap
|
for (int j = 0; j < indexes.limit(); ++j) {
|
||||||
* this object maps the group index (to which a vertices in
|
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
|
||||||
* blender belong) to bone index of the model
|
if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
|
||||||
* @return arrays of vertices weights and their bone indices and (as an
|
int ii = index * 3;
|
||||||
* output parameter) the maximum amount of weights for a vertex
|
pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
|
||||||
* @throws BlenderFileException
|
// move the vertex to the global space position
|
||||||
* this exception is thrown when the blend file structure is
|
objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
|
||||||
* somehow invalid or corrupted
|
if (boneEnvelope.isInEnvelope(pos)) {
|
||||||
*/
|
vertexGroup.addVertex(index, boneEnvelope.getWeight());
|
||||||
private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap) throws BlenderFileException {
|
} else if (boneIndex == 5) {
|
||||||
bonesGroups[0] = 0;
|
System.out.println("Siê nie za³apa³: " + pos);
|
||||||
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
|
||||||
|
if (vertexGroups.size() > 0) {
|
||||||
|
LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
|
||||||
|
for (VertexGroup vertexGroup : vertexGroups.values()) {
|
||||||
|
for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
|
||||||
|
WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
|
||||||
|
if (vertexWeights == null) {
|
||||||
|
vertexWeights = new WeightsAndBoneIndexes();
|
||||||
|
weights.put(entry.getKey(), vertexWeights);
|
||||||
|
}
|
||||||
|
vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
|
for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
|
||||||
|
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
|
||||||
|
entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
|
||||||
|
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
|
||||||
|
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Preparing buffers for the mesh.");
|
||||||
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
|
for (int i = 0; i < indexes.limit(); ++i) {
|
||||||
if (pDvert.isNotNull()) {// assigning weights and bone indices
|
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
|
||||||
boolean warnAboutTooManyVertexWeights = false;
|
WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
|
||||||
// dverts.size() = verticesAmount (one dvert per vertex in blender)
|
if (weightsAndBoneIndexes != null) {
|
||||||
List<Structure> dverts = pDvert.fetchData();
|
int count = 0;
|
||||||
int vertexIndex = 0;
|
for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
|
||||||
// use tree map to sort weights from the lowest to the highest ones
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
|
||||||
TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>();
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
|
||||||
|
++count;
|
||||||
for (Structure dvert : dverts) {
|
|
||||||
// we fetch the referenced vertices here
|
|
||||||
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));
|
|
||||||
if (vertexIndices != null) {
|
|
||||||
// total amount of wights assigned to the vertex (max. 4 in JME)
|
|
||||||
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();
|
|
||||||
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
|
|
||||||
if (totweight > 0 && groupToBoneIndexMap != null) {
|
|
||||||
weightToIndexMap.clear();
|
|
||||||
int weightIndex = 0;
|
|
||||||
List<Structure> dw = pDW.fetchData();
|
|
||||||
for (Structure deformWeight : dw) {
|
|
||||||
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
|
|
||||||
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
|
|
||||||
// boneIndex == null: it here means that we came
|
|
||||||
// accross group that has no bone attached to, so
|
|
||||||
// simply ignore it
|
|
||||||
// if weight == 0 and weightIndex == 0 then ignore
|
|
||||||
// the weight (do not set weight = 0 as a first
|
|
||||||
// weight)
|
|
||||||
if (boneIndex != null && (weight > 0.0f || weightIndex > 0)) {
|
|
||||||
if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) {
|
|
||||||
if (weight == 0.0f) {
|
|
||||||
boneIndex = Integer.valueOf(0);
|
|
||||||
}
|
|
||||||
// we apply the weight to all referenced
|
|
||||||
// vertices
|
|
||||||
for (Integer index : vertexIndices) {
|
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
|
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
|
|
||||||
}
|
|
||||||
weightToIndexMap.put(weight, weightIndex);
|
|
||||||
bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1);
|
|
||||||
} else if (weight > 0) {// if weight is zero the
|
|
||||||
// simply ignore it
|
|
||||||
warnAboutTooManyVertexWeights = true;
|
|
||||||
Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry();
|
|
||||||
if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) {
|
|
||||||
// we apply the weight to all referenced
|
|
||||||
// vertices
|
|
||||||
for (Integer index : vertexIndices) {
|
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight);
|
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue());
|
|
||||||
}
|
|
||||||
weightToIndexMap.remove(lowestWeightAndIndex.getKey());
|
|
||||||
weightToIndexMap.put(weight, lowestWeightAndIndex.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++weightIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 0.0 weight indicates, do not transform this vertex,
|
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
|
||||||
// but keep it in bind pose.
|
|
||||||
for (Integer index : vertexIndices) {
|
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f);
|
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++vertexIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warnAboutTooManyVertexWeights) {
|
|
||||||
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// always bind all vertices to 0-indexed bone
|
|
||||||
// this bone makes the model look normally if vertices have no bone
|
|
||||||
// assigned and it is used in object animation, so if we come
|
|
||||||
// accross object
|
|
||||||
// animation we can use the 0-indexed bone for this
|
|
||||||
for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
|
|
||||||
// we apply the weight to all referenced vertices
|
|
||||||
for (Integer index : vertexIndexList) {
|
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bonesGroups[0] = Math.max(bonesGroups[0], 1);
|
|
||||||
|
|
||||||
this.endBoneAssigns(vertexListSize, weightsFloatData);
|
|
||||||
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
|
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
|
||||||
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
|
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
|
||||||
|
|
||||||
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
|
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
|
||||||
verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
|
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
|
||||||
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
|
|
||||||
|
return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes weights if needed and finds largest amount of weights used for
|
* A class that gathers the data for mesh bone buffers.
|
||||||
* all vertices in the buffer.
|
* Added to increase code readability.
|
||||||
*
|
*
|
||||||
* @param vertCount
|
* @author Marcin Roguski (Kaelthas)
|
||||||
* amount of vertices
|
|
||||||
* @param weightsFloatData
|
|
||||||
* weights for vertices
|
|
||||||
*/
|
*/
|
||||||
private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
|
private static class MeshWeightsData {
|
||||||
weightsFloatData.rewind();
|
public final int maximumWeightsPerVertex;
|
||||||
float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX];
|
public final VertexBuffer verticesWeights;
|
||||||
for (int v = 0; v < vertCount; ++v) {
|
public final VertexBuffer verticesWeightsIndices;
|
||||||
float sum = 0;
|
|
||||||
for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {
|
public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
|
||||||
weights[i] = weightsFloatData.get();
|
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
|
||||||
sum += weights[i];
|
this.verticesWeights = verticesWeights;
|
||||||
|
this.verticesWeightsIndices = verticesWeightsIndices;
|
||||||
}
|
}
|
||||||
if (sum != 1f && sum != 0.0f) {
|
|
||||||
weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX);
|
|
||||||
// compute new vals based on sum
|
|
||||||
float sumToB = 1f / sum;
|
|
||||||
for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {
|
|
||||||
weightsFloatData.put(weights[i] * sumToB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
weightsFloatData.rewind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is now not used because it broke animations.
|
/**
|
||||||
// Perhaps in the future I will find a solution to this problem.
|
* A map between the bone index and the bone's weight.
|
||||||
// I store it here for future use.
|
*
|
||||||
//
|
* @author Marcin Roguski (Kaelthas)
|
||||||
// private void loadBonePoses() {
|
*/
|
||||||
// TempVars tempVars = TempVars.get();
|
private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
|
||||||
// try {
|
private static final long serialVersionUID = 2754299007299077459L;
|
||||||
// Pointer pPose = (Pointer) armatureObject.getFieldValue("pose");
|
|
||||||
// if (pPose.isNotNull()) {
|
/**
|
||||||
// LOGGER.fine("Loading the pose of the armature.");
|
* The method normalizes the weights and bone indexes data.
|
||||||
// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
|
||||||
// ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
* Next it normalizes the weights so that the sum of all verts is 1.
|
||||||
//
|
* @param maximumSize
|
||||||
// Structure pose = pPose.fetchData().get(0);
|
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
|
||||||
// Structure chanbase = (Structure) pose.getFieldValue("chanbase");
|
*/
|
||||||
// List<Structure> chans = chanbase.evaluateListBase();
|
public void normalize(int maximumSize) {
|
||||||
// Transform transform = new Transform();
|
if (this.size() > maximumSize) {// select only the most significant weights
|
||||||
// for (Structure poseChannel : chans) {
|
float lowestWeight = Float.MAX_VALUE;
|
||||||
// Pointer pBone = (Pointer) poseChannel.getFieldValue("bone");
|
int lowestWeightIndex = -1;
|
||||||
// if (pBone.isNull()) {
|
HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
|
||||||
// throw new BlenderFileException("Cannot find bone for pose channel named: " + poseChannel.getName());
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
// }
|
if (msw.size() < maximumSize) {
|
||||||
// BoneContext boneContext = blenderContext.getBoneContext(pBone.getOldMemoryAddress());
|
msw.put(entry.getKey(), entry.getValue());
|
||||||
//
|
if (entry.getValue() < lowestWeight) {
|
||||||
// LOGGER.log(Level.FINEST, "Getting the global pose transformation for bone: {0}", boneContext);
|
lowestWeight = entry.getValue();
|
||||||
// Matrix4f poseMat = objectHelper.getMatrix(poseChannel, "pose_mat", blenderContext.getBlenderKey().isFixUpAxis());
|
lowestWeightIndex = entry.getKey();
|
||||||
// poseMat.multLocal(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
}
|
||||||
//
|
} else if (entry.getValue() > lowestWeight) {
|
||||||
// Matrix4f armatureWorldMat = objectHelper.getMatrix(armatureObject, "obmat", blenderContext.getBlenderKey().isFixUpAxis());
|
msw.remove(lowestWeightIndex);
|
||||||
// Matrix4f boneWorldMat = armatureWorldMat.multLocal(poseMat);
|
msw.put(lowestWeightIndex, lowestWeight);
|
||||||
//
|
|
||||||
// boneWorldMat.toTranslationVector(tempVars.vect1);
|
// search again for the lowest weight
|
||||||
// boneWorldMat.toRotationQuat(tempVars.quat1);
|
lowestWeight = Float.MAX_VALUE;
|
||||||
// boneWorldMat.toScaleVector(tempVars.vect2);
|
for (Entry<Integer, Float> e : msw.entrySet()) {
|
||||||
// transform.setTranslation(tempVars.vect1);
|
if (e.getValue() < lowestWeight) {
|
||||||
// transform.setRotation(tempVars.quat1);
|
lowestWeight = e.getValue();
|
||||||
// transform.setScale(tempVars.vect2);
|
lowestWeightIndex = e.getKey();
|
||||||
//
|
}
|
||||||
// constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform);
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// } catch (BlenderFileException e) {
|
|
||||||
// LOGGER.log(Level.WARNING, "Problems occured during pose loading: {0}.", e.getLocalizedMessage());
|
// replace current weights with the given ones
|
||||||
// } finally {
|
this.clear();
|
||||||
// tempVars.release();
|
this.putAll(msw);
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
// normalizing the weights so that the sum of the values is equal to '1'
|
||||||
|
float sum = 0;
|
||||||
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
|
sum += entry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum != 0 && sum != 1) {
|
||||||
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
|
entry.setValue(entry.getValue() / sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user