Armature loading refactoring and bugfixing.

- animations should now load properly
- armature loading moved to ArmatureModifier (no bones are touched inside MeshHelper so the code should be a little more clear now)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8238 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Kae..pl 13 years ago
parent 64c61c86e4
commit 3402f51824
  1. 26
      engine/src/blender/com/jme3/scene/plugins/blender/DataRepository.java
  2. 57
      engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
  3. 184
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  4. 272
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  5. 22
      engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@ -49,6 +49,7 @@ import com.jme3.scene.plugins.blender.file.DnaBlockData;
import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext; import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.meshes.MeshHelper.VertexData;
import com.jme3.scene.plugins.blender.modifiers.Modifier; import com.jme3.scene.plugins.blender.modifiers.Modifier;
/** /**
@ -89,6 +90,8 @@ public class DataRepository {
protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>(); protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>();
/** A list of constraints for the specified object. */ /** A list of constraints for the specified object. */
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>(); protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
/** Vertex data for a mesh specified by OMA. */
protected Map<Long, VertexData> vertexData = new HashMap<Long, VertexData>();
/** A map of material contexts. */ /** A map of material contexts. */
protected Map<Material, MaterialContext> materialContexts = new HashMap<Material, MaterialContext>(); protected Map<Material, MaterialContext> materialContexts = new HashMap<Material, MaterialContext>();
/** A map og helpers that perform loading. */ /** A map og helpers that perform loading. */
@ -398,6 +401,29 @@ public class DataRepository {
return constraints.get(objectOMA); return constraints.get(objectOMA);
} }
/**
* This method sets the vertex data for the specified mesh. Attention!!! If
* vertex data is already set it will be overwritten.
* @param meshOMA
* old memeory address of mesh
* @param vertexData
* mesh's vertex data
*/
public void setVertexData(Long meshOMA, VertexData vertexData) {
this.vertexData.put(meshOMA, vertexData);
}
/**
* This method returns vertex data for a mesh with the specified old memory
* address. If no data is registered then null is returned.
* @param meshOMA
* old memeory address of mesh
* @return mesh's vertex data
*/
public VertexData getVertexData(Long meshOMA) {
return vertexData.get(meshOMA);
}
/** /**
* This method sets the material context for the given material. * This method sets the material context for the given material.
* If the context is already set it will be replaced. * If the context is already set it will be replaced.

@ -31,7 +31,6 @@
*/ */
package com.jme3.scene.plugins.blender.animations; package com.jme3.scene.plugins.blender.animations;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -41,12 +40,8 @@ import java.util.logging.Logger;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack; import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.DataRepository; import com.jme3.scene.plugins.blender.DataRepository;
import com.jme3.scene.plugins.blender.curves.BezierCurve; import com.jme3.scene.plugins.blender.curves.BezierCurve;
@ -100,27 +95,6 @@ public class ArmatureHelper extends AbstractBlenderHelper {
return result; return result;
} }
/**
* This method reads the bones and returns an empty skeleton. Bones should be assigned later.
* @param structure
* armature structure
* @param dataRepository
* the data repository
* @return an empty skeleton, bones are stored within the helper object
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow corrupted
*/
public Skeleton toArmature(Structure structure, DataRepository dataRepository) throws BlenderFileException {
LOGGER.log(Level.INFO, "Converting structure to Armature!");
Structure bonebase = (Structure) structure.getFieldValue("bonebase");
List<Structure> bonesStructures = bonebase.evaluateListBase(dataRepository);
for (Structure boneStructure : bonesStructures) {
BoneTransformationData rootBoneTransformationData = this.readBoneAndItsChildren(boneStructure, null, dataRepository);
boneDataRoots.add(rootBoneTransformationData);
}
return new Skeleton();//bones are assigned later
}
/** /**
* 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 * 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. * bone index in the armature.
@ -135,7 +109,7 @@ public class ArmatureHelper extends AbstractBlenderHelper {
if (bonesMap != null && bonesMap.size() != 0) { if (bonesMap != null && bonesMap.size() != 0) {
result = new HashMap<Integer, Integer>(); result = new HashMap<Integer, Integer>();
List<Structure> deformGroups = defBaseStructure.evaluateListBase(dataRepository);//bDeformGroup List<Structure> deformGroups = defBaseStructure.evaluateListBase(dataRepository);//bDeformGroup
int groupIndex = 0;//TODO: consider many armatures attached to one object in the future !!! int groupIndex = 0;
for (Structure deformGroup : deformGroups) { for (Structure deformGroup : deformGroups) {
String deformGroupName = deformGroup.getFieldValue("name").toString(); String deformGroupName = deformGroup.getFieldValue("name").toString();
Integer boneIndex = bonesMap.get(deformGroupName); Integer boneIndex = bonesMap.get(deformGroupName);
@ -179,7 +153,7 @@ public class ArmatureHelper extends AbstractBlenderHelper {
* this exception is thrown when the blender file is somehow corrupted * this exception is thrown when the blender file is somehow corrupted
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected BoneTransformationData readBoneAndItsChildren(Structure boneStructure, BoneTransformationData parent, DataRepository dataRepository) throws BlenderFileException { public BoneTransformationData readBoneAndItsChildren(Structure boneStructure, BoneTransformationData parent, DataRepository dataRepository) throws BlenderFileException {
String name = boneStructure.getFieldValue("name").toString(); String name = boneStructure.getFieldValue("name").toString();
Bone bone = new Bone(name); Bone bone = new Bone(name);
int bonesAmount = bonesOMAs.size(); int bonesAmount = bonesOMAs.size();
@ -230,6 +204,10 @@ public class ArmatureHelper extends AbstractBlenderHelper {
} }
} }
public void addBoneDataRoot(BoneTransformationData dataRoot) {
this.boneDataRoots.add(dataRoot);
}
/** /**
* This method returns bone transformation data for the bone of a given index. * This method returns bone transformation data for the bone of a given index.
* @param index * @param index
@ -252,7 +230,7 @@ public class ArmatureHelper extends AbstractBlenderHelper {
* This class holds the data needed later for bone transformation calculation and to bind parent with children. * This class holds the data needed later for bone transformation calculation and to bind parent with children.
* @author Marcin Roguski * @author Marcin Roguski
*/ */
private static class BoneTransformationData { public static class BoneTransformationData {
/** Inverse matrix of bone's parent bone. */ /** Inverse matrix of bone's parent bone. */
private Matrix4f totalInverseBoneParentMatrix; private Matrix4f totalInverseBoneParentMatrix;
@ -299,7 +277,7 @@ public class ArmatureHelper extends AbstractBlenderHelper {
* additional bone transformation which indicates it's mesh parent and armature object transformations * additional bone transformation which indicates it's mesh parent and armature object transformations
* @return * @return
*/ */
public Bone[] buildBonesStructure(Long armatureOMA, Matrix4f additionalRootBoneTransformation) {//TODO: consider many skeletons ??? public Bone[] buildBonesStructure(Long armatureOMA, Matrix4f additionalRootBoneTransformation) {
List<Bone> bones = new ArrayList<Bone>(boneDataRoots.size() + 1); List<Bone> bones = new ArrayList<Bone>(boneDataRoots.size() + 1);
bones.add(new Bone("")); bones.add(new Bone(""));
for (BoneTransformationData btd : boneDataRoots) { for (BoneTransformationData btd : boneDataRoots) {
@ -308,25 +286,6 @@ public class ArmatureHelper extends AbstractBlenderHelper {
return bones.toArray(new Bone[bones.size()]); return bones.toArray(new Bone[bones.size()]);
} }
/**
* This method assigns an immovable bone to vertices that have no bone assigned. They have the bone index with the
* value -1.
* @param immovableBoneIndex
* the ondex of immovable bone
* @param meshes
* a list of meshes whose vertices will be assigned to immovable bone
*/
public void assignBoneToOrphanedVertices(byte immovableBoneIndex, Mesh[] meshes) {
//bone indices are common for all the object's meshes (vertex indices specify which are to be used)
VertexBuffer boneIndices = meshes[0].getBuffer(Type.BoneIndex);//common buffer to all the meshes
ByteBuffer data = (ByteBuffer) boneIndices.getData();
for (int i = 0; i < boneIndices.getNumElements(); ++i) {
if (data.get(i) == -1) {
data.put(i, immovableBoneIndex);
}
}
}
@Override @Override
public void clearState() { public void clearState() {
bonesMap.clear(); bonesMap.clear();

@ -31,7 +31,6 @@
*/ */
package com.jme3.scene.plugins.blender.meshes; package com.jme3.scene.plugins.blender.meshes;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -41,9 +40,6 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
@ -58,7 +54,6 @@ import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.DataRepository; import com.jme3.scene.plugins.blender.DataRepository;
import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Pointer;
@ -77,8 +72,6 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class MeshHelper extends AbstractBlenderHelper { public class MeshHelper extends AbstractBlenderHelper {
protected static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // have no idea why 4, could someone please explain ?
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions. * versions.
@ -250,6 +243,8 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
} }
dataRepository.setVertexData(structure.getOldMemoryAddress(), new VertexData(vertexList, vertexReferenceMap));
Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]); Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);
// reading vertices groups (from the parent) // reading vertices groups (from the parent)
@ -262,18 +257,6 @@ public class MeshHelper extends AbstractBlenderHelper {
verticesGroups[defIndex++] = def.getFieldValue("name").toString(); verticesGroups[defIndex++] = def.getFieldValue("name").toString();
} }
// vertices bone weights and indices
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
Structure defBase = (Structure) parent.getFieldValue("defbase");
Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository);
VertexBuffer verticesWeights = null, verticesWeightsIndices = null;
int[] bonesGroups = new int[] { 0 };
VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(structure, vertexList.size(), bonesGroups,
vertexReferenceMap, groupToBoneIndexMap, dataRepository);
verticesWeights = boneWeightsAndIndex[0];
verticesWeightsIndices = boneWeightsAndIndex[1];
// reading materials // reading materials
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
Material[] materials = null; Material[] materials = null;
@ -333,13 +316,6 @@ public class MeshHelper extends AbstractBlenderHelper {
mesh.setBuffer(Type.Color, 4, verticesColorsBuffer); mesh.setBuffer(Type.Color, 4, verticesColorsBuffer);
} }
// setting weights for bones
if (verticesWeights != null) {
mesh.setMaxNumWeights(bonesGroups[0]);
mesh.setBuffer(verticesWeights);
mesh.setBuffer(verticesWeightsIndices);
}
// setting faces' normals // setting faces' normals
mesh.setBuffer(normalsBuffer); mesh.setBuffer(normalsBuffer);
mesh.setBuffer(normalsBind); mesh.setBuffer(normalsBind);
@ -418,22 +394,6 @@ public class MeshHelper extends AbstractBlenderHelper {
return geometries; return geometries;
} }
/**
* This method returns bounding shpere of a given mesh.
* @param mesh the mesh to read the bounding sphere from
* @return the bounding sphere of the given mesh
*/
protected BoundingSphere getBoundingSphere(Mesh mesh) {
BoundingVolume bv = mesh.getBound();
if(bv instanceof BoundingSphere) {
return (BoundingSphere)bv;
} else if(bv instanceof BoundingBox) {
BoundingBox bb = (BoundingBox)bv;
return new BoundingSphere(bb.getCenter().subtract(bb.getMin(null)).length(), bb.getCenter());
}
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
/** /**
* This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth. * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
* *
@ -535,136 +495,26 @@ public class MeshHelper extends AbstractBlenderHelper {
return vertices; return vertices;
} }
/** @Override
* This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The public boolean shouldBeLoaded(Structure structure, DataRepository dataRepository) {
* second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to). return true;
*
* @param meshStructure
* the mesh structure object
* @param vertexListSize
* a number of vertices in the model
* @param bonesGroups
* this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to
* MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
* @param vertexReferenceMap
* this reference map allows to map the original vertices read from blender to vertices that are really in the model; one
* vertex may appear several times in the result model
* @param groupToBoneIndexMap
* this object maps the group index (to which a vertices in blender belong) to bone index of the model
* @param dataRepository
* the data repository
* @return arrays of vertices weights and their bone indices and (as an outpot parameter) the maximum amount of weights for a vertex
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
public VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups,
Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository)
throws BlenderFileException {
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per
// vertex in blender)
int vertexIndex = 0;
for (Structure dvert : dverts) {
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
// (max. 4 in JME)
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :)
int weightIndex = 0;
List<Structure> dw = pDW.fetchData(dataRepository.getInputStream());
for (Structure deformWeight : dw) {
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
if (boneIndex != null) {// null here means that we came accross group that has no bone attached to
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
if (weight == 0.0f) {
weight = 1;
boneIndex = Integer.valueOf(0);
}
// we apply the weight to all referenced vertices
for (Integer index : vertexIndices) {
// all indices are always assigned to 0-indexed bone
// weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, 1.0f);
// indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, (byte)0);
// if(weight != 0.0f) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
// }
}
}
++weightIndex;
}
} else {
for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
}
++vertexIndex;
}
} 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);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
}
}
bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
} }
/** public static class VertexData {
* Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer. private List<Vector3f> vertexList;
*/ private Map<Integer, List<Integer>> vertexReferenceMap;
protected int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
int maxWeightsPerVert = 0;
weightsFloatData.rewind();
for (int v = 0; v < vertCount; ++v) {
float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();
if (w3 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
} else if (w2 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
} else if (w1 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
} else if (w0 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
}
float sum = w0 + w1 + w2 + w3; public VertexData(List<Vector3f> vertexList, Map<Integer, List<Integer>> vertexReferenceMap) {
if (sum != 1f && sum != 0.0f) { this.vertexList = vertexList;
weightsFloatData.position(weightsFloatData.position() - 4); this.vertexReferenceMap = vertexReferenceMap;
// compute new vals based on sum
float sumToB = 1f / sum;
weightsFloatData.put(w0 * sumToB);
weightsFloatData.put(w1 * sumToB);
weightsFloatData.put(w2 * sumToB);
weightsFloatData.put(w3 * sumToB);
}
} }
weightsFloatData.rewind();
// mesh.setMaxNumWeights(maxWeightsPerVert); public List<Vector3f> getVertexList() {
return maxWeightsPerVert; return vertexList;
} }
@Override public Map<Integer, List<Integer>> getVertexReferenceMap() {
public boolean shouldBeLoaded(Structure structure, DataRepository dataRepository) { return vertexReferenceMap;
return true; }
} }
} }

