Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
AnimationComponent.h
Go to the documentation of this file.
1/**
2 * @file AnimationComponent.h
3 * @ingroup mnd_components
4 * @brief Skeletal / transform animation system driven by GLTF keyframe data.
5 *
6 * ## Data model
7 * ```
8 * AnimationClip
9 * └── TransformTrack[] (one per animated GLTF node)
10 * ├── KeyFrameVec3[] positions
11 * ├── KeyFrameQuat[] rotations
12 * └── KeyFrameVec3[] scales
13 * ```
14 * Each track targets a child GameObject by name (targetName). On Play(),
15 * AnimationComponent walks the owner's child hierarchy and caches a binding
16 * (ObjectBinding) that maps track indices to live GameObjects.
17 *
18 * ## Per-frame playback
19 * Update() advances m_time by deltaTime, linearly interpolates between the
20 * surrounding keyframes for each track, and writes the result directly to
21 * the target GameObject's local position / rotation / scale.
22 *
23 * ## Loading
24 * AnimationClips are populated by GameObject::LoadGLTF() from the GLTF
25 * animation channels, then registered on this component via RegisterClip().
26 *
27 * @code
28 * auto *anim = gunObject->GetComponent<AnimationComponent>();
29 * anim->Play("fire", false); // play once, no loop
30 * @endcode
31 *
32 * @see GameObject::LoadGLTF, TransformTrack, AnimationClip
33 */
34
35#pragma once
36#include <memory>
37#include <string>
38#include <unordered_map>
39#include <vector>
40
41#include <glm/gtc/quaternion.hpp>
42#include <glm/vec3.hpp>
43
44#include <glm/mat4x4.hpp>
45
46#include "animation/KeyFrame.h"
47#include "animation/Pose.h"
48#include "scene/Component.h"
49
50namespace mnd
51{
52
53class Skeleton;
54struct SkeletalAnimationClip;
55
56/**
57 * @brief Animation data for one GLTF node: position, rotation, and scale tracks.
58 *
59 * targetName matches the child GameObject name so AnimationComponent can
60 * resolve the live object reference at Play() time.
61 */
63{
64 std::string targetName; ///< Name of the child GameObject to animate.
65 std::vector<KeyFrameVec3> positions; ///< Sorted position keyframes.
66 std::vector<KeyFrameQuat> rotations; ///< Sorted rotation keyframes (slerp-interpolated).
67 std::vector<KeyFrameVec3> scales; ///< Sorted scale keyframes.
68};
69
70/**
71 * @brief A named, playable animation containing one or more transform tracks.
72 */
74{
75 std::string name; ///< Clip identifier (matches GLTF animation name).
76 float duration = 0.0f; ///< Total clip length in seconds.
77 bool looping = true; ///< Whether playback wraps at the end.
78 std::vector<TransformTrack> tracks; ///< Per-node animation channels.
79};
80
81/**
82 * @brief Runtime binding from a TransformTrack to a live child GameObject.
83 *
84 * Built once when Play() is called (or when the clip changes) by walking
85 * the owner's child hierarchy and matching names to track targets.
86 */
88{
89 GameObject *object = nullptr; ///< Non-owning pointer to the target child.
90 std::vector<size_t> trackIndices; ///< Indices into AnimationClip::tracks that drive this object.
91};
92
93/**
94 * @brief Drives transform animation on a hierarchy of GameObjects.
95 *
96 * Attach to the root of a GLTF-loaded hierarchy. Register the clips loaded
97 * from the file (this is done automatically by GameObject::LoadGLTF), then
98 * call Play() to start playback.
99 */
101{
103public:
104 /**
105 * @brief Advance playback time and write interpolated transforms to bound objects.
106 * @param deltaTime Seconds since the previous frame.
107 */
108 void Update(float deltaTime) override;
109
110 /**
111 * @brief Set the active clip directly (bypasses the name registry).
112 * @param clip Raw pointer to the clip to play; not owned.
113 */
114 void SetClip(AnimationClip *clip);
115
116 /**
117 * @brief Register a named clip so it can be started via Play().
118 * @param name Lookup key (typically the GLTF animation name).
119 * @param clip Shared AnimationClip loaded from GLTF.
120 */
121 void RegisterClip(const std::string &name, const std::shared_ptr<AnimationClip> &clip);
122
123 /// Register a skeletal clip (per-bone TRS tracks) — used by SkinnedMeshComponent.
124 void RegisterSkeletalClip(const std::string &name, const std::shared_ptr<SkeletalAnimationClip> &clip);
125
126 /// Bind a skeleton to the component so skeletal clips can be evaluated.
127 void SetSkeleton(const std::shared_ptr<Skeleton> &skeleton);
128
129 /// Read-only handle to the bound skeleton (may be null for non-skeletal anims).
130 const std::shared_ptr<Skeleton> &GetSkeleton() const { return m_skeleton; }
131
132 /// Halt playback. Pose freezes at the current frame.
133 void Stop() { m_isPlaying = false; }
134
135 bool IsPlaying();
136
137 /**
138 * @brief Read-only access to the bone palette computed at the last Update().
139 *
140 * Empty until a skeletal clip is registered and Play() has been called.
141 * `SkinnedMeshComponent` reads this to upload `uBones[]` each frame.
142 */
143 const std::vector<glm::mat4> &GetPalette() const { return m_palette; }
144
145 /**
146 * @brief Start playing a registered clip by name.
147 *
148 * Resolves target GameObjects by name (BuildBindings) and resets m_time
149 * to 0. Calling Play() while already playing restarts from the beginning.
150 *
151 * @param name Loop key registered via RegisterClip.
152 * @param loop true to loop indefinitely; false to play once and stop.
153 */
154 void Play(const std::string &name, bool loop = true);
155
156 /**
157 * @brief List every clip name registered on this component (node + skeletal).
158 *
159 * Names are deduplicated and returned sorted. Useful for editor UIs that
160 * want to enumerate available animations without poking at the maps.
161 */
162 std::vector<std::string> GetClipNames() const;
163
164private:
165 /// Linear interpolation between the two surrounding vec3 keyframes.
166 glm::vec3 Interpolate(const std::vector<KeyFrameVec3> &keys, float time);
167
168 /// Spherical linear interpolation (slerp) between surrounding quat keyframes.
169 glm::quat Interpolate(const std::vector<KeyFrameQuat> &keys, float time);
170
171 /**
172 * @brief Walk the owner's child hierarchy and cache live object pointers for each track.
173 *
174 * Called automatically by Play(). Matches TransformTrack::targetName against
175 * child GameObject names using FindChildByName().
176 */
177 void BuildBindings();
178
179private:
180 AnimationClip *m_clip = nullptr; ///< Active clip (non-owning); may be nullptr.
181 float m_time = 0.0f; ///< Current playback position in seconds.
182 bool m_looping = true; ///< Whether to wrap at the end of the clip.
183 bool m_isPlaying = false; ///< False until Play() is called.
184
185 std::unordered_map<std::string, std::shared_ptr<AnimationClip>> m_clips; ///< Registered clips by name.
186 std::unordered_map<GameObject *, std::unique_ptr<ObjectBinding>>
187 m_bindings; ///< Track-to-object bindings built at play time.
188
189 std::vector<glm::mat4> m_palette; ///< Latest skinning palette (empty for non-skeletal clips). Filled in step 7.
190
191 std::shared_ptr<Skeleton> m_skeleton;
192 std::unordered_map<std::string, std::shared_ptr<SkeletalAnimationClip>> m_skelClips;
193 SkeletalAnimationClip *m_activeSkelClip = nullptr;
194 Pose m_pose;
195};
196
197} // namespace mnd
Shared keyframe value types for transform-track animation.
Per-frame skeletal pose: local bone transforms + final GPU palette.
Drives transform animation on a hierarchy of GameObjects.
void SetClip(AnimationClip *clip)
Set the active clip directly (bypasses the name registry).
void Stop()
Halt playback. Pose freezes at the current frame.
const std::vector< glm::mat4 > & GetPalette() const
Read-only access to the bone palette computed at the last Update().
const std::shared_ptr< Skeleton > & GetSkeleton() const
Read-only handle to the bound skeleton (may be null for non-skeletal anims).
std::vector< std::string > GetClipNames() const
List every clip name registered on this component (node + skeletal).
void Play(const std::string &name, bool loop=true)
Start playing a registered clip by name.
void SetSkeleton(const std::shared_ptr< Skeleton > &skeleton)
Bind a skeleton to the component so skeletal clips can be evaluated.
void Update(float deltaTime) override
Advance playback time and write interpolated transforms to bound objects.
void RegisterSkeletalClip(const std::string &name, const std::shared_ptr< SkeletalAnimationClip > &clip)
Register a skeletal clip (per-bone TRS tracks) — used by SkinnedMeshComponent.
void RegisterClip(const std::string &name, const std::shared_ptr< AnimationClip > &clip)
Register a named clip so it can be started via Play().
Node in the scene graph: a named transform that owns components and children.
Definition GameObject.h:91
#define COMPONENT(ComponentClass)
Definition Component.h:105
A named, playable animation containing one or more transform tracks.
std::string name
Clip identifier (matches GLTF animation name).
std::vector< TransformTrack > tracks
Per-node animation channels.
float duration
Total clip length in seconds.
bool looping
Whether playback wraps at the end.
Runtime binding from a TransformTrack to a live child GameObject.
std::vector< size_t > trackIndices
Indices into AnimationClip::tracks that drive this object.
Per-frame pose state for a Skeleton.
Definition Pose.h:30
Animation data for one GLTF node: position, rotation, and scale tracks.
std::vector< KeyFrameVec3 > scales
Sorted scale keyframes.
std::string targetName
Name of the child GameObject to animate.
std::vector< KeyFrameQuat > rotations
Sorted rotation keyframes (slerp-interpolated).
std::vector< KeyFrameVec3 > positions
Sorted position keyframes.