Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
Pose.cpp
Go to the documentation of this file.
1#include "animation/Pose.h"
2
3#include <algorithm>
4
5#include <glm/ext/matrix_transform.hpp>
6#include <glm/gtc/quaternion.hpp>
7#include <glm/gtc/type_ptr.hpp>
8
11
12namespace mnd
13{
14
15namespace
16{
17template <typename Key>
18usize FindKeyIndex(const std::vector<Key> &keys, f32 time)
19{
20 for (usize i = 0; i + 1 < keys.size(); ++i)
21 {
22 if (time < keys[i + 1].time)
23 {
24 return i;
25 }
26 }
27 return keys.empty() ? 0 : keys.size() - 1;
28}
29
30glm::vec3 InterpolateVec3(const std::vector<KeyFrameVec3> &keys, f32 time)
31{
32 if (keys.empty())
33 {
34 return glm::vec3(0.0f);
35 }
36 if (keys.size() == 1)
37 {
38 return keys.front().value;
39 }
40 auto i0 = FindKeyIndex(keys, time);
41 auto i1 = std::min(i0 + 1, keys.size() - 1);
42 if (i0 == i1)
43 {
44 return keys[i0].value;
45 }
46 f32 dt = keys[i1].time - keys[i0].time;
47 f32 t = dt > 0.0f ? (time - keys[i0].time) / dt : 0.0f;
48 t = std::clamp(t, 0.0f, 1.0f);
49 return glm::mix(keys[i0].value, keys[i1].value, t);
50}
51
52glm::quat InterpolateQuat(const std::vector<KeyFrameQuat> &keys, f32 time)
53{
54 if (keys.empty())
55 {
56 return glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
57 }
58 if (keys.size() == 1)
59 {
60 return keys.front().value;
61 }
62 auto i0 = FindKeyIndex(keys, time);
63 auto i1 = std::min(i0 + 1, keys.size() - 1);
64 if (i0 == i1)
65 {
66 return keys[i0].value;
67 }
68 f32 dt = keys[i1].time - keys[i0].time;
69 f32 t = dt > 0.0f ? (time - keys[i0].time) / dt : 0.0f;
70 t = std::clamp(t, 0.0f, 1.0f);
71 return glm::slerp(keys[i0].value, keys[i1].value, t);
72}
73
74glm::mat4 ComposeTRS(const glm::vec3 &translation, const glm::quat &rotation, const glm::vec3 &scale)
75{
76 glm::mat4 mat(1.0f);
77 mat = glm::translate(mat, translation);
78 mat = mat * glm::mat4_cast(rotation);
79 mat = glm::scale(mat, scale);
80 return mat;
81}
82} // namespace
83
84void ResetPoseToBind(const Skeleton &skeleton, Pose &pose)
85{
86 const auto &bones = skeleton.GetBones();
87 pose.localTransforms.resize(bones.size());
88 pose.finalPalette.resize(bones.size());
89 for (usize i = 0; i < bones.size(); ++i)
90 {
91 pose.localTransforms[i] = bones[i].localBind;
92 pose.finalPalette[i] = glm::mat4(1.0f);
93 }
94}
95
96void EvaluatePose(const SkeletalAnimationClip &clip, const Skeleton &skeleton, f32 time, Pose &pose)
97{
98 const auto &bones = skeleton.GetBones();
99 if (pose.localTransforms.size() != bones.size())
100 {
101 ResetPoseToBind(skeleton, pose);
102 } else
103 {
104 for (usize i = 0; i < bones.size(); ++i)
105 {
106 pose.localTransforms[i] = bones[i].localBind;
107 }
108 }
109
110 for (const auto &track : clip.tracks)
111 {
112 if (track.boneIndex < 0 || static_cast<usize>(track.boneIndex) >= bones.size())
113 {
114 continue;
115 }
116 // Missing channels fall back to the bone's bind component, not zero —
117 // otherwise a rotation-only track would collapse the bone's bind translation/scale.
118 const Bone &bone = bones[track.boneIndex];
119 glm::vec3 t = track.positions.empty() ? bone.bindPos : InterpolateVec3(track.positions, time);
120 glm::quat r = track.rotations.empty() ? bone.bindRot : InterpolateQuat(track.rotations, time);
121 glm::vec3 s = track.scales.empty() ? bone.bindScale : InterpolateVec3(track.scales, time);
122
123 pose.localTransforms[track.boneIndex] = ComposeTRS(t, r, s);
124 }
125}
126
127void ComputePalette(const Skeleton &skeleton, Pose &pose)
128{
129 const auto &bones = skeleton.GetBones();
130 if (pose.localTransforms.size() != bones.size())
131 {
132 ResetPoseToBind(skeleton, pose);
133 }
134 pose.finalPalette.resize(bones.size());
135
136 std::vector<glm::mat4> globalTransforms(bones.size(), glm::mat4(1.0f));
137
138 for (usize i = 0; i < bones.size(); ++i)
139 {
140 const auto &bone = bones[i];
141 if (bone.parentIndex < 0)
142 {
143 globalTransforms[i] = pose.localTransforms[i];
144 } else
145 {
146 globalTransforms[i] = globalTransforms[bone.parentIndex] * pose.localTransforms[i];
147 }
148 pose.finalPalette[i] = globalTransforms[i] * bone.offsetMatrix;
149 }
150}
151
152} // namespace mnd
Per-frame skeletal pose: local bone transforms + final GPU palette.
Per-bone TRS keyframe tracks driving a Skeleton.
Bone hierarchy + bind/offset matrices for GPU-skinned meshes.
const std::vector< Bone > & GetBones() const
Definition Skeleton.h:44
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
size_t usize
Platform-native unsigned size type.
Definition Types.h:43
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
glm::vec3 bindPos
Definition Skeleton.h:32
glm::quat bindRot
Definition Skeleton.h:33
glm::vec3 bindScale
Definition Skeleton.h:34
Per-frame pose state for a Skeleton.
Definition Pose.h:30
std::vector< glm::mat4 > localTransforms
Animated TRS per bone (parent-relative).
Definition Pose.h:31
std::vector< glm::mat4 > finalPalette
Skinning matrices: globalPose * offsetMatrix.
Definition Pose.h:32
std::vector< BoneTrack > tracks