@ -1,16 +1,17 @@
package com.jme3.scene.plugins.blender.modifiers; package com.jme3.scene.plugins.blender.modifiers;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
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.Entry; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.jme3.animation.AnimControl; import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation; import com.jme3.animation.Animation;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.animation.BoneAnimation; import com.jme3.animation.BoneAnimation;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton; import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl; import com.jme3.animation.SkeletonControl;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
@ -18,16 +19,23 @@ 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.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.DataRepository; import com.jme3.scene.plugins.blender.DataRepository;
import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper; import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper.BoneTransformationData;
import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.constraints.Constraint;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader;
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.MeshHelper.VertexData;
import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.scene.plugins.ogre.AnimData; import com.jme3.scene.plugins.ogre.AnimData;
import com.jme3.util.BufferUtils;
/** /**
* This modifier allows to add bone animation to the object. * This modifier allows to add bone animation to the object.
@ -35,6 +43,18 @@ import com.jme3.scene.plugins.ogre.AnimData;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ArmatureModifier extends Modifier { /* package */class ArmatureModifier extends Modifier {
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // have no idea why 4, could someone please explain ?
/** Old memory address of the armature's object. */
private Long armatureObjectOMA;
/** Old memory address of the mesh that will have the skeleton applied. */
private Long meshOMA;
/** The maxiumum amount of bone groups applied to a single vertex (max = MAXIMUM_WEIGHTS_PER_VERTEX). */
private int boneGroups;
/** The weights of vertices. */
private VertexBuffer verticesWeights;
/** The indexes of bones applied to vertices. */
private VertexBuffer verticesWeightsIndices;
/** /**
* This constructor is only temporary. It will be removed when object * This constructor is only temporary. It will be removed when object
@ -61,29 +81,31 @@ import com.jme3.scene.plugins.ogre.AnimData;
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
if (pArmatureObject.isNotNull()) { if (pArmatureObject.isNotNull()) {
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
Structure armatureObject = (Structure) dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(), ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
LoadedFeatureDataType.LOADED_STRUCTURE);
if (armatureObject == null) {// we check this first not to fetch the Structure armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
// structure unnecessary this.armatureObjectOMA = armatureObject.getOldMemoryAddress();
armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
objectHelper.toObject(armatureObject, dataRepository);
}
additionalData = armatureObject.getOldMemoryAddress();
ArmatureHelper armatureHelper = dataRepository
.getHelper(ArmatureHelper.class);
// changing bones matrices so that they fit the current object (that //read skeleton
// is why we need a copy of a skeleton) // changing bones matrices so that they fit the current object
Structure armatureStructure = ((Pointer)armatureObject.getFieldValue("data")).fetchData(dataRepository.getInputStream()).get(0);
Structure bonebase = (Structure) armatureStructure.getFieldValue("bonebase");
List<Structure> bonesStructures = bonebase.evaluateListBase(dataRepository);
for (Structure boneStructure : bonesStructures) {
BoneTransformationData rootBoneTransformationData = armatureHelper.readBoneAndItsChildren(boneStructure, null, dataRepository);
armatureHelper.addBoneDataRoot(rootBoneTransformationData);
}
Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject); Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject);
Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert(); Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert();
Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix); Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation); Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);
// setting the bones structure inside the skeleton (thus completing //read mesh indexes
// its loading) Structure meshStructure = ((Pointer)objectStructure.getFieldValue("data")).fetchData(dataRepository.getInputStream()).get(0);
Skeleton skeleton = new Skeleton(bones); this.meshOMA = meshStructure.getOldMemoryAddress();
dataRepository.addLoadedFeatures(armatureObject.getOldMemoryAddress(), armatureObject.getName(), armatureObject, skeleton); this.readVerticesWeightsData(objectStructure, meshStructure, dataRepository);
//read animations
String objectName = objectStructure.getName(); String objectName = objectStructure.getName();
Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName); Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
if (animationNames != null && animationNames.size() > 0) { if (animationNames != null && animationNames.size() > 0) {
@ -108,15 +130,27 @@ import com.jme3.scene.plugins.ogre.AnimData;
} }
@Override @Override
@SuppressWarnings("unchecked")
public Node apply(Node node, DataRepository dataRepository) { public Node apply(Node node, DataRepository dataRepository) {
if(jmeModifierRepresentation == null) { if(jmeModifierRepresentation == null) {
return node; return node;
} }
// setting weights for bones
List<Geometry> geomList = (List<Geometry>) dataRepository.getLoadedFeature(this.meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
for(Geometry geom : geomList) {
Mesh mesh = geom.getMesh();
if (this.verticesWeights != null) {
mesh.setMaxNumWeights(this.boneGroups);
mesh.setBuffer(this.verticesWeights);
mesh.setBuffer(this.verticesWeightsIndices);
}
}
AnimData ad = (AnimData) jmeModifierRepresentation; AnimData ad = (AnimData) jmeModifierRepresentation;
ArrayList<Animation> animList = ad.anims; ArrayList<Animation> animList = ad.anims;
Long modifierArmatureObject = (Long) additionalData;
if (animList != null && animList.size() > 0) { if (animList != null && animList.size() > 0) {
List<Constraint> constraints = dataRepository.getConstraints(modifierArmatureObject); List<Constraint> constraints = dataRepository.getConstraints(this.armatureObjectOMA);
HashMap<String, Animation> anims = new HashMap<String, Animation>(); HashMap<String, Animation> anims = new HashMap<String, Animation>();
for (int i = 0; i < animList.size(); ++i) { for (int i = 0; i < animList.size(); ++i) {
BoneAnimation boneAnimation = (BoneAnimation) animList.get(i).clone(); BoneAnimation boneAnimation = (BoneAnimation) animList.get(i).clone();
@ -146,39 +180,8 @@ import com.jme3.scene.plugins.ogre.AnimData;
// applying the control to the node // applying the control to the node
SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton); SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton);
AnimControl control = node.getControl(AnimControl.class); AnimControl control = new AnimControl(ad.skeleton);
if (control == null) {
control = new AnimControl(ad.skeleton);
} else {
// merging skeletons
Skeleton controlSkeleton = control.getSkeleton();
int boneIndexIncrease = controlSkeleton.getBoneCount();
Skeleton skeleton = this.merge(controlSkeleton, ad.skeleton);
// merging animations
HashMap<String, Animation> animations = new HashMap<String, Animation>();
for (String animationName : control.getAnimationNames()) {
animations.put(animationName,
control.getAnim(animationName));
}
for (Entry<String, Animation> animEntry : anims.entrySet()) {
BoneAnimation ba = (BoneAnimation) animEntry.getValue();
for (int i = 0; i < ba.getTracks().length; ++i) {
BoneTrack bt = ba.getTracks()[i];
int newBoneIndex = bt.getTargetBoneIndex()
+ boneIndexIncrease;
ba.getTracks()[i] = new BoneTrack(newBoneIndex,
bt.getTimes(), bt.getTranslations(),
bt.getRotations(), bt.getScales());
}
animations.put(animEntry.getKey(), animEntry.getValue());
}
// replacing the control
node.removeControl(control);
control = new AnimControl(skeleton);
}
control.setAnimations(anims); control.setAnimations(anims);
node.addControl(control); node.addControl(control);
node.addControl(skeletonControl); node.addControl(skeletonControl);
@ -186,32 +189,157 @@ import com.jme3.scene.plugins.ogre.AnimData;
return node; return node;
} }
@Override /**
public String getType() { * This method reads mesh indexes
return Modifier.ARMATURE_MODIFIER_DATA; * @param objectStructure structure of the object that has the armature modifier applied
* @param meshStructure the structure of the object's mesh
* @param dataRepository the data repository
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
private void readVerticesWeightsData(Structure objectStructure, Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
Structure defBase = (Structure) objectStructure.getFieldValue("defbase");
Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository);
int[] bonesGroups = new int[] { 0 };
VertexData vertexData = dataRepository.getVertexData(meshStructure.getOldMemoryAddress());
VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(meshStructure, vertexData.getVertexList().size(), bonesGroups,
vertexData.getVertexReferenceMap(), groupToBoneIndexMap, dataRepository);
this.verticesWeights = boneWeightsAndIndex[0];
this.verticesWeightsIndices = boneWeightsAndIndex[1];
this.boneGroups = bonesGroups[0];
} }
/** /**
* This method merges two skeletons into one. I assume that each skeleton's * This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The
* 0-indexed bone is objectAnimationBone so only one such bone should be * second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).
* placed in the result
* *
* @param s1 * @param meshStructure
* first skeleton * the mesh structure object
* @param s2 * @param vertexListSize
* second skeleton * a number of vertices in the model
* @return merged skeleton * @param bonesGroups
* this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to
* MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
* @param vertexReferenceMap
* this reference map allows to map the original vertices read from blender to vertices that are really in the model; one
* vertex may appear several times in the result model
* @param groupToBoneIndexMap
* this object maps the group index (to which a vertices in blender belong) to bone index of the model
* @param dataRepository
* the data repository
* @return arrays of vertices weights and their bone indices and (as an output parameter) the maximum amount of weights for a vertex
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/ */
protected Skeleton merge(Skeleton s1, Skeleton s2) { private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups,
List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount() Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository)
+ s2.getBoneCount()); throws BlenderFileException {
for (int i = 0; i < s1.getBoneCount(); ++i) { Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
bones.add(s1.getBone(i)); FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per
// vertex in blender)
int vertexIndex = 0;
for (Structure dvert : dverts) {
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
// (max. 4 in JME)
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :)
int weightIndex = 0;
List<Structure> dw = pDW.fetchData(dataRepository.getInputStream());
for (Structure deformWeight : dw) {
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
if (boneIndex != null) {// null here means that we came accross group that has no bone attached to
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
if (weight == 0.0f) {
weight = 1;
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());
}
}
++weightIndex;
}
} else {
for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
}
++vertexIndex;
}
} 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);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
}
} }
for (int i = 1; i < s2.getBoneCount(); ++i) {// ommit
// objectAnimationBone bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);
bones.add(s2.getBone(i)); VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
}
/**
* Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer.
* @param vertCount amount of vertices
* @param weightsFloatData weights for vertices
*/
private int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
int maxWeightsPerVert = 0;
weightsFloatData.rewind();
for (int v = 0; v < vertCount; ++v) {
float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();
if (w3 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
} else if (w2 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
} else if (w1 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
} else if (w0 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
}
float sum = w0 + w1 + w2 + w3;
if (sum != 1f && sum != 0.0f) {
weightsFloatData.position(weightsFloatData.position() - 4);
// compute new vals based on sum
float sumToB = 1f / sum;
weightsFloatData.put(w0 * sumToB);
weightsFloatData.put(w1 * sumToB);
weightsFloatData.put(w2 * sumToB);
weightsFloatData.put(w3 * sumToB);
}
} }
return new Skeleton(bones.toArray(new Bone[bones.size()])); weightsFloatData.rewind();
// mesh.setMaxNumWeights(maxWeightsPerVert);
return maxWeightsPerVert;
}
@Override
public String getType() {
return Modifier.ARMATURE_MODIFIER_DATA;
} }
} }

