1 /**
2  * All classes dealing with using 3D skeletal animation
3  */
4 module dash.components.animation;
5 import dash.core.properties;
6 import dash.components;
7 import dash.utility;
8 
9 import derelict.assimp3.assimp;
10 import std..string: fromStringz;
11 import std.conv: to;
12 
13 mixin( registerComponents!() );
14 
15 /**
16  * Animation object which handles all animation specific to the gameobject
17  */
18 class Animation : Component
19 {
20 private:
21     /// Asset animation that the gameobject is animating based off of
22     @ignore
23     AnimationData _animationData;
24     /// Current animation out of all the animations in the asset animation
25     @ignore
26     int _currentAnim;
27     /// Current time of the animation
28     @ignore
29     float _currentAnimTime;
30     /// Bone transforms for the current pose
31     @ignore
32     mat4f[] _currBoneTransforms;
33     /// If the gameobject should be animating
34     @ignore
35     bool _animating;
36 
37     /// Animation to return to if _animateOnce is true
38     @ignore
39     int _returnAnimation;
40     /// If the animation is animating once, then returning to _returnAnimation
41     @ignore
42     bool _animateOnce;
43 
44 public:
45     /// Bone transforms for the current pose (Passed to the shader)
46     mixin( Property!_currBoneTransforms );
47 
48     this()
49     {
50         _currentAnim = 0;
51         _currentAnimTime = 0.0f;
52         _animating = true;
53     }
54 
55     /**
56      * Create animation object based on asset animation
57      */
58     this( AnimationData assetAnimation )
59     {
60         this();
61         _animationData = assetAnimation;
62     }
63 
64     /**
65      * Updates the animation, updating time and getting a pose based on time
66      */
67     override void update()
68     {
69         if( _animating )
70         {
71             // Update currentanimtime based on deltatime and animations fps
72             _currentAnimTime += Time.deltaTime * 24.0f;
73 
74             if( _currentAnimTime >= _animationData.animationSet[ _currentAnim ].duration * 24 - 1 )
75             {
76                 _currentAnimTime = 0.0f;
77 
78                 if( _animateOnce )
79                 {
80                     _animateOnce = false;
81                     _currentAnim = _returnAnimation;
82                 }
83             }
84         }
85 
86         // Calculate and store array of bonetransforms to pass to the shader
87         currBoneTransforms = _animationData.getTransformsAtTime( _currentAnim, _currentAnimTime );
88     }
89 
90     /**
91     * Continue animating.
92     */
93     void play()
94     {
95         _animating = true;
96     }
97     /**
98      * Pause the animation
99      */
100     void pause()
101     {
102         _animating = false;
103     }
104     /**
105      * Stops the animation, moving to the beginning
106      */
107     void stop()
108     {
109         _animating = false;
110         _currentAnimTime = 0.0f;
111     }
112     /**
113      * Switches the current animation
114      */
115     void changeAnimation( int animNumber, int startAnimTime )
116     {
117         if( animNumber < _animationData.animationSet.length )
118         {
119             _currentAnim = animNumber;
120             _currentAnimTime = startAnimTime;
121         }
122         else
123             warning( "Could not change to new animation, the animation did not exist." );
124     }
125     /**
126     * Runs an animation once, then returns
127     */
128     void runAnimationOnce( int animNumber, int returnAnimNumber )
129     {
130         if( animNumber < _animationData.animationSet.length )
131         {
132             _animateOnce = true;
133             _currentAnim = animNumber;
134             _returnAnimation = returnAnimNumber;
135             _currentAnimTime = 0;
136         }
137         else
138             warning( "Could not change to new animation, the animation did not exist." );
139     }
140 
141     /**
142     * Shutdown the gameobjects animation data
143     */
144     override void shutdown()
145     {
146 
147     }
148 }
149 
150 /**
151  * Stores the animation skeleton/bones, stores the animations poses, and makes this information accessible to gameobjects
152  */
153 class AnimationData : Asset
154 {
155 private:
156     /// List of animations, containing all of the information specific to each
157     AnimationSet[] _animationSet;
158     /// Amount of bones
159     int _numberOfBones;
160     /// Hierarchy of bones for this animation
161     Bone boneHierarchy;
162     bool _isUsed;
163 
164 public:
165     /// List of animations, containing all of the information specific to each
166     mixin( Property!_animationSet );
167     /// Amount of bones
168     mixin( Property!_numberOfBones );
169     /// Whether or not the material is actually used.
170     mixin( Property!( _isUsed, AccessModifier.Package ) );
171 
172     /**
173      * Create the assetanimation, parsing all of the animation data
174      *
175      * Params:
176      *      animations =    List of assimp animation/poses object
177      *      numAnimations = Number of
178      *      mesh =          Assimp mesh/bone object
179      *      nodeHierarchy = Hierarchy of bones/filler nodes used for the animation
180      */
181     this( Resource res, const(aiAnimation**) animations, int numAnimations, const(aiMesh*) mesh, const(aiNode*) nodeHierarchy )
182     {
183         super( res );
184 
185         for( int i = 0; i < nodeHierarchy.mNumChildren; i++)
186         {
187             string name = nodeHierarchy.mChildren[ i ].mName.data.ptr.fromStringz().to!string;
188             if( findBoneWithName( name, mesh ) != -1 )
189             {
190                 boneHierarchy = makeBonesFromHierarchy( mesh, nodeHierarchy.mChildren[ i ] );
191             }
192         }
193 
194         for( int ii = 0; ii < numAnimations; ii++)
195             addAnimationSet( animations[ ii ], 24 );
196     }
197 
198     /**
199      * Returns the animation as an addible component.
200      */
201     Animation getComponent()
202     {
203         return new Animation( this );
204     }
205 
206     /**
207      * Recurse the node hierarchy, parsing it into a usable bone hierarchy
208      *
209      * Params:
210      *      animation = Assimp animation/poses object
211      *      mesh =      Assimp mesh/bone object
212      *      currNode =  The current node checking in the hierarchy
213      *
214      * Returns: The bone based off of the currNode data
215      */
216     Bone makeBonesFromHierarchy( const(aiMesh*) mesh, const(aiNode*) currNode )
217     {
218         //NOTE: Currently only works if each node is a Bone, works with bones without animation b/c of storing nodeOffset
219         //NOTE: Needs to be reworked to support this in the future
220         Bone bone;
221         string name = currNode.mName.data.ptr.fromStringz().to!string;
222         int boneNumber = findBoneWithName( name, mesh );
223 
224         if( boneNumber != -1 )
225         {
226             bone = new Bone( name, boneNumber );
227             bone.offset = convertAIMatrix( mesh.mBones[ bone.boneNumber ].mOffsetMatrix );
228             bone.nodeOffset = convertAIMatrix( currNode.mTransformation );
229             _numberOfBones++;
230         }
231 
232         for( int i = 0; i < currNode.mNumChildren; i++ )
233         {
234             string childName = currNode.mChildren[ i ].mName.data.ptr.fromStringz().to!string;
235             int childBoneNumber = findBoneWithName( childName, mesh );
236 
237             // Ensure end nodes are bones, otherwise do not keep
238             if( boneNumber != -1 && childBoneNumber != -1 )
239                 bone.children ~= makeBonesFromHierarchy( mesh, currNode.mChildren[ i ] );
240             else if( childBoneNumber != -1 )
241                 return makeBonesFromHierarchy( mesh, currNode.mChildren[ i ] );
242         }
243 
244         return bone;
245     }
246     /**
247     * Get a bone number by matching name bones in mesh
248     *
249     * Params:
250     *      name = Name searching for
251     *      mesh = Mesh containing bones to check
252     *
253     * Returns: Bone number of desired bone
254     */
255     int findBoneWithName( string name, const(aiMesh*) mesh )
256     {
257         for( int i = 0; i < mesh.mNumBones; i++ )
258         {
259             if( name == mesh.mBones[ i ].mName.data.ptr.fromStringz )
260             {
261                 return i;
262             }
263         }
264 
265         return -1;
266     }
267 
268     void addAnimationSet( const(aiAnimation*) animation, int fps )
269     {
270         AnimationSet newAnimSet;
271         newAnimSet.duration = cast(float)animation.mDuration;
272         newAnimSet.fps = fps;
273         for( int i = 0; i < _numberOfBones; i++)
274         {
275             newAnimSet.bonePoses ~= new BonePose();
276         }
277         addPoses( animation, boneHierarchy, newAnimSet );
278         _animationSet ~= newAnimSet;
279     }
280     void addPoses( const(aiAnimation*) animation, Bone currBone, AnimationSet newAnimSet )
281     {
282         if( currBone.boneNumber != -1 )
283         {
284             assignAnimationData( animation, currBone.name, newAnimSet.bonePoses[ currBone.boneNumber ] );
285         }
286 
287         // Add to children
288         for( int i = 0; i < currBone.children.length; i++ )
289         {
290             addPoses( animation, currBone.children[ i ], newAnimSet );
291         }
292     }
293     /**
294      * Access the animation channels to find a match with the bone, then store its animation data
295      *
296      * Params:
297      *      animation =    Assimp animation/poses object
298      *      boneToAssign = Bone to assign the animation keys/poses
299      */
300     void assignAnimationData( const(aiAnimation*) animation, string boneName, BonePose poseToAssign )
301     {
302         for( int i = 0; i < animation.mNumChannels; i++)
303         {
304             if( animation.mChannels[ i ].mNodeName.data.ptr.fromStringz == boneName )
305             {
306                 // Assign the bone animation data to the bone
307                 poseToAssign.positionKeys = convertVectorArray( animation.mChannels[ i ].mPositionKeys,
308                                                                 animation.mChannels[ i ].mNumPositionKeys );
309                 poseToAssign.scaleKeys = convertVectorArray( animation.mChannels[ i ].mScalingKeys,
310                                                              animation.mChannels[ i ].mNumScalingKeys );
311                 poseToAssign.rotationKeys = convertQuat( animation.mChannels[ i ].mRotationKeys,
312                                                          animation.mChannels[ i ].mNumRotationKeys );
313             }
314         }
315     }
316 
317     /**
318      * Called by gameobject animation components to get an animation pose
319      *
320      * Params:
321      *      time = The current animations time
322      *
323      * Returns: The boneTransforms, returned to the gameobject animation component
324      */
325     mat4f[] getTransformsAtTime( int animationNumber, float time )
326     {
327         mat4f[] boneTransforms = new mat4f[ _numberOfBones ];
328 
329         // Check shader/model
330         for( int i = 0; i < _numberOfBones; i++)
331         {
332             boneTransforms[ i ] = mat4f.identity;
333         }
334 
335         fillTransforms( animationSet[ animationNumber ].bonePoses, boneTransforms, boneHierarchy, time, mat4f.identity );
336 
337         return boneTransforms;
338     }
339     /**
340      * Recurse the bone hierarchy, filling up the bone transforms along the way
341      *
342      * Params:
343      *      transforms =      The boneTransforms to fill up
344      *      bone =            The current bone checking
345      *      time =            The animations current time
346      *      parentTransform = The parents transform (which effects this bone)
347      */
348     void fillTransforms( BonePose[] bonePoses, mat4f[] transforms, Bone bone, float time, mat4f parentTransform )
349     {
350         BonePose bonePose = bonePoses[ bone.boneNumber ];
351         mat4f finalTransform;
352         if( bonePose.positionKeys.length == 0 && bonePose.rotationKeys.length == 0 && bonePose.scaleKeys.length == 0 )
353         {
354             finalTransform = parentTransform * bone.nodeOffset;
355             transforms[ bone.boneNumber ] = finalTransform * bone.offset;
356         }
357         else
358         {
359             mat4f boneTransform = mat4f.identity;
360 
361             if( bonePose.positionKeys.length > cast(int)time )
362             {
363                 boneTransform = boneTransform.translation( bonePose.positionKeys[ cast(int)time ].vector[ 0 ],
364                                                            bonePose.positionKeys[ cast(int)time ].vector[ 1 ],
365                                                            bonePose.positionKeys[ cast(int)time ].vector[ 2 ] );
366             }
367             if( bonePose.rotationKeys.length > cast(int)time )
368             {
369                 boneTransform = boneTransform * bonePose.rotationKeys[ cast(int)time ].toMatrix!4;
370             }
371             if( bonePose.scaleKeys.length > cast(int)time )
372             {
373                 boneTransform = boneTransform.scale( bonePose.scaleKeys[ cast(int)time ].vector[ 0 ],
374                                                      bonePose.scaleKeys[ cast(int)time ].vector[ 1 ],
375                                                      bonePose.scaleKeys[ cast(int)time ].vector[ 2 ] );
376             }
377 
378             finalTransform = parentTransform * boneTransform;
379             transforms[ bone.boneNumber ] = finalTransform * bone.offset;
380         }
381 
382         // Check children
383         for( int i = 0; i < bone.children.length; i++ )
384         {
385             fillTransforms( bonePoses, transforms, bone.children[ i ], time, finalTransform );
386         }
387     }
388 
389     /**
390     * Converts a aiVectorKey[] to vec3f[].
391      *
392      * Params:
393      *      quaternions = aiVectorKey[] to be converted
394      *      numKeys =     Number of keys in vector array
395      *
396      * Returns: The vectors in vector[] format
397      */
398     vec3f[] convertVectorArray( const(aiVectorKey*) vectors, int numKeys )
399     {
400         vec3f[] keys;
401         for( int i = 0; i < numKeys; i++ )
402         {
403             aiVector3D vector = vectors[ i ].mValue;
404             keys ~= vec3f( vector.x, vector.y, vector.z );
405         }
406 
407         return keys;
408     }
409     /**
410      * Converts a aiQuatKey[] to quat[].
411      *
412      * Params:
413      *      quaternions = aiQuatKey[] to be converted
414      *      numKeys =     Number of keys in quaternions array
415      *
416      * Returns: The quaternions in quat[] format
417      */
418     quatf[] convertQuat( const(aiQuatKey*) quaternions, int numKeys )
419     {
420         quatf[] keys;
421         for( int i = 0; i < numKeys; i++ )
422         {
423             aiQuatKey quaternion = quaternions[ i ];
424             keys ~= quatf( quaternion.mValue.w, quaternion.mValue.x, quaternion.mValue.y, quaternion.mValue.z );
425         }
426 
427         return keys;
428     }
429     /**
430      * Converts an aiMatrix to a mat4
431      *
432      * Params:
433      *      aiMatrix = Matrix to be converted
434      *
435      * Returns: The matrix in mat4 format
436      */
437     mat4f convertAIMatrix( aiMatrix4x4 aiMatrix )
438     {
439         mat4f matrix = mat4f.identity;
440 
441         matrix[0][0] = aiMatrix.a1;
442         matrix[0][1] = aiMatrix.a2;
443         matrix[0][2] = aiMatrix.a3;
444         matrix[0][3] = aiMatrix.a4;
445         matrix[1][0] = aiMatrix.b1;
446         matrix[1][1] = aiMatrix.b2;
447         matrix[1][2] = aiMatrix.b3;
448         matrix[1][3] = aiMatrix.b4;
449         matrix[2][0] = aiMatrix.c1;
450         matrix[2][1] = aiMatrix.c2;
451         matrix[2][2] = aiMatrix.c3;
452         matrix[2][3] = aiMatrix.c4;
453         matrix[3][0] = aiMatrix.d1;
454         matrix[3][1] = aiMatrix.d2;
455         matrix[3][2] = aiMatrix.d3;
456         matrix[3][3] = aiMatrix.d4;
457 
458         return matrix;
459     }
460 
461     /**
462      * Shutdown the animation bone/pose data
463      */
464     override void shutdown()
465     {
466 
467     }
468 
469     /**
470      * A single animation track, storing its bones and poses
471      */
472     struct AnimationSet
473     {
474         float duration;
475         float fps;
476         BonePose[] bonePoses;
477     }
478     /**
479     * All the bone transforms/rotations/scales for a sc
480     */
481     class BonePose
482     {
483         vec3f[] positionKeys;
484         quatf[] rotationKeys;
485         vec3f[] scaleKeys;
486     }
487     /**
488      * A bone in the animation, storing everything it needs
489      */
490     class Bone
491     {
492         this( string boneName, int boneNum )
493         {
494             name = boneName;
495             boneNumber = boneNum;
496         }
497 
498         string name;
499         int boneNumber;
500         Bone[] children;
501 
502         mat4f offset;
503         mat4f nodeOffset;
504     }
505 }