13 std::vector<AnimationClip*> clips)
14 :
Component(
owner), m_skeleton(skeleton), m_clips(std::move(clips)) {
18 delete m_stateMachine;
22 if (!m_skeleton || m_skeleton->GetBoneCount() == 0) {
23 SLEAK_WARN(
"AnimatorComponent: No skeleton or empty skeleton");
27 int boneCount = m_skeleton->GetBoneCount();
32 uint32_t bufferSize =
static_cast<uint32_t
>(boneCount *
sizeof(
Math::Matrix4));
38 m_boneBuffer->SetSlot(3);
41 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
46 meshComp->AddConstantBuffer(m_boneBuffer);
48 SLEAK_WARN(
"AnimatorComponent: No sibling MeshComponent found");
52 SLEAK_INFO(
"AnimatorComponent: Initialized with {} bones, {} clips",
53 boneCount, m_clips.size());
69 ComputeBoneTransformsForClip(req.
clipA, req.
timeA, m_boneMatrices);
70 ComputeBoneTransformsForClip(req.
clipB, req.
timeB, m_boneMatricesB);
71 BlendBoneMatrices(m_boneMatrices, m_boneMatricesB,
75 ComputeBoneTransformsForClip(req.
clipA, req.
timeA, m_boneMatrices);
78 uint32_t bufferSize =
static_cast<uint32_t
>(
80 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
84 if (!m_playing || m_currentClip < 0)
92 if (m_currentTime > clip->
duration) {
94 m_currentTime = std::fmod(m_currentTime, clip->
duration);
101 ComputeBoneTransforms(m_currentTime);
103 uint32_t bufferSize =
static_cast<uint32_t
>(
105 m_boneBuffer->Update(m_boneMatrices.data(), bufferSize);
109 delete m_stateMachine;
111 return m_stateMachine;
116 m_clips.push_back(clip);
119void AnimatorComponent::ComputeBoneTransformsForClip(
AnimationClip* clip,
float animTime,
120 std::vector<Math::Matrix4>& outMatrices) {
124 ProcessNodeHierarchyForClip(rootIdx, identity, clip, animTime, outMatrices);
128void AnimatorComponent::ProcessNodeHierarchyForClip(
int nodeIndex,
131 std::vector<Math::Matrix4>& outMatrices) {
132 const NodeData& node = m_skeleton->GetNode(nodeIndex);
136 const AnimationChannel* channel = clip->FindChannel(node.name);
138 Math::Vector3D pos = InterpolatePosition(*channel, animTime);
139 Math::Quaternion rot = InterpolateRotation(*channel, animTime);
140 Math::Vector3D scl = InterpolateScale(*channel, animTime);
142 Math::Quaternion rotConj(rot.GetW(), -rot.GetX(), -rot.GetY(), -rot.GetZ());
148 nodeTransform = scaleMat * rotMat * transMat;
151 Math::Matrix4 globalTransform = nodeTransform * parentTransform;
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();
159 for (
int childIdx : node.children) {
160 ProcessNodeHierarchyForClip(childIdx, globalTransform, clip, animTime, outMatrices);
164void AnimatorComponent::BlendBoneMatrices(
const std::vector<Math::Matrix4>& a,
165 const std::vector<Math::Matrix4>& b,
167 std::vector<Math::Matrix4>& out) {
168 float w0 = 1.0f - 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;
179void AnimatorComponent::ComputeBoneTransforms(
float animTime) {
181 int rootIdx = m_skeleton->GetRootNodeIndex();
183 ProcessNodeHierarchy(rootIdx, identity, animTime);
187void AnimatorComponent::ProcessNodeHierarchy(
int nodeIndex,
190 const NodeData& node = m_skeleton->GetNode(nodeIndex);
191 AnimationClip* clip = m_clips[m_currentClip];
195 const AnimationChannel* channel = clip->FindChannel(node.name);
197 Math::Vector3D pos = InterpolatePosition(*channel, animTime);
198 Math::Quaternion rot = InterpolateRotation(*channel, animTime);
199 Math::Vector3D scl = InterpolateScale(*channel, animTime);
201 Math::Quaternion rotConj(rot.GetW(), -rot.GetX(), -rot.GetY(), -rot.GetZ());
207 nodeTransform = scaleMat * rotMat * transMat;
210 Math::Matrix4 globalTransform = nodeTransform * parentTransform;
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();
218 for (
int childIdx : node.children) {
219 ProcessNodeHierarchy(childIdx, globalTransform, animTime);
227 for (
int i = 0; i < static_cast<int>(keys.size()) - 1; ++i) {
228 if (time < keys[i + 1].time) { idx = i;
break; }
232 if (next >=
static_cast<int>(keys.size()))
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))};
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;
255 if (idx + 1 >=
static_cast<int>(keys.size()))
return keys[idx].value;
256 return LerpVec3(keys[idx].value, keys[idx + 1].value, t);
261 auto& keys = channel.rotationKeys;
262 if (keys.empty())
return Math::Quaternion();
263 if (keys.size() == 1)
return keys[0].value;
266 if (idx + 1 >=
static_cast<int>(keys.size()))
return keys[idx].value;
267 return Slerp(keys[idx].value, keys[idx + 1].value, t);
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;
277 if (idx + 1 >=
static_cast<int>(keys.size()))
return keys[idx].value;
278 return LerpVec3(keys[idx].value, keys[idx + 1].value, t);
283 float dot = a.
GetW() * b.GetW() + a.GetX() * b.GetX() +
284 a.GetY() * b.GetY() + a.GetZ() * b.GetZ();
286 Math::Quaternion b2 = b;
288 b2 = Math::Quaternion(-b.GetW(), -b.GetX(), -b.GetY(), -b.GetZ());
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);
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;
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);
315 for (
int i = 0; i < static_cast<int>(m_clips.size()); ++i) {
316 if (m_clips[i] && m_clips[i]->name == clipName) {
321 SLEAK_WARN(
"AnimatorComponent: Clip '{}' not found", clipName);
325 if (clipIndex < 0 || clipIndex >=
static_cast<int>(m_clips.size())) {
326 SLEAK_WARN(
"AnimatorComponent: Invalid clip index {}", clipIndex);
329 m_currentClip = clipIndex;
330 m_currentTime = 0.0f;
337 m_currentTime = 0.0f;
345 if (m_currentClip >= 0)
362 return m_currentTime;
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;
const std::string & GetCurrentClipName() const
AnimationStateMachine * CreateStateMachine()
Replaces any existing state machine with a fresh, empty one.
float GetCurrentTime() const
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.
void SetSpeed(float speed)
virtual bool Initialize() override
Allocates the bone constant buffer and attaches it to the sibling MeshComponent.
virtual ~AnimatorComponent()
Component(GameObject *object)
T * GetComponent()
Finds the first attached component of type T, or nullptr.
static Matrix< float, Rows, Rows > Identity()
static Matrix< float, 4, 4 > Rotate(const Quaternion &rotation)
static Matrix< float, 4, 4 > Scale(const Vector3D &scale)
static Matrix< float, 4, 4 > Translate(const Vector3D &translation)
Represents a quaternion for 3D rotations.
static BufferBase * CreateBuffer(BufferType Type, uint32_t Size, void *Data)
Creates a buffer via the currently registered backend factory.
int GetRootNodeIndex() const
Matrix< float, 4, 4 > Matrix4
Root namespace for everything the engine exposes.
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.