SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
AnimatorComponent.cpp
Go to the documentation of this file.
3#include <Core/GameObject.hpp>
7#include <Core/Logger.hpp>
8#include <cmath>
9
10namespace Sleak {
11
13 std::vector<AnimationClip*> clips)
14 : Component(owner), m_skeleton(skeleton), m_clips(std::move(clips)) {
15}
16
18 delete m_stateMachine;
19}
20
22 if (!m_skeleton || m_skeleton->GetBoneCount() == 0) {
23 SLEAK_WARN("AnimatorComponent: No skeleton or empty skeleton");
24 return false;
25 }
26
27 int boneCount = m_skeleton->GetBoneCount();
28 m_boneMatrices.resize(boneCount, Math::Matrix4::Identity());
29 m_boneMatricesB.resize(boneCount, Math::Matrix4::Identity());
30
31 // Create bone constant buffer at slot 3
32 uint32_t bufferSize = static_cast<uint32_t>(boneCount * sizeof(Math::Matrix4));
36 bufferSize,
37 nullptr));
38 m_boneBuffer->SetSlot(3);
39
40 // Initialize with identity matrices
41 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
42
43 // Attach bone buffer to sibling MeshComponent so DrawIndexedCommand binds it
44 auto* meshComp = GetOwner()->GetComponent<MeshComponent>();
45 if (meshComp) {
46 meshComp->AddConstantBuffer(m_boneBuffer);
47 } else {
48 SLEAK_WARN("AnimatorComponent: No sibling MeshComponent found");
49 }
50
51 bIsInitialized = true;
52 SLEAK_INFO("AnimatorComponent: Initialized with {} bones, {} clips",
53 boneCount, m_clips.size());
54 return true;
55}
56
57void AnimatorComponent::Update(float deltaTime) {
58 if (!bIsInitialized)
59 return;
60
61 if (m_stateMachine) {
62 SampleRequest req = m_stateMachine->Update(deltaTime);
63
64 if (!req.clipA)
65 return;
66
67 if (req.clipB && req.blendWeight > 0.0f) {
68 // Blending two clips
69 ComputeBoneTransformsForClip(req.clipA, req.timeA, m_boneMatrices);
70 ComputeBoneTransformsForClip(req.clipB, req.timeB, m_boneMatricesB);
71 BlendBoneMatrices(m_boneMatrices, m_boneMatricesB,
72 req.blendWeight, m_boneMatrices);
73 } else {
74 // Single clip
75 ComputeBoneTransformsForClip(req.clipA, req.timeA, m_boneMatrices);
76 }
77
78 uint32_t bufferSize = static_cast<uint32_t>(
79 m_skeleton->GetBoneCount() * sizeof(Math::Matrix4));
80 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
81 return;
82 }
83
84 if (!m_playing || m_currentClip < 0)
85 return;
86
87 AnimationClip* clip = m_clips[m_currentClip];
88 if (!clip) return;
89
90 m_currentTime += deltaTime * m_speed * clip->ticksPerSecond;
91
92 if (m_currentTime > clip->duration) {
93 if (m_loop) {
94 m_currentTime = std::fmod(m_currentTime, clip->duration);
95 } else {
96 m_currentTime = clip->duration;
97 m_playing = false;
98 }
99 }
100
101 ComputeBoneTransforms(m_currentTime);
102
103 uint32_t bufferSize = static_cast<uint32_t>(
104 m_skeleton->GetBoneCount() * sizeof(Math::Matrix4));
105 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
106}
107
109 delete m_stateMachine;
110 m_stateMachine = new AnimationStateMachine();
111 return m_stateMachine;
112}
113
115 if (clip)
116 m_clips.push_back(clip);
117}
118
119void AnimatorComponent::ComputeBoneTransformsForClip(AnimationClip* clip, float animTime,
120 std::vector<Math::Matrix4>& outMatrices) {
122 int rootIdx = m_skeleton->GetRootNodeIndex();
123 if (rootIdx >= 0) {
124 ProcessNodeHierarchyForClip(rootIdx, identity, clip, animTime, outMatrices);
125 }
126}
127
128void AnimatorComponent::ProcessNodeHierarchyForClip(int nodeIndex,
129 const Math::Matrix4& parentTransform,
130 AnimationClip* clip, float animTime,
131 std::vector<Math::Matrix4>& outMatrices) {
132 const NodeData& node = m_skeleton->GetNode(nodeIndex);
133
134 Math::Matrix4 nodeTransform = node.defaultTransform;
135
136 const AnimationChannel* channel = clip->FindChannel(node.name);
137 if (channel) {
138 Math::Vector3D pos = InterpolatePosition(*channel, animTime);
139 Math::Quaternion rot = InterpolateRotation(*channel, animTime);
140 Math::Vector3D scl = InterpolateScale(*channel, animTime);
141
142 Math::Quaternion rotConj(rot.GetW(), -rot.GetX(), -rot.GetY(), -rot.GetZ());
143
144 Math::Matrix4 scaleMat = Math::Matrix4::Scale(scl);
145 Math::Matrix4 rotMat = Math::Matrix4::Rotate(rotConj);
147
148 nodeTransform = scaleMat * rotMat * transMat;
149 }
150
151 Math::Matrix4 globalTransform = nodeTransform * parentTransform;
152
153 if (node.boneIndex >= 0 && node.boneIndex < static_cast<int>(outMatrices.size())) {
154 const Bone& bone = m_skeleton->GetBone(node.boneIndex);
155 outMatrices[node.boneIndex] = bone.offsetMatrix * globalTransform *
156 m_skeleton->GetGlobalInverseTransform();
157 }
158
159 for (int childIdx : node.children) {
160 ProcessNodeHierarchyForClip(childIdx, globalTransform, clip, animTime, outMatrices);
161 }
162}
163
164void AnimatorComponent::BlendBoneMatrices(const std::vector<Math::Matrix4>& a,
165 const std::vector<Math::Matrix4>& b,
166 float weight,
167 std::vector<Math::Matrix4>& out) {
168 float w0 = 1.0f - weight;
169 float w1 = weight;
170 for (size_t i = 0; i < a.size() && i < b.size(); ++i) {
171 for (int r = 0; r < 4; ++r) {
172 for (int c = 0; c < 4; ++c) {
173 out[i](r, c) = a[i](r, c) * w0 + b[i](r, c) * w1;
174 }
175 }
176 }
177}
178
179void AnimatorComponent::ComputeBoneTransforms(float animTime) {
181 int rootIdx = m_skeleton->GetRootNodeIndex();
182 if (rootIdx >= 0) {
183 ProcessNodeHierarchy(rootIdx, identity, animTime);
184 }
185}
186
187void AnimatorComponent::ProcessNodeHierarchy(int nodeIndex,
188 const Math::Matrix4& parentTransform,
189 float animTime) {
190 const NodeData& node = m_skeleton->GetNode(nodeIndex);
191 AnimationClip* clip = m_clips[m_currentClip];
192
193 Math::Matrix4 nodeTransform = node.defaultTransform;
194
195 const AnimationChannel* channel = clip->FindChannel(node.name);
196 if (channel) {
197 Math::Vector3D pos = InterpolatePosition(*channel, animTime);
198 Math::Quaternion rot = InterpolateRotation(*channel, animTime);
199 Math::Vector3D scl = InterpolateScale(*channel, animTime);
200
201 Math::Quaternion rotConj(rot.GetW(), -rot.GetX(), -rot.GetY(), -rot.GetZ());
202
203 Math::Matrix4 scaleMat = Math::Matrix4::Scale(scl);
204 Math::Matrix4 rotMat = Math::Matrix4::Rotate(rotConj);
206
207 nodeTransform = scaleMat * rotMat * transMat;
208 }
209
210 Math::Matrix4 globalTransform = nodeTransform * parentTransform;
211
212 if (node.boneIndex >= 0 && node.boneIndex < static_cast<int>(m_boneMatrices.size())) {
213 const Bone& bone = m_skeleton->GetBone(node.boneIndex);
214 m_boneMatrices[node.boneIndex] = bone.offsetMatrix * globalTransform *
215 m_skeleton->GetGlobalInverseTransform();
216 }
217
218 for (int childIdx : node.children) {
219 ProcessNodeHierarchy(childIdx, globalTransform, animTime);
220 }
221}
222
223/// Finds the keyframe pair straddling time and the lerp factor between them.
224template<typename T>
225static std::pair<int, float> FindKeyframe(const std::vector<Keyframe<T>>& keys, float time) {
226 int idx = 0;
227 for (int i = 0; i < static_cast<int>(keys.size()) - 1; ++i) {
228 if (time < keys[i + 1].time) { idx = i; break; }
229 idx = i;
230 }
231 int next = idx + 1;
232 if (next >= static_cast<int>(keys.size()))
233 return {idx, 0.0f};
234
235 float dt = keys[next].time - keys[idx].time;
236 float t = (dt > 0.0f) ? (time - keys[idx].time) / dt : 0.0f;
237 return {idx, std::max(0.0f, std::min(1.0f, t))};
238}
239
240/// Componentwise vector lerp.
241static Math::Vector3D LerpVec3(const Math::Vector3D& a, const Math::Vector3D& b, float t) {
242 return Math::Vector3D(
243 a.GetX() + (b.GetX() - a.GetX()) * t,
244 a.GetY() + (b.GetY() - a.GetY()) * t,
245 a.GetZ() + (b.GetZ() - a.GetZ()) * t);
246}
247
248Math::Vector3D AnimatorComponent::InterpolatePosition(
249 const AnimationChannel& channel, float time) {
250 auto& keys = channel.positionKeys;
251 if (keys.empty()) return Math::Vector3D(0.0f, 0.0f, 0.0f);
252 if (keys.size() == 1) return keys[0].value;
253
254 auto [idx, t] = FindKeyframe(keys, time);
255 if (idx + 1 >= static_cast<int>(keys.size())) return keys[idx].value;
256 return LerpVec3(keys[idx].value, keys[idx + 1].value, t);
257}
258
259Math::Quaternion AnimatorComponent::InterpolateRotation(
260 const AnimationChannel& channel, float time) {
261 auto& keys = channel.rotationKeys;
262 if (keys.empty()) return Math::Quaternion();
263 if (keys.size() == 1) return keys[0].value;
264
265 auto [idx, t] = FindKeyframe(keys, time);
266 if (idx + 1 >= static_cast<int>(keys.size())) return keys[idx].value;
267 return Slerp(keys[idx].value, keys[idx + 1].value, t);
268}
269
270Math::Vector3D AnimatorComponent::InterpolateScale(
271 const AnimationChannel& channel, float time) {
272 auto& keys = channel.scaleKeys;
273 if (keys.empty()) return Math::Vector3D(1.0f, 1.0f, 1.0f);
274 if (keys.size() == 1) return keys[0].value;
275
276 auto [idx, t] = FindKeyframe(keys, time);
277 if (idx + 1 >= static_cast<int>(keys.size())) return keys[idx].value;
278 return LerpVec3(keys[idx].value, keys[idx + 1].value, t);
279}
280
281Math::Quaternion AnimatorComponent::Slerp(const Math::Quaternion& a,
282 const Math::Quaternion& b, float t) {
283 float dot = a.GetW() * b.GetW() + a.GetX() * b.GetX() +
284 a.GetY() * b.GetY() + a.GetZ() * b.GetZ();
285
286 Math::Quaternion b2 = b;
287 if (dot < 0.0f) {
288 b2 = Math::Quaternion(-b.GetW(), -b.GetX(), -b.GetY(), -b.GetZ());
289 dot = -dot;
290 }
291
292 if (dot > 0.9995f) {
293 Math::Quaternion result(
294 a.GetW() + (b2.GetW() - a.GetW()) * t,
295 a.GetX() + (b2.GetX() - a.GetX()) * t,
296 a.GetY() + (b2.GetY() - a.GetY()) * t,
297 a.GetZ() + (b2.GetZ() - a.GetZ()) * t);
298 result.normalize();
299 return result;
300 }
301
302 float theta = std::acos(dot);
303 float sinTheta = std::sin(theta);
304 float wa = std::sin((1.0f - t) * theta) / sinTheta;
305 float wb = std::sin(t * theta) / sinTheta;
306
307 return Math::Quaternion(
308 a.GetW() * wa + b2.GetW() * wb,
309 a.GetX() * wa + b2.GetX() * wb,
310 a.GetY() * wa + b2.GetY() * wb,
311 a.GetZ() * wa + b2.GetZ() * wb);
312}
313
314void AnimatorComponent::Play(const std::string& clipName, bool loop) {
315 for (int i = 0; i < static_cast<int>(m_clips.size()); ++i) {
316 if (m_clips[i] && m_clips[i]->name == clipName) {
317 Play(i, loop);
318 return;
319 }
320 }
321 SLEAK_WARN("AnimatorComponent: Clip '{}' not found", clipName);
322}
323
324void AnimatorComponent::Play(int clipIndex, bool loop) {
325 if (clipIndex < 0 || clipIndex >= static_cast<int>(m_clips.size())) {
326 SLEAK_WARN("AnimatorComponent: Invalid clip index {}", clipIndex);
327 return;
328 }
329 m_currentClip = clipIndex;
330 m_currentTime = 0.0f;
331 m_loop = loop;
332 m_playing = true;
333}
334
336 m_playing = false;
337 m_currentTime = 0.0f;
338}
339
341 m_playing = false;
342}
343
345 if (m_currentClip >= 0)
346 m_playing = true;
347}
348
350 m_speed = speed;
351}
352
354 return m_speed;
355}
356
358 return m_playing;
359}
360
362 return m_currentTime;
363}
364
365const std::string& AnimatorComponent::GetCurrentClipName() const {
366 static const std::string empty;
367 if (m_currentClip >= 0 && m_currentClip < static_cast<int>(m_clips.size()))
368 return m_clips[m_currentClip]->name;
369 return empty;
370}
371
375
376} // namespace Sleak
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
const std::string & GetCurrentClipName() const
AnimationStateMachine * CreateStateMachine()
Replaces any existing state machine with a fresh, empty one.
void AddClip(AnimationClip *clip)
virtual void Update(float deltaTime) override
Samples the active state machine or clip and pushes the resulting bone matrices to the GPU.
RefPtr< RenderEngine::BufferBase > GetBoneBuffer() const
AnimatorComponent(GameObject *owner, Skeleton *skeleton, std::vector< AnimationClip * > clips)
void Play(const std::string &clipName, bool loop=true)
Switches to the clip by name, restarting from time zero.
virtual bool Initialize() override
Allocates the bone constant buffer and attaches it to the sibling MeshComponent.
GameObject * owner
Definition Component.hpp:81
Component(GameObject *object)
Definition Component.hpp:61
GameObject * GetOwner()
Definition Component.hpp:76
T * GetComponent()
Finds the first attached component of type T, or nullptr.
static Matrix< float, Rows, Rows > Identity()
Definition Matrix.hpp:203
static Matrix< float, 4, 4 > Rotate(const Quaternion &rotation)
Definition Matrix.hpp:380
static Matrix< float, 4, 4 > Scale(const Vector3D &scale)
Definition Matrix.hpp:385
static Matrix< float, 4, 4 > Translate(const Vector3D &translation)
Definition Matrix.hpp:368
Represents a quaternion for 3D rotations.
float GetY() const
Definition Vector.hpp:361
float GetX() const
Definition Vector.hpp:360
float GetZ() const
Definition Vector.hpp:362
static BufferBase * CreateBuffer(BufferType Type, uint32_t Size, void *Data)
Creates a buffer via the currently registered backend factory.
int GetRootNodeIndex() const
Definition Skeleton.hpp:84
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
static std::pair< int, float > FindKeyframe(const std::vector< Keyframe< T > > &keys, float time)
Finds the keyframe pair straddling time and the lerp factor between them.
static Math::Vector3D LerpVec3(const Math::Vector3D &a, const Math::Vector3D &b, float t)
Componentwise vector lerp.