SleakEngine 0.1.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.
friend class GameObject
Definition Component.hpp:88
GameObject * owner
Definition Component.hpp:84
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:362
float GetX() const
Definition Vector.hpp:361
float GetZ() const
Definition Vector.hpp:363
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.