OSDN Git Service

Hardware Skinning now uses its own bone index and bone weight buffers. The vertex...
[mikumikustudio/MikuMikuStudio.git] / engine / src / blender / com / jme3 / scene / plugins / blender / modifiers / ArmatureModifier.java
1 package com.jme3.scene.plugins.blender.modifiers;\r
2 \r
3 import java.nio.ByteBuffer;\r
4 import java.nio.FloatBuffer;\r
5 import java.util.ArrayList;\r
6 import java.util.HashMap;\r
7 import java.util.List;\r
8 import java.util.Map;\r
9 import java.util.Map.Entry;\r
10 import java.util.TreeMap;\r
11 import java.util.logging.Level;\r
12 import java.util.logging.Logger;\r
13 \r
14 import com.jme3.animation.AnimControl;\r
15 import com.jme3.animation.Animation;\r
16 import com.jme3.animation.Bone;\r
17 import com.jme3.animation.BoneTrack;\r
18 import com.jme3.animation.Skeleton;\r
19 import com.jme3.animation.SkeletonControl;\r
20 import com.jme3.math.Matrix4f;\r
21 import com.jme3.scene.Geometry;\r
22 import com.jme3.scene.Mesh;\r
23 import com.jme3.scene.Node;\r
24 import com.jme3.scene.VertexBuffer;\r
25 import com.jme3.scene.VertexBuffer.Format;\r
26 import com.jme3.scene.VertexBuffer.Type;\r
27 import com.jme3.scene.VertexBuffer.Usage;\r
28 import com.jme3.scene.plugins.blender.BlenderContext;\r
29 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;\r
30 import com.jme3.scene.plugins.blender.animations.AnimationData;\r
31 import com.jme3.scene.plugins.blender.animations.ArmatureHelper;\r
32 import com.jme3.scene.plugins.blender.animations.BoneContext;\r
33 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;\r
34 import com.jme3.scene.plugins.blender.file.FileBlockHeader;\r
35 import com.jme3.scene.plugins.blender.file.Pointer;\r
36 import com.jme3.scene.plugins.blender.file.Structure;\r
37 import com.jme3.scene.plugins.blender.meshes.MeshContext;\r
38 import com.jme3.scene.plugins.blender.objects.ObjectHelper;\r
39 import com.jme3.util.BufferUtils;\r
40 \r
41 /**\r
42  * This modifier allows to add bone animation to the object.\r
43  * \r
44  * @author Marcin Roguski (Kaelthas)\r
45  */\r
46 /* package */class ArmatureModifier extends Modifier {\r
47     private static final Logger LOGGER                     = Logger.getLogger(ArmatureModifier.class.getName());\r
48     private static final int    MAXIMUM_WEIGHTS_PER_VERTEX = 4;                                                 // JME\r
49                                                                                                                  // limitation\r
50 \r
51     private Skeleton            skeleton;\r
52     private Structure           objectStructure;\r
53     private Structure           meshStructure;\r
54 \r
55     /** Loaded animation data. */\r
56     private AnimationData       animationData;\r
57     /** Old memory address of the mesh that will have the skeleton applied. */\r
58     private Long                meshOMA;\r
59 \r
60     /**\r
61      * This constructor reads animation data from the object structore. The\r
62      * stored data is the AnimData and additional data is armature's OMA.\r
63      * \r
64      * @param objectStructure\r
65      *            the structure of the object\r
66      * @param modifierStructure\r
67      *            the structure of the modifier\r
68      * @param blenderContext\r
69      *            the blender context\r
70      * @throws BlenderFileException\r
71      *             this exception is thrown when the blender file is somehow\r
72      *             corrupted\r
73      */\r
74     public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {\r
75         Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);\r
76         Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices\r
77 \r
78         // if pDvert==null then there are not vertex groups and no need to load\r
79         // skeleton (untill bone envelopes are supported)\r
80         if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) {\r
81             Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");\r
82             if (pArmatureObject.isNotNull()) {\r
83                 ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);\r
84 \r
85                 Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0);\r
86 \r
87                 // load skeleton\r
88                 Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);\r
89 \r
90                 ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);\r
91                 boolean fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();\r
92                 Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", fixUpAxis);\r
93                 Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "imat", fixUpAxis);\r
94                 Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix);\r
95 \r
96                 List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext);\r
97                 List<Bone> bonesList = new ArrayList<Bone>();\r
98                 for (int i = 0; i < bonebase.size(); ++i) {\r
99                     armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, blenderContext);\r
100                 }\r
101                 bonesList.add(0, new Bone(""));\r
102                 Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);\r
103                 skeleton = new Skeleton(bones);\r
104                 blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);\r
105                 this.objectStructure = objectStructure;\r
106                 this.meshStructure = meshStructure;\r
107 \r
108                 // read mesh indexes\r
109                 this.meshOMA = meshStructure.getOldMemoryAddress();\r
110 \r
111                 // read animations\r
112                 ArrayList<Animation> animations = new ArrayList<Animation>();\r
113                 List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));\r
114                 if (actionHeaders != null) {// it may happen that the model has\r
115                                             // armature with no actions\r
116                     for (FileBlockHeader header : actionHeaders) {\r
117                         Structure actionStructure = header.getStructure(blenderContext);\r
118                         String actionName = actionStructure.getName();\r
119 \r
120                         BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);\r
121                         if (tracks != null && tracks.length > 0) {\r
122                             // determining the animation time\r
123                             float maximumTrackLength = 0;\r
124                             for (BoneTrack track : tracks) {\r
125                                 float length = track.getLength();\r
126                                 if (length > maximumTrackLength) {\r
127                                     maximumTrackLength = length;\r
128                                 }\r
129                             }\r
130 \r
131                             Animation boneAnimation = new Animation(actionName, maximumTrackLength);\r
132                             boneAnimation.setTracks(tracks);\r
133                             animations.add(boneAnimation);\r
134                         }\r
135                     }\r
136                 }\r
137                 // fetching action defined in object\r
138                 Pointer pAction = (Pointer) objectStructure.getFieldValue("action");\r
139                 if (pAction.isNotNull()) {\r
140                     Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);\r
141                     String actionName = actionStructure.getName();\r
142 \r
143                     BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);\r
144                     if (tracks != null && tracks.length > 0) {\r
145                         // determining the animation time\r
146                         float maximumTrackLength = 0;\r
147                         for (BoneTrack track : tracks) {\r
148                             float length = track.getLength();\r
149                             if (length > maximumTrackLength) {\r
150                                 maximumTrackLength = length;\r
151                             }\r
152                         }\r
153 \r
154                         Animation boneAnimation = new Animation(actionName, maximumTrackLength);\r
155                         boneAnimation.setTracks(tracks);\r
156                         animations.add(boneAnimation);\r
157                     }\r
158                 }\r
159 \r
160                 animationData = new AnimationData(skeleton, animations);\r
161 \r
162                 // store the animation data for each bone\r
163                 for (Bone bone : bones) {\r
164                     if(bone.getName().length() > 0) {\r
165                         BoneContext boneContext = blenderContext.getBoneContext(bone);\r
166                         Long boneOma = boneContext.getBoneOma();\r
167                         if (boneOma != null) {\r
168                             blenderContext.setAnimData(boneOma, animationData);\r
169                         }\r
170                     }\r
171                 }\r
172             } else {\r
173                 modifying = false;\r
174             }\r
175         }\r
176     }\r
177 \r
178     @Override\r
179     @SuppressWarnings("unchecked")\r
180     public Node apply(Node node, BlenderContext blenderContext) {\r
181         if (invalid) {\r
182             LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());\r
183         }// if invalid, animData will be null\r
184         if (animationData == null || skeleton == null) {\r
185             return node;\r
186         }\r
187 \r
188         // setting weights for bones\r
189         List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);\r
190         MeshContext meshContext = blenderContext.getMeshContext(meshOMA);\r
191         int[] bonesGroups = new int[] { 0 };\r
192         for (Geometry geom : geomList) {\r
193             int materialIndex = meshContext.getMaterialIndex(geom);\r
194             Mesh mesh = geom.getMesh();\r
195 \r
196             try {\r
197                 VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext);\r
198                 if (buffers != null) {\r
199                     mesh.setMaxNumWeights(bonesGroups[0]);\r
200                     mesh.setBuffer(buffers[0]);\r
201                     mesh.setBuffer(buffers[1]);\r
202 \r
203                     //FIXME @Kaelthas this should be replaced by a call to \r
204                     //mesh.generateBindPos(true)\r
205                     VertexBuffer bindNormalBuffer = meshContext.getBindNormalBuffer(materialIndex);\r
206                     if (bindNormalBuffer != null) {\r
207                         mesh.setBuffer(bindNormalBuffer);\r
208                     }\r
209                     VertexBuffer bindPoseBuffer = meshContext.getBindPoseBuffer(materialIndex);\r
210                     if (bindPoseBuffer != null) {\r
211                         mesh.setBuffer(bindPoseBuffer);\r
212                     }\r
213                     // change the usage type of vertex and normal buffers from\r
214                     // Static to Stream\r
215                     mesh.getBuffer(Type.Position).setUsage(Usage.Stream);\r
216                     mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);\r
217                     \r
218                     \r
219                     //creating empty buffers for HW skinning \r
220                     //the buffers will be setup if ever used.\r
221                     VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);\r
222                     VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);\r
223                     mesh.setBuffer(verticesWeightsHW);\r
224                     mesh.setBuffer(verticesWeightsIndicesHW);\r
225                 }\r
226             } catch (BlenderFileException e) {\r
227                 LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);\r
228                 this.invalid = true;\r
229                 return node;\r
230             }\r
231         }\r
232 \r
233         // applying animations\r
234         AnimControl control = new AnimControl(animationData.skeleton);\r
235         List<Animation> animList = animationData.anims;\r
236         if (animList != null && animList.size() > 0) {\r
237             HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size());\r
238             for (int i = 0; i < animList.size(); ++i) {\r
239                 Animation animation = animList.get(i);\r
240                 anims.put(animation.getName(), animation);\r
241             }\r
242             control.setAnimations(anims);\r
243         }\r
244         node.addControl(control);\r
245         node.addControl(new SkeletonControl(animationData.skeleton));\r
246 \r
247         blenderContext.setNodeForSkeleton(skeleton, node);\r
248 \r
249         return node;\r
250     }\r
251 \r
252     /**\r
253      * This method reads mesh indexes\r
254      * \r
255      * @param objectStructure\r
256      *            structure of the object that has the armature modifier applied\r
257      * @param meshStructure\r
258      *            the structure of the object's mesh\r
259      * @param blenderContext\r
260      *            the blender context\r
261      * @throws BlenderFileException\r
262      *             this exception is thrown when the blend file structure is\r
263      *             somehow invalid or corrupted\r
264      */\r
265     private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException {\r
266         ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);\r
267         Structure defBase = (Structure) objectStructure.getFieldValue("defbase");\r
268         Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);\r
269 \r
270         MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());\r
271 \r
272         return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext);\r
273     }\r
274 \r
275     /**\r
276      * This method returns an array of size 2. The first element is a vertex\r
277      * buffer holding bone weights for every vertex in the model. The second\r
278      * element is a vertex buffer holding bone indices for vertices (the indices\r
279      * of bones the vertices are assigned to).\r
280      * \r
281      * @param meshStructure\r
282      *            the mesh structure object\r
283      * @param vertexListSize\r
284      *            a number of vertices in the model\r
285      * @param bonesGroups\r
286      *            this is an output parameter, it should be a one-sized array;\r
287      *            the maximum amount of weights per vertex (up to\r
288      *            MAXIMUM_WEIGHTS_PER_VERTEX) is stored there\r
289      * @param vertexReferenceMap\r
290      *            this reference map allows to map the original vertices read\r
291      *            from blender to vertices that are really in the model; one\r
292      *            vertex may appear several times in the result model\r
293      * @param groupToBoneIndexMap\r
294      *            this object maps the group index (to which a vertices in\r
295      *            blender belong) to bone index of the model\r
296      * @param blenderContext\r
297      *            the blender context\r
298      * @return arrays of vertices weights and their bone indices and (as an\r
299      *         output parameter) the maximum amount of weights for a vertex\r
300      * @throws BlenderFileException\r
301      *             this exception is thrown when the blend file structure is\r
302      *             somehow invalid or corrupted\r
303      */\r
304     private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)\r
305             throws BlenderFileException {\r
306         bonesGroups[0] = 0;\r
307         Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert\r
308                                                                         // =\r
309                                                                         // DeformVERTices\r
310         FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
311         ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);\r
312 \r
313         if (pDvert.isNotNull()) {// assigning weights and bone indices\r
314             boolean warnAboutTooManyVertexWeights = false;\r
315             // dverts.size() = verticesAmount (one dvert per vertex in blender)\r
316             List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());\r
317             int vertexIndex = 0;\r
318             // use tree map to sort weights from the lowest to the highest ones\r
319             TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>();\r
320 \r
321             for (Structure dvert : dverts) {\r
322                 //we fetch the referenced vertices here\r
323                 List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));\r
324                 if (vertexIndices != null) {\r
325                     // total amount of wights assigned to the vertex (max. 4 in JME)\r
326                     int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();\r
327                     Pointer pDW = (Pointer) dvert.getFieldValue("dw");\r
328                     if (totweight > 0 && groupToBoneIndexMap != null) {\r
329                         weightToIndexMap.clear();\r
330                         int weightIndex = 0;\r
331                         List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());\r
332                         for (Structure deformWeight : dw) {\r
333                             Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());\r
334                             float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();\r
335                             // boneIndex == null: it here means that we came\r
336                             // accross group that has no bone attached to, so\r
337                             // simply ignore it\r
338                             // if weight == 0 and weightIndex == 0 then ignore\r
339                             // the weight (do not set weight = 0 as a first\r
340                             // weight)\r
341                             if (boneIndex != null && (weight > 0.0f || weightIndex > 0)) {\r
342                                 if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) {\r
343                                     if (weight == 0.0f) {\r
344                                         boneIndex = Integer.valueOf(0);\r
345                                     }\r
346                                     // we apply the weight to all referenced\r
347                                     // vertices\r
348                                     for (Integer index : vertexIndices) {\r
349                                         weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);\r
350                                         indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());\r
351                                     }\r
352                                     weightToIndexMap.put(weight, weightIndex);\r
353                                     bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1);\r
354                                 } else if (weight > 0) {// if weight is zero the\r
355                                                         // simply ignore it\r
356                                     warnAboutTooManyVertexWeights = true;\r
357                                     Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry();\r
358                                     if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) {\r
359                                         // we apply the weight to all referenced\r
360                                         // vertices\r
361                                         for (Integer index : vertexIndices) {\r
362                                             weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight);\r
363                                             indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue());\r
364                                         }\r
365                                         weightToIndexMap.remove(lowestWeightAndIndex.getKey());\r
366                                         weightToIndexMap.put(weight, lowestWeightAndIndex.getValue());\r
367                                     }\r
368                                 }\r
369                                 ++weightIndex;\r
370                             }\r
371                         }\r
372                     } else {\r
373                         // 0.0 weight indicates, do not transform this vertex,\r
374                         // but keep it in bind pose.\r
375                         for (Integer index : vertexIndices) {\r
376                             weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f);\r
377                             indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
378                         }\r
379                     }\r
380                 }\r
381                 ++vertexIndex;\r
382             }\r
383 \r
384             if (warnAboutTooManyVertexWeights) {\r
385                 LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());\r
386             }\r
387         } else {\r
388             // always bind all vertices to 0-indexed bone\r
389             // this bone makes the model look normally if vertices have no bone\r
390             // assigned and it is used in object animation, so if we come\r
391             // accross object\r
392             // animation we can use the 0-indexed bone for this\r
393             for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {\r
394                 // we apply the weight to all referenced vertices\r
395                 for (Integer index : vertexIndexList) {\r
396                     weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);\r
397                     indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);\r
398                 }\r
399             }\r
400         }\r
401 \r
402         bonesGroups[0] = Math.max(bonesGroups[0], 1);\r
403 \r
404         this.endBoneAssigns(vertexListSize, weightsFloatData);\r
405         VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);\r
406         verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);\r
407 \r
408         VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);\r
409         verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);\r
410         return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };\r
411     }\r
412 \r
413     /**\r
414      * Normalizes weights if needed and finds largest amount of weights used for\r
415      * all vertices in the buffer.\r
416      * \r
417      * @param vertCount\r
418      *            amount of vertices\r
419      * @param weightsFloatData\r
420      *            weights for vertices\r
421      */\r
422     private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {\r
423         weightsFloatData.rewind();\r
424         float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX];\r
425         for (int v = 0; v < vertCount; ++v) {\r
426             float sum = 0;\r
427             for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {\r
428                 weights[i] = weightsFloatData.get();\r
429                 sum += weights[i];\r
430             }\r
431             if (sum != 1f && sum != 0.0f) {\r
432                 weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX);\r
433                 // compute new vals based on sum\r
434                 float sumToB = 1f / sum;\r
435                 for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {\r
436                     weightsFloatData.put(weights[i] * sumToB);\r
437                 }\r
438             }\r
439         }\r
440         weightsFloatData.rewind();\r
441     }\r
442 }\r