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. 190
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  4. 278
      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.Structure;
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;
/**
@ -89,6 +90,8 @@ public class DataRepository {
protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>();
/** A list of constraints for the specified object. */
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. */
protected Map<Material, MaterialContext> materialContexts = new HashMap<Material, MaterialContext>();
/** A map og helpers that perform loading. */
@ -398,6 +401,29 @@ public class DataRepository {
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.
* If the context is already set it will be replaced.

@ -31,7 +31,6 @@
*/
package com.jme3.scene.plugins.blender.animations;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -41,12 +40,8 @@ import java.util.logging.Logger;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
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.DataRepository;
import com.jme3.scene.plugins.blender.curves.BezierCurve;
@ -100,27 +95,6 @@ public class ArmatureHelper extends AbstractBlenderHelper {
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
* bone index in the armature.
@ -135,7 +109,7 @@ public class ArmatureHelper extends AbstractBlenderHelper {
if (bonesMap != null && bonesMap.size() != 0) {
result = new HashMap<Integer, Integer>();
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) {
String deformGroupName = deformGroup.getFieldValue("name").toString();
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
*/
@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();
Bone bone = new Bone(name);
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.
* @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.
* @author Marcin Roguski
*/
private static class BoneTransformationData {
public static class BoneTransformationData {
/** Inverse matrix of bone's parent bone. */
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
* @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);
bones.add(new Bone(""));
for (BoneTransformationData btd : boneDataRoots) {
@ -308,25 +286,6 @@ public class ArmatureHelper extends AbstractBlenderHelper {
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
public void clearState() {
bonesMap.clear();

@ -31,7 +31,6 @@
*/
package com.jme3.scene.plugins.blender.meshes;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
@ -41,9 +40,6 @@ import java.util.Map;
import java.util.Map.Entry;
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.math.FastMath;
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.DataRepository;
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.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
@ -77,8 +72,6 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas)
*/
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
* 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()]);
// reading vertices groups (from the parent)
@ -262,18 +257,6 @@ public class MeshHelper extends AbstractBlenderHelper {
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
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
Material[] materials = null;
@ -333,13 +316,6 @@ public class MeshHelper extends AbstractBlenderHelper {
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
mesh.setBuffer(normalsBuffer);
mesh.setBuffer(normalsBind);
@ -418,22 +394,6 @@ public class MeshHelper extends AbstractBlenderHelper {
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.
*
@ -535,136 +495,26 @@ public class MeshHelper extends AbstractBlenderHelper {
return vertices;
}
/**
* 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
* second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).
*
* @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 };
}
/**
* Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer.
*/
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;
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);
}
}
weightsFloatData.rewind();
// mesh.setMaxNumWeights(maxWeightsPerVert);
return maxWeightsPerVert;
}
@Override
public boolean shouldBeLoaded(Structure structure, DataRepository dataRepository) {
return true;
}
public static class VertexData {
private List<Vector3f> vertexList;
private Map<Integer, List<Integer>> vertexReferenceMap;
public VertexData(List<Vector3f> vertexList, Map<Integer, List<Integer>> vertexReferenceMap) {
this.vertexList = vertexList;
this.vertexReferenceMap = vertexReferenceMap;
}
public List<Vector3f> getVertexList() {
return vertexList;
}
public Map<Integer, List<Integer>> getVertexReferenceMap() {
return vertexReferenceMap;
}
}
}

@ -1,16 +1,17 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneAnimation;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.math.Matrix4f;
@ -18,16 +19,23 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
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.LoadedFeatureDataType;
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.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Pointer;
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.ogre.AnimData;
import com.jme3.util.BufferUtils;
/**
* This modifier allows to add bone animation to the object.
@ -35,7 +43,19 @@ import com.jme3.scene.plugins.ogre.AnimData;
* @author Marcin Roguski (Kaelthas)
*/
/* 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
* animation is implemented in jme. TODO!!!!!!!
@ -61,29 +81,31 @@ import com.jme3.scene.plugins.ogre.AnimData;
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
if (pArmatureObject.isNotNull()) {
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
Structure armatureObject = (Structure) dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(),
LoadedFeatureDataType.LOADED_STRUCTURE);
if (armatureObject == null) {// we check this first not to fetch the
// structure unnecessary
armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
objectHelper.toObject(armatureObject, dataRepository);
}
additionalData = armatureObject.getOldMemoryAddress();
ArmatureHelper armatureHelper = dataRepository
.getHelper(ArmatureHelper.class);
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
Structure armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
this.armatureObjectOMA = armatureObject.getOldMemoryAddress();
// changing bones matrices so that they fit the current object (that
// is why we need a copy of a skeleton)
//read 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 inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert();
Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);
// setting the bones structure inside the skeleton (thus completing
// its loading)
Skeleton skeleton = new Skeleton(bones);
dataRepository.addLoadedFeatures(armatureObject.getOldMemoryAddress(), armatureObject.getName(), armatureObject, skeleton);
//read mesh indexes
Structure meshStructure = ((Pointer)objectStructure.getFieldValue("data")).fetchData(dataRepository.getInputStream()).get(0);
this.meshOMA = meshStructure.getOldMemoryAddress();
this.readVerticesWeightsData(objectStructure, meshStructure, dataRepository);
//read animations
String objectName = objectStructure.getName();
Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
if (animationNames != null && animationNames.size() > 0) {
@ -106,17 +128,29 @@ import com.jme3.scene.plugins.ogre.AnimData;
}
}
}
@Override
@SuppressWarnings("unchecked")
public Node apply(Node node, DataRepository dataRepository) {
if(jmeModifierRepresentation == null) {
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;
ArrayList<Animation> animList = ad.anims;
Long modifierArmatureObject = (Long) additionalData;
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>();
for (int i = 0; i < animList.size(); ++i) {
BoneAnimation boneAnimation = (BoneAnimation) animList.get(i).clone();
@ -146,72 +180,166 @@ import com.jme3.scene.plugins.ogre.AnimData;
// applying the control to the node
SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton);
AnimControl control = node.getControl(AnimControl.class);
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());
}
AnimControl control = new AnimControl(ad.skeleton);
// replacing the control
node.removeControl(control);
control = new AnimControl(skeleton);
}
control.setAnimations(anims);
node.addControl(control);
node.addControl(skeletonControl);
}
return node;
}
/**
* This method reads mesh indexes
* @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);
@Override
public String getType() {
return Modifier.ARMATURE_MODIFIER_DATA;
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
* 0-indexed bone is objectAnimationBone so only one such bone should be
* placed in the result
* 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
* second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).
*
* @param s1
* first skeleton
* @param s2
* second skeleton
* @return merged skeleton
* @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 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) {
List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount()
+ s2.getBoneCount());
for (int i = 0; i < s1.getBoneCount(); ++i) {
bones.add(s1.getBone(i));
private 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) {
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
bones.add(s2.getBone(i));
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 };
}
/**
* 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.DataRepository;
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.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
@ -141,9 +140,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
}
dataRepository.pushParent(objectStructure);
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
//get object data
int type = ((Number)objectStructure.getFieldValue("type")).intValue();
@ -161,7 +158,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
Object parent = dataRepository.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
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);
}
@ -263,10 +260,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
}
break;
case OBJECT_TYPE_ARMATURE:
LOGGER.log(Level.INFO, "Importing armature.");
Pointer pArmature = (Pointer)objectStructure.getFieldValue("data");
List<Structure> armaturesArray = pArmature.fetchData(dataRepository.getInputStream());//TODO: moze byc wiecej???
result = armatureHelper.toArmature(armaturesArray.get(0), dataRepository);
//Do not do anything, the object with all needed data is loaded when armature modifier loads
break;
default:
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
@ -275,13 +269,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
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) {
//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);
}
return result;

Loading…
Cancel
Save