diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/DataRepository.java b/engine/src/blender/com/jme3/scene/plugins/blender/DataRepository.java index 8a63e3499..da627f14f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/DataRepository.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/DataRepository.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> modifiers = new HashMap>(); /** A list of constraints for the specified object. */ protected Map> constraints = new HashMap>(); + /** Vertex data for a mesh specified by OMA. */ + protected Map vertexData = new HashMap(); /** A map of material contexts. */ protected Map materialContexts = new HashMap(); /** 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. diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java index c30f0211e..0c3894655 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java @@ -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 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(); List 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 bones = new ArrayList(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(); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index fe3e0c34e..aa6f59d94 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -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 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> vertexReferenceMap, Map 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 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 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 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 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 vertexList; + private Map> vertexReferenceMap; + + public VertexData(List vertexList, Map> vertexReferenceMap) { + this.vertexList = vertexList; + this.vertexReferenceMap = vertexReferenceMap; + } + + public List getVertexList() { + return vertexList; + } + + public Map> getVertexReferenceMap() { + return vertexReferenceMap; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index 7819e541c..ff5adcd3e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -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 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 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 geomList = (List) 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 animList = ad.anims; - Long modifierArmatureObject = (Long) additionalData; if (animList != null && animList.size() > 0) { - List constraints = dataRepository.getConstraints(modifierArmatureObject); + List constraints = dataRepository.getConstraints(this.armatureObjectOMA); HashMap anims = new HashMap(); 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 animations = new HashMap(); - for (String animationName : control.getAnimationNames()) { - animations.put(animationName, - control.getAnim(animationName)); - } - for (Entry 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 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 bones = new ArrayList(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> vertexReferenceMap, Map 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 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 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 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 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; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index f5781e392..6d049c477 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -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 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;