@ -55,7 +55,6 @@ import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.DataRepository; import com.jme3.scene.plugins.blender.DataRepository;
import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
import com.jme3.scene.plugins.blender.cameras.CameraHelper; import com.jme3.scene.plugins.blender.cameras.CameraHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.curves.CurvesHelper; import com.jme3.scene.plugins.blender.curves.CurvesHelper;
@ -141,9 +140,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
} }
dataRepository.pushParent(objectStructure); dataRepository.pushParent(objectStructure);
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
//get object data //get object data
int type = ((Number)objectStructure.getFieldValue("type")).intValue(); int type = ((Number)objectStructure.getFieldValue("type")).intValue();
@ -161,7 +158,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
Pointer pParent = (Pointer)objectStructure.getFieldValue("parent"); Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
Object parent = dataRepository.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); Object parent = dataRepository.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if(parent == null && pParent.isNotNull()) { if(parent == null && pParent.isNotNull()) {
Structure parentStructure = pParent.fetchData(dataRepository.getInputStream()).get(0);//TODO: moze byc wiecej rodzicow Structure parentStructure = pParent.fetchData(dataRepository.getInputStream()).get(0);//TODO: what if there are more parents ??
parent = this.toObject(parentStructure, dataRepository); parent = this.toObject(parentStructure, dataRepository);
} }
@ -263,10 +260,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
} }
break; break;
case OBJECT_TYPE_ARMATURE: case OBJECT_TYPE_ARMATURE:
LOGGER.log(Level.INFO, "Importing armature."); //Do not do anything, the object with all needed data is loaded when armature modifier loads
Pointer pArmature = (Pointer)objectStructure.getFieldValue("data");
List<Structure> armaturesArray = pArmature.fetchData(dataRepository.getInputStream());//TODO: moze byc wiecej???
result = armatureHelper.toArmature(armaturesArray.get(0), dataRepository);
break; break;
default: default:
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
@ -275,13 +269,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
dataRepository.popParent(); dataRepository.popParent();
} }
//reading custom properties
Properties properties = this.loadProperties(objectStructure, dataRepository);
if(result instanceof Spatial && properties != null && properties.getValue() != null) {
((Spatial)result).setUserData("properties", properties);
}
if(result != null) { if(result != null) {
//reading custom properties
Properties properties = this.loadProperties(objectStructure, dataRepository);
if(result instanceof Spatial && properties != null && properties.getValue() != null) {
((Spatial)result).setUserData("properties", properties);
}
dataRepository.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); dataRepository.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
} }
return result; return result;

Loading…
Cancel
Save