Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
AnimationComponent.cpp
Go to the documentation of this file.
1#include "scene/components/AnimationComponent.h"
2
3#include <algorithm>
4#include <unordered_set>
5
8#include "scene/GameObject.h"
9
10namespace mnd
11{
12std::vector<std::string> AnimationComponent::GetClipNames() const
13{
14 std::unordered_set<std::string> seen;
15 seen.reserve(m_clips.size() + m_skelClips.size());
16 for (const auto &kv : m_clips) { seen.insert(kv.first); }
17 for (const auto &kv : m_skelClips) { seen.insert(kv.first); }
18
19 std::vector<std::string> out(seen.begin(), seen.end());
20 std::sort(out.begin(), out.end());
21 return out;
22}
23
24void AnimationComponent::Update(float deltaTime)
25{
26 if (!m_isPlaying)
27 {
28 return;
29 }
30 if (!m_clip && !m_activeSkelClip)
31 {
32 return;
33 }
34
35 // Effective duration: longest of the active lanes.
36 float duration = 0.0f;
37 if (m_clip) duration = std::max(duration, m_clip->duration);
38 if (m_activeSkelClip) duration = std::max(duration, m_activeSkelClip->duration);
39
40 m_time += deltaTime;
41 if (duration > 0.0f && m_time > duration)
42 {
43 if (m_looping)
44 {
45 m_time = std::fmod(m_time, duration);
46 } else
47 {
48 m_time = duration;
49 m_isPlaying = false;
50 }
51 }
52
53 // Node-track lane: drive child GameObjects by name.
54 if (m_clip)
55 {
56 for (auto &binding : m_bindings)
57 {
58 auto &obj = binding.first;
59 auto &trackIndices = binding.second->trackIndices;
60 for (auto i : trackIndices)
61 {
62 auto &track = m_clip->tracks[i];
63 if (!track.positions.empty())
64 {
65 obj->SetPosition(Interpolate(track.positions, m_time));
66 }
67 if (!track.rotations.empty())
68 {
69 obj->SetRotation(Interpolate(track.rotations, m_time));
70 }
71 if (!track.scales.empty())
72 {
73 obj->SetScale(Interpolate(track.scales, m_time));
74 }
75 }
76 }
77 }
78
79 // Skeletal lane: evaluate pose → fill GPU palette read by SkinnedMeshComponent.
80 if (m_activeSkelClip && m_skeleton)
81 {
82 EvaluatePose(*m_activeSkelClip, *m_skeleton, m_time, m_pose);
83 ComputePalette(*m_skeleton, m_pose);
84 m_palette = m_pose.finalPalette;
85 }
86}
87
89{
90 m_clip = clip;
91 BuildBindings();
92}
93
94void AnimationComponent::RegisterClip(const std::string &name, const std::shared_ptr<AnimationClip> &clip)
95{
96 m_clips[name] = clip;
97}
98
99void AnimationComponent::RegisterSkeletalClip(const std::string &name, const std::shared_ptr<SkeletalAnimationClip> &clip)
100{
101 // Step 7 wires this into Update(). For now we just stash so the importer compiles.
102 m_skelClips[name] = clip;
103}
104
105void AnimationComponent::SetSkeleton(const std::shared_ptr<Skeleton> &skeleton)
106{
107 m_skeleton = skeleton;
108}
109
110void AnimationComponent::Play(const std::string &name, bool loop)
111{
112 bool found = false;
113
114 auto nodeIt = m_clips.find(name);
115 if (nodeIt != m_clips.end())
116 {
117 SetClip(nodeIt->second.get());
118 found = true;
119 } else
120 {
121 m_clip = nullptr;
122 m_bindings.clear();
123 }
124
125 auto skelIt = m_skelClips.find(name);
126 if (skelIt != m_skelClips.end())
127 {
128 m_activeSkelClip = skelIt->second.get();
129 if (m_skeleton)
130 {
131 ResetPoseToBind(*m_skeleton, m_pose);
132 }
133 found = true;
134 } else
135 {
136 m_activeSkelClip = nullptr;
137 }
138
139 if (!found)
140 {
141 return;
142 }
143
144 m_time = 0.0f;
145 m_isPlaying = true;
146 m_looping = loop;
147}
148
149void AnimationComponent::BuildBindings()
150{
151 m_bindings.clear();
152 if (!m_clip)
153 {
154 return;
155 }
156
157 for (size_t i = 0; i < m_clip->tracks.size(); ++i)
158 {
159 auto &track = m_clip->tracks[i];
160 auto targetObject = m_owner->FindChildByName(track.targetName);
161 if (targetObject)
162 {
163 auto it = m_bindings.find(targetObject);
164 if (it != m_bindings.end())
165 {
166 it->second->trackIndices.push_back(i);
167 } else
168 {
169 auto binding = std::make_unique<ObjectBinding>();
170 binding->object = targetObject;
171 binding->trackIndices.push_back(i);
172 m_bindings.emplace(targetObject, std::move(binding));
173 }
174 }
175 }
176}
177
178glm::vec3 AnimationComponent::Interpolate(const std::vector<KeyFrameVec3> &keys, float time)
179{
180 if (keys.empty())
181 {
182 return glm::vec3(0.0f);
183 }
184
185 if (keys.size() == 1)
186 {
187 return keys[0].value;
188 }
189
190 if (time <= keys.front().time)
191 {
192 return keys.front().value;
193 }
194
195 if (time >= keys.back().time)
196 {
197 return keys.back().value;
198 }
199
200 size_t i0 = 0;
201 size_t i1 = 0;
202
203 for (size_t i = 1; i < keys.size(); ++i)
204 {
205 if (time <= keys[i].time)
206 {
207 i1 = i;
208 break;
209 }
210 }
211
212 i0 = i1 > 0 ? i1 - 1 : 0;
213
214 if (time >= keys[i0].time && time <= keys[i1].time)
215 {
216 float deltaTime = keys[i1].time - keys[i0].time;
217 float k = (time - keys[i0].time) / deltaTime;
218
219 return glm::mix(keys[i0].value, keys[i1].value, k);
220 }
221
222 return keys.back().value;
223}
224
225glm::quat AnimationComponent::Interpolate(const std::vector<KeyFrameQuat> &keys, float time)
226{
227 if (keys.empty())
228 {
229 return glm::vec3(0.0f);
230 }
231
232 if (keys.size() == 1)
233 {
234 return keys[0].value;
235 }
236
237 if (time <= keys.front().time)
238 {
239 return keys.front().value;
240 }
241
242 if (time >= keys.back().time)
243 {
244 return keys.back().value;
245 }
246
247 size_t i0 = 0;
248 size_t i1 = 0;
249
250 for (size_t i = 1; i < keys.size(); ++i)
251 {
252 if (time <= keys[i].time)
253 {
254 i1 = i;
255 break;
256 }
257 }
258
259 i0 = i1 > 0 ? i1 - 1 : 0;
260
261 if (time >= keys[i0].time && time <= keys[i1].time)
262 {
263 float deltaTime = keys[i1].time - keys[i0].time;
264 float k = (time - keys[i0].time) / deltaTime;
265
266 return glm::slerp(keys[i0].value, keys[i1].value, k);
267 }
268
269 return keys.back().value;
270}
271
273{
274 return m_isPlaying;
275}
276
277} // namespace mnd
Per-bone TRS keyframe tracks driving a Skeleton.
Bone hierarchy + bind/offset matrices for GPU-skinned meshes.
void SetClip(AnimationClip *clip)
Set the active clip directly (bypasses the name registry).
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().
GameObject * m_owner
Definition Component.h:57
GameObject * FindChildByName(const std::string &name)
Depth-first search for a child with the given name.
void ComputePalette(const Skeleton &skeleton, Pose &pose)
Concatenate parent transforms and apply offsetMatrix to produce the GPU palette.
Definition Pose.cpp:127
void ResetPoseToBind(const Skeleton &skeleton, Pose &pose)
Resize pose buffers to match skeleton and reset to bind pose.
Definition Pose.cpp:84
void EvaluatePose(const SkeletalAnimationClip &clip, const Skeleton &skeleton, f32 time, Pose &pose)
Sample clip at time and write each animated bone's TRS into pose.localTransforms.
Definition Pose.cpp:96
A named, playable animation containing one or more transform tracks.
std::vector< TransformTrack > tracks
Per-node animation channels.
float duration
Total clip length in seconds.
std::vector< glm::mat4 > finalPalette
Skinning matrices: globalPose * offsetMatrix.
Definition Pose.h:32