diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index d435df8f8..637a5a174 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -560,7 +560,7 @@ public class BlenderContext { */ public BoneContext getBoneContext(Bone bone) { for (Entry entry : boneContexts.entrySet()) { - if (entry.getValue().getBone().equals(bone)) { + if (entry.getValue().getBone().getName().equals(bone.getName())) { return entry.getValue(); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java index c398f7730..8cd8dba67 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -23,7 +23,8 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; */ public class BoneContext { // 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). @@ -54,6 +55,8 @@ public class BoneContext { private Bone bone; /** The length of the bone. */ private float length; + /** The bone's deform envelope. */ + private BoneEnvelope boneEnvelope; /** * 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 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 globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); } @@ -111,6 +114,11 @@ public class BoneContext { // and now compute the final bone matrix in world space 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 List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(); for (Structure child : childbase) { @@ -209,7 +217,7 @@ public class BoneContext { public Skeleton getSkeleton() { return blenderContext.getSkeleton(armatureObjectOMA); } - + /** * @return the initial bone's matrix in model space */ @@ -217,6 +225,13 @@ public class BoneContext { 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. * @param flagMask diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java new file mode 100644 index 000000000..83e708dc1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java @@ -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 headArray = (DynamicArray) 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 tailArray = (DynamicArray) 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 true if the point is inside the envelope and false 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 + ")]"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java index 45742d78b..5bdd23b6b 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java @@ -2,9 +2,12 @@ package com.jme3.scene.plugins.blender.meshes; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; import com.jme3.scene.Geometry; @@ -14,10 +17,17 @@ import com.jme3.scene.Geometry; * @author Marcin Roguski (Kaelthas) */ public class MeshContext { + private static final Logger LOGGER = Logger.getLogger(MeshContext.class.getName()); + /** A map between material index and the geometry. */ - private Map> geometries = new HashMap>(); + private Map> geometries = new HashMap>(); /** The vertex reference map. */ private Map>> 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 vertexGroups = new LinkedHashMap(); /** * Adds a geometry for the specified material index. @@ -85,4 +95,91 @@ public class MeshContext { public void setVertexReferenceMap(Map>> 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 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 { + 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; + } + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 3d10c1ecc..d9bdd9a55 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -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. * - * @param structure + * @param meshStructure * the structure we read the mesh from * @return the mesh feature * @throws BlenderFileException */ @SuppressWarnings("unchecked") - public List toMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - List geometries = (List) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + public List toMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + List geometries = (List) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); if (geometries != null) { List copiedGeometries = new ArrayList(geometries.size()); for (Geometry geometry : geometries) { @@ -110,7 +110,7 @@ public class MeshHelper extends AbstractBlenderHelper { return copiedGeometries; } - String name = structure.getName(); + String name = meshStructure.getName(); MeshContext meshContext = new MeshContext(); LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); @@ -118,33 +118,22 @@ public class MeshHelper extends AbstractBlenderHelper { MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialContext[] materials = null; if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { - materials = materialHelper.getMaterials(structure, blenderContext); + materials = materialHelper.getMaterials(meshStructure, blenderContext); } LOGGER.fine("Reading vertices."); - MeshBuilder meshBuilder = new MeshBuilder(structure, materials, blenderContext); + MeshBuilder meshBuilder = new MeshBuilder(meshStructure, materials, blenderContext); if (meshBuilder.isEmpty()) { LOGGER.fine("The geometry is empty."); geometries = new ArrayList(0); - blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); - blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); + blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries); + blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext); return geometries; } - meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap()); - LOGGER.fine("Reading vertices groups (from the Object structure)."); - Structure parent = blenderContext.peekParent(); - Structure defbase = (Structure) parent.getFieldValue("defbase"); - List 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."); - Properties properties = this.loadProperties(structure, blenderContext); + Properties properties = this.loadProperties(meshStructure, blenderContext); LOGGER.fine("Generating meshes."); Map> 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 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 dverts = pDvert.fetchData(); + int blenderVertexIndex = 0; + for (Structure dvert : dverts) { + Pointer pDW = (Pointer) dvert.getFieldValue("dw"); + if (pDW.isNotNull()) { + List 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>> vertexReferenceMap : meshBuilder.getVertexReferenceMap().entrySet()) {// iterate through the meshes [key is the material index] + for (Entry> 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 - blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); - blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); + blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries); + blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext); // apply materials only when all geometries are in place if (materials != null) { @@ -175,7 +200,7 @@ public class MeshHelper extends AbstractBlenderHelper { } else if (materials[materialNumber] != null) { LinkedHashMap> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber); MaterialContext materialContext = materials[materialNumber]; - materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext); + materialContext.applyMaterial(geometry, meshStructure.getOldMemoryAddress(), uvCoordinates, blenderContext); } else { 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."); @@ -203,7 +228,7 @@ public class MeshHelper extends AbstractBlenderHelper { geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext)); } else { Material defaultMaterial = blenderContext.getDefaultMaterial(); - if(geometry.getMesh().getBuffer(Type.Color) != null) { + if (geometry.getMesh().getBuffer(Type.Color) != null) { defaultMaterial = defaultMaterial.clone(); defaultMaterial.setBoolean("VertexColor", true); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index 731e55d40..610711afa 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -1,21 +1,26 @@ package com.jme3.scene.plugins.blender.modifiers; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; 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; @@ -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.animations.AnimationHelper; 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.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.meshes.MeshContext; +import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup; 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 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 Skeleton skeleton; - private Structure objectStructure; 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. */ 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 @@ -66,24 +81,35 @@ import com.jme3.util.BufferUtils; if (this.validate(modifierStructure, blenderContext)) { Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); if (pArmatureObject.isNotNull()) { - armatureObject = pArmatureObject.fetchData().get(0); - - // load skeleton - Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0); - List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); - List bonesList = new ArrayList(); - for (int i = 0; i < bonebase.size(); ++i) { - this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); + 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); + + // load skeleton + Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0); + List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); + List bonesList = new ArrayList(); + for (int i = 0; i < bonebase.size(); ++i) { + this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); + } + bonesList.add(0, new Bone("")); + Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); + skeleton = new Skeleton(bones); + blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); + this.meshStructure = meshStructure; + + // read mesh indexes + 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()); + } } - bonesList.add(0, new Bone("")); - Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); - skeleton = new Skeleton(bones); - blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); - this.objectStructure = objectStructure; - this.meshStructure = meshStructure; - - // read mesh indexes - meshOMA = meshStructure.getOldMemoryAddress(); } else { modifying = false; } @@ -114,35 +140,6 @@ import com.jme3.util.BufferUtils; 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 getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException { - Map result = null; - if (skeleton.getBoneCount() != 0) { - result = new HashMap(); - List 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 @SuppressWarnings("unchecked") public void apply(Node node, BlenderContext blenderContext) { @@ -153,39 +150,33 @@ import com.jme3.util.BufferUtils; // setting weights for bones List geomList = (List) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); MeshContext meshContext = blenderContext.getMeshContext(meshOMA); - int[] bonesGroups = new int[] { 0 }; for (Geometry geom : geomList) { int materialIndex = meshContext.getMaterialIndex(geom); Mesh mesh = geom.getMesh(); - try { - VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext); - if (buffers != null) { - mesh.setMaxNumWeights(bonesGroups[0]); - mesh.setBuffer(buffers[0]); - mesh.setBuffer(buffers[1]); - - LOGGER.fine("Generating bind pose and normal buffers."); - mesh.generateBindPose(true); - - // change the usage type of vertex and normal buffers from - // Static to Stream - mesh.getBuffer(Type.Position).setUsage(Usage.Stream); - mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); - - // creating empty buffers for HW skinning - // the buffers will be setup if ever used. - VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight); - VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex); - mesh.setBuffer(verticesWeightsHW); - mesh.setBuffer(verticesWeightsIndicesHW); - } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); - invalid = true; + MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext); + if (buffers != null) { + mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex); + mesh.setBuffer(buffers.verticesWeights); + mesh.setBuffer(buffers.verticesWeightsIndices); + + LOGGER.fine("Generating bind pose and normal buffers."); + mesh.generateBindPose(true); + + // change the usage type of vertex and normal buffers from + // Static to Stream + mesh.getBuffer(Type.Position).setUsage(Usage.Stream); + mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); + + // creating empty buffers for HW skinning + // the buffers will be setup if ever used. + VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight); + VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex); + mesh.setBuffer(verticesWeightsHW); + mesh.setBuffer(verticesWeightsIndicesHW); } } - + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName())); node.updateModelBound(); @@ -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 - * structure of the object that has the armature modifier applied - * @param meshStructure - * the structure of the object's mesh + * @param meshContext + * the mesh context + * @param skeleton + * the current skeleton + * @param materialIndex + * the material index + * @param mesh + * the mesh we create the buffers for * @param blenderContext * the blender context - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted + * @return an instance that aggregates all needed data for the mesh */ - private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { - Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); - Map groupToBoneIndexMap = this.getGroupToBoneIndexMap(defBase, skeleton); + private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) { + int vertexListSize = meshContext.getVertexCount(materialIndex); + Map> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex); - MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); + Map vertexGroups = new HashMap(); + 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); + } + } + } - /** - * 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 - * @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 - */ - private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap) throws BlenderFileException { - bonesGroups[0] = 0; - 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 (useBoneEnvelopes) { + LOGGER.fine("Attaching verts to bones using bone envelopes."); + Vector3f pos = new Vector3f(); - if (pDvert.isNotNull()) {// assigning weights and bone indices - boolean warnAboutTooManyVertexWeights = false; - // dverts.size() = verticesAmount (one dvert per vertex in blender) - List dverts = pDvert.fetchData(); - int vertexIndex = 0; - // use tree map to sort weights from the lowest to the highest ones - TreeMap weightToIndexMap = new TreeMap(); - - for (Structure dvert : dverts) { - // we fetch the referenced vertices here - List 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 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 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()); - } + for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone + Bone bone = skeleton.getBone(boneIndex); + BoneContext boneContext = blenderContext.getBoneContext(bone); + BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope(); + if (boneEnvelope != null) { + VertexGroup vertexGroup = vertexGroups.get(bone.getName()); + if (vertexGroup == null) { + vertexGroup = new VertexGroup(); + vertexGroups.put(bone.getName(), vertexGroup); + } + vertexGroup.setBoneIndex(boneIndex); + + for (Entry> entry : vertexReferenceMap.entrySet()) { + List vertexIndices = entry.getValue(); + for (int j = 0; j < indexes.limit(); ++j) { + int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j); + if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh + int ii = index * 3; + pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2)); + // move the vertex to the global space position + objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references + if (boneEnvelope.isInEnvelope(pos)) { + vertexGroup.addVertex(index, boneEnvelope.getWeight()); + } else if (boneIndex == 5) { + System.out.println("Się nie załapał: " + pos); } - ++weightIndex; } } - } else { - // 0.0 weight indicates, do not transform this vertex, - // 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; + } + } + + Map weights = new HashMap();// [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 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 entry : weights.entrySet()) { + maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size()); + entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX); } - if (warnAboutTooManyVertexWeights) { + 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 } - } 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 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); + } + + LOGGER.fine("Preparing buffers for the mesh."); + FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + for (int i = 0; i < indexes.limit(); ++i) { + int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i); + WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index); + if (weightsAndBoneIndexes != null) { + int count = 0; + for (Entry entry : weightsAndBoneIndexes.entrySet()) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue()); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue()); + ++count; } + } else { + // if no bone is assigned to this vertex then attach it to the 0-indexed root bone + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); + 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); - verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); + verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData); VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); - verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); - return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; + verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData); + + return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices); } /** - * Normalizes weights if needed and finds largest amount of weights used for - * all vertices in the buffer. + * A class that gathers the data for mesh bone buffers. + * Added to increase code readability. * - * @param vertCount - * amount of vertices - * @param weightsFloatData - * weights for vertices + * @author Marcin Roguski (Kaelthas) */ - private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { - weightsFloatData.rewind(); - float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX]; - for (int v = 0; v < vertCount; ++v) { + private static class MeshWeightsData { + public final int maximumWeightsPerVertex; + public final VertexBuffer verticesWeights; + public final VertexBuffer verticesWeightsIndices; + + public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) { + this.maximumWeightsPerVertex = maximumWeightsPerVertex; + this.verticesWeights = verticesWeights; + this.verticesWeightsIndices = verticesWeightsIndices; + } + } + + /** + * A map between the bone index and the bone's weight. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class WeightsAndBoneIndexes extends HashMap { + private static final long serialVersionUID = 2754299007299077459L; + + /** + * The method normalizes the weights and bone indexes data. + * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle. + * Next it normalizes the weights so that the sum of all verts is 1. + * @param maximumSize + * the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX) + */ + public void normalize(int maximumSize) { + if (this.size() > maximumSize) {// select only the most significant weights + float lowestWeight = Float.MAX_VALUE; + int lowestWeightIndex = -1; + HashMap msw = new HashMap(maximumSize);// msw = Most Significant Weight + for (Entry entry : this.entrySet()) { + if (msw.size() < maximumSize) { + msw.put(entry.getKey(), entry.getValue()); + if (entry.getValue() < lowestWeight) { + lowestWeight = entry.getValue(); + lowestWeightIndex = entry.getKey(); + } + } else if (entry.getValue() > lowestWeight) { + msw.remove(lowestWeightIndex); + msw.put(lowestWeightIndex, lowestWeight); + + // search again for the lowest weight + lowestWeight = Float.MAX_VALUE; + for (Entry e : msw.entrySet()) { + if (e.getValue() < lowestWeight) { + lowestWeight = e.getValue(); + lowestWeightIndex = e.getKey(); + } + } + } + } + + // replace current weights with the given ones + this.clear(); + this.putAll(msw); + } + + // normalizing the weights so that the sum of the values is equal to '1' float sum = 0; - for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { - weights[i] = weightsFloatData.get(); - sum += weights[i]; + for (Entry entry : this.entrySet()) { + sum += entry.getValue(); } - 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); + + if (sum != 0 && sum != 1) { + for (Entry entry : this.entrySet()) { + entry.setValue(entry.getValue() / sum); } } } - weightsFloatData.rewind(); } - -// This method is now not used because it broke animations. -// Perhaps in the future I will find a solution to this problem. -// I store it here for future use. -// -// private void loadBonePoses() { -// TempVars tempVars = TempVars.get(); -// try { -// Pointer pPose = (Pointer) armatureObject.getFieldValue("pose"); -// if (pPose.isNotNull()) { -// LOGGER.fine("Loading the pose of the armature."); -// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); -// ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); -// -// Structure pose = pPose.fetchData().get(0); -// Structure chanbase = (Structure) pose.getFieldValue("chanbase"); -// List chans = chanbase.evaluateListBase(); -// Transform transform = new Transform(); -// for (Structure poseChannel : chans) { -// Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); -// if (pBone.isNull()) { -// throw new BlenderFileException("Cannot find bone for pose channel named: " + poseChannel.getName()); -// } -// BoneContext boneContext = blenderContext.getBoneContext(pBone.getOldMemoryAddress()); -// -// LOGGER.log(Level.FINEST, "Getting the global pose transformation for bone: {0}", boneContext); -// Matrix4f poseMat = objectHelper.getMatrix(poseChannel, "pose_mat", blenderContext.getBlenderKey().isFixUpAxis()); -// poseMat.multLocal(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX); -// -// Matrix4f armatureWorldMat = objectHelper.getMatrix(armatureObject, "obmat", blenderContext.getBlenderKey().isFixUpAxis()); -// Matrix4f boneWorldMat = armatureWorldMat.multLocal(poseMat); -// -// boneWorldMat.toTranslationVector(tempVars.vect1); -// boneWorldMat.toRotationQuat(tempVars.quat1); -// boneWorldMat.toScaleVector(tempVars.vect2); -// transform.setTranslation(tempVars.vect1); -// transform.setRotation(tempVars.quat1); -// transform.setScale(tempVars.vect2); -// -// 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()); -// } finally { -// tempVars.release(); -// } -// } }