1 package com.jme3.scene.plugins.blender.modifiers; 2 3 import java.nio.ByteBuffer; 4 import java.nio.FloatBuffer; 5 import java.util.ArrayList; 6 import java.util.HashMap; 7 import java.util.List; 8 import java.util.Map; 9 import java.util.logging.Level; 10 import java.util.logging.Logger; 11 12 import com.jme3.animation.AnimControl; 13 import com.jme3.animation.Animation; 14 import com.jme3.animation.Bone; 15 import com.jme3.animation.BoneTrack; 16 import com.jme3.animation.Skeleton; 17 import com.jme3.animation.SkeletonControl; 18 import com.jme3.math.Matrix4f; 19 import com.jme3.scene.Geometry; 20 import com.jme3.scene.Mesh; 21 import com.jme3.scene.Node; 22 import com.jme3.scene.VertexBuffer; 23 import com.jme3.scene.VertexBuffer.Format; 24 import com.jme3.scene.VertexBuffer.Type; 25 import com.jme3.scene.VertexBuffer.Usage; 26 import com.jme3.scene.plugins.blender.BlenderContext; 27 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; 28 import com.jme3.scene.plugins.blender.animations.ArmatureHelper; 29 import com.jme3.scene.plugins.blender.constraints.Constraint; 30 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 31 import com.jme3.scene.plugins.blender.file.FileBlockHeader; 32 import com.jme3.scene.plugins.blender.file.Pointer; 33 import com.jme3.scene.plugins.blender.file.Structure; 34 import com.jme3.scene.plugins.blender.meshes.MeshContext; 35 import com.jme3.scene.plugins.blender.objects.ObjectHelper; 36 import com.jme3.scene.plugins.ogre.AnimData; 37 import com.jme3.util.BufferUtils; 38 39 /** 40 * This modifier allows to add bone animation to the object. 41 * 42 * @author Marcin Roguski (Kaelthas) 43 */ 44 /* package */class ArmatureModifier extends Modifier { 45 private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); 46 private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; 47 // @Marcin it was an Ogre limitation, but as long as we use a MaxNumWeight 48 // variable in mesh, 49 // i guess this limitation has no sense for the blender loader...so i guess 50 // it's up to you. You'll have to deternine the max weight according to the 51 // provided blend file 52 // I added a check to avoid crash when loading a model that has more than 4 53 // weight per vertex on line 258 54 // If you decide to remove this limitation, remove this code. 55 // Rmy 56 57 /** Loaded animation data. */ 58 private AnimData animData; 59 /** Old memory address of the mesh that will have the skeleton applied. */ 60 private Long meshOMA; 61 /** 62 * The maxiumum amount of bone groups applied to a single vertex (max = 63 * MAXIMUM_WEIGHTS_PER_VERTEX). 64 */ 65 private int boneGroups; 66 /** The weights of vertices. */ 67 private VertexBuffer verticesWeights; 68 /** The indexes of bones applied to vertices. */ 69 private VertexBuffer verticesWeightsIndices; 70 71 /** 72 * This constructor reads animation data from the object structore. The 73 * stored data is the AnimData and additional data is armature's OMA. 74 * 75 * @param objectStructure 76 * the structure of the object 77 * @param modifierStructure 78 * the structure of the modifier 79 * @param blenderContext 80 * the blender context 81 * @throws BlenderFileException 82 * this exception is thrown when the blender file is somehow 83 * corrupted 84 */ 85 public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { 86 Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); 87 Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert 88 // = 89 // DeformVERTices 90 91 // if pDvert==null then there are not vertex groups and no need to load 92 // skeleton (untill bone envelopes are supported) 93 if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) { 94 Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); 95 if (pArmatureObject.isNotNull()) { 96 ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); 97 98 Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0); 99 100 // load skeleton 101 Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); 102 103 Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0); 104 List<Structure> chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext); 105 106 Map<Long, Structure> bonesPoseChannels = new HashMap<Long, Structure>(chanbase.size()); 107 for (Structure poseChannel : chanbase) { 108 Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); 109 bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel); 110 } 111 112 ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); 113 Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true); 114 Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal(); 115 Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix); 116 117 List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext); 118 List<Bone> bonesList = new ArrayList<Bone>(); 119 for (int i = 0; i < bonebase.size(); ++i) { 120 armatureHelper.buildBones(bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext); 121 } 122 bonesList.add(0, new Bone("")); 123 Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); 124 Skeleton skeleton = new Skeleton(bones); 125 126 // read mesh indexes 127 this.meshOMA = meshStructure.getOldMemoryAddress(); 128 this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, blenderContext); 129 130 // read animations 131 ArrayList<Animation> animations = new ArrayList<Animation>(); 132 List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); 133 if (actionHeaders != null) {// it may happen that the model has 134 // armature with no actions 135 for (FileBlockHeader header : actionHeaders) { 136 Structure actionStructure = header.getStructure(blenderContext); 137 String actionName = actionStructure.getName(); 138 139 BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); 140 if(tracks != null && tracks.length > 0) { 141 // determining the animation time 142 float maximumTrackLength = 0; 143 for (BoneTrack track : tracks) { 144 float length = track.getLength(); 145 if (length > maximumTrackLength) { 146 maximumTrackLength = length; 147 } 148 } 149 150 Animation boneAnimation = new Animation(actionName, maximumTrackLength); 151 boneAnimation.setTracks(tracks); 152 animations.add(boneAnimation); 153 } 154 } 155 } 156 animData = new AnimData(skeleton, animations); 157 158 // store the animation data for each bone 159 for (Bone bone : bones) { 160 Long boneOma = armatureHelper.getBoneOMA(bone); 161 if (boneOma != null) { 162 blenderContext.setAnimData(boneOma, animData); 163 } 164 } 165 } 166 } 167 } 168 169 @Override 170 @SuppressWarnings("unchecked") 171 public Node apply(Node node, BlenderContext blenderContext) { 172 if (invalid) { 173 LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); 174 }// if invalid, animData will be null 175 if (animData == null) { 176 return node; 177 } 178 179 // setting weights for bones 180 List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(this.meshOMA, LoadedFeatureDataType.LOADED_FEATURE); 181 for (Geometry geom : geomList) { 182 Mesh mesh = geom.getMesh(); 183 if (this.verticesWeights != null) { 184 mesh.setMaxNumWeights(this.boneGroups); 185 mesh.setBuffer(this.verticesWeights); 186 mesh.setBuffer(this.verticesWeightsIndices); 187 } 188 } 189 190 // applying constraints to Bones 191 ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); 192 for (int i = 0; i < animData.skeleton.getBoneCount(); ++i) { 193 Long boneOMA = armatureHelper.getBoneOMA(animData.skeleton.getBone(i)); 194 List<Constraint> constraints = blenderContext.getConstraints(boneOMA); 195 if (constraints != null && constraints.size() > 0) { 196 for (Constraint constraint : constraints) { 197 constraint.bake(); 198 } 199 } 200 } 201 202 // applying animations 203 AnimControl control = new AnimControl(animData.skeleton); 204 ArrayList<Animation> animList = animData.anims; 205 if (animList != null && animList.size() > 0) { 206 HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size()); 207 for (int i = 0; i < animList.size(); ++i) { 208 Animation animation = animList.get(i); 209 anims.put(animation.getName(), animation); 210 } 211 control.setAnimations(anims); 212 } 213 node.addControl(control); 214 node.addControl(new SkeletonControl(animData.skeleton)); 215 216 return node; 217 } 218 219 /** 220 * This method reads mesh indexes 221 * 222 * @param objectStructure 223 * structure of the object that has the armature modifier applied 224 * @param meshStructure 225 * the structure of the object's mesh 226 * @param blenderContext 227 * the blender context 228 * @throws BlenderFileException 229 * this exception is thrown when the blend file structure is 230 * somehow invalid or corrupted 231 */ 232 private void readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { 233 ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); 234 Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); 235 Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext); 236 237 int[] bonesGroups = new int[] { 0 }; 238 MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); 239 240 VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexList().size(), bonesGroups, meshContext.getVertexReferenceMap(), groupToBoneIndexMap, blenderContext); 241 this.verticesWeights = boneWeightsAndIndex[0]; 242 this.verticesWeightsIndices = boneWeightsAndIndex[1]; 243 this.boneGroups = bonesGroups[0]; 244 } 245 246 /** 247 * This method returns an array of size 2. The first element is a vertex 248 * buffer holding bone weights for every vertex in the model. The second 249 * element is a vertex buffer holding bone indices for vertices (the indices 250 * of bones the vertices are assigned to). 251 * 252 * @param meshStructure 253 * the mesh structure object 254 * @param vertexListSize 255 * a number of vertices in the model 256 * @param bonesGroups 257 * this is an output parameter, it should be a one-sized array; 258 * the maximum amount of weights per vertex (up to 259 * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there 260 * @param vertexReferenceMap 261 * this reference map allows to map the original vertices read 262 * from blender to vertices that are really in the model; one 263 * vertex may appear several times in the result model 264 * @param groupToBoneIndexMap 265 * this object maps the group index (to which a vertices in 266 * blender belong) to bone index of the model 267 * @param blenderContext 268 * the blender context 269 * @return arrays of vertices weights and their bone indices and (as an 270 * output parameter) the maximum amount of weights for a vertex 271 * @throws BlenderFileException 272 * this exception is thrown when the blend file structure is 273 * somehow invalid or corrupted 274 */ 275 private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) 276 throws BlenderFileException { 277 Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices 278 FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); 279 ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); 280 if (pDvert.isNotNull()) {// assigning weights and bone indices 281 List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per 282 // vertex in blender) 283 int vertexIndex = 0; 284 for (Structure dvert : dverts) { 285 int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex 286 // (max. 4 in JME) 287 Pointer pDW = (Pointer) dvert.getFieldValue("dw"); 288 List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here 289 if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :) 290 int weightIndex = 0; 291 List<Structure> dw = pDW.fetchData(blenderContext.getInputStream()); 292 for (Structure deformWeight : dw) { 293 Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); 294 295 // Remove this code if 4 weights limitation is removed 296 if (weightIndex == 4) { 297 LOGGER.log(Level.WARNING, "{0} has more than 4 weight on bone index {1}", new Object[] { meshStructure.getName(), boneIndex }); 298 break; 299 } 300 301 // null here means that we came accross group that has no bone attached to 302 if (boneIndex != null) { 303 float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); 304 if (weight == 0.0f) { 305 weight = 1; 306 boneIndex = Integer.valueOf(0); 307 } 308 // we apply the weight to all referenced vertices 309 for (Integer index : vertexIndices) { 310 weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); 311 indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); 312 } 313 } 314 ++weightIndex; 315 } 316 } else { 317 for (Integer index : vertexIndices) { 318 weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); 319 indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); 320 } 321 } 322 ++vertexIndex; 323 } 324 } else { 325 // always bind all vertices to 0-indexed bone 326 // this bone makes the model look normally if vertices have no bone 327 // assigned 328 // and it is used in object animation, so if we come accross object 329 // animation 330 // we can use the 0-indexed bone for this 331 for (List<Integer> vertexIndexList : vertexReferenceMap.values()) { 332 // we apply the weight to all referenced vertices 333 for (Integer index : vertexIndexList) { 334 weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); 335 indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); 336 } 337 } 338 } 339 340 bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData); 341 VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); 342 verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); 343 344 VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); 345 verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); 346 return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; 347 } 348 349 /** 350 * Normalizes weights if needed and finds largest amount of weights used for 351 * all vertices in the buffer. 352 * 353 * @param vertCount 354 * amount of vertices 355 * @param weightsFloatData 356 * weights for vertices 357 */ 358 private int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { 359 int maxWeightsPerVert = 0; 360 weightsFloatData.rewind(); 361 for (int v = 0; v < vertCount; ++v) { 362 float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get(); 363 364 if (w3 != 0) { 365 maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); 366 } else if (w2 != 0) { 367 maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); 368 } else if (w1 != 0) { 369 maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); 370 } else if (w0 != 0) { 371 maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); 372 } 373 374 float sum = w0 + w1 + w2 + w3; 375 if (sum != 1f && sum != 0.0f) { 376 weightsFloatData.position(weightsFloatData.position() - 4); 377 // compute new vals based on sum 378 float sumToB = 1f / sum; 379 weightsFloatData.put(w0 * sumToB); 380 weightsFloatData.put(w1 * sumToB); 381 weightsFloatData.put(w2 * sumToB); 382 weightsFloatData.put(w3 * sumToB); 383 } 384 } 385 weightsFloatData.rewind(); 386 return maxWeightsPerVert; 387 } 388 389 @Override 390 public String getType() { 391 return Modifier.ARMATURE_MODIFIER_DATA; 392 } 393 } 394