VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
skeleton.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3#include "skeleton.h"
4
5#include <algorithm>
6#include <cmath>
7
8namespace visutwin::canvas
9{
10 namespace
11 {
12 void addInterpolatedKeysRecursive(GraphNode* node,
13 std::vector<InterpolatedKey>& interpolatedKeys,
14 std::unordered_map<std::string, size_t>& interpolatedKeyDict,
15 std::unordered_map<std::string, int>& currKeyIndices)
16 {
17 if (!node) {
18 return;
19 }
20
21 InterpolatedKey interpKey;
22 interpKey.name = node->name();
23 interpolatedKeyDict[interpKey.name] = interpolatedKeys.size();
24 currKeyIndices[interpKey.name] = 0;
25 interpolatedKeys.push_back(interpKey);
26
27 for (auto* child : node->children()) {
28 addInterpolatedKeysRecursive(child, interpolatedKeys, interpolatedKeyDict, currKeyIndices);
29 }
30 }
31 }
32
34 {
35 addInterpolatedKeysRecursive(graph, _interpolatedKeys, _interpolatedKeyDict, _currKeyIndices);
36 }
37
39 {
40 _animation = value;
41 setCurrentTime(0.0f);
42 }
43
44 void Skeleton::setCurrentTime(const float value)
45 {
46 _time = value;
47
48 for (const auto& key : _interpolatedKeys) {
49 _currKeyIndices[key.name] = 0;
50 }
51
52 addTime(0.0f);
54 }
55
56 Vector3 Skeleton::lerpVec3(const Vector3& a, const Vector3& b, const float alpha)
57 {
58 return a + (b - a) * alpha;
59 }
60
61 Quaternion Skeleton::slerpQuat(const Quaternion& a, const Quaternion& b, const float alpha)
62 {
63 float ax = a.getX();
64 float ay = a.getY();
65 float az = a.getZ();
66 float aw = a.getW();
67
68 float bx = b.getX();
69 float by = b.getY();
70 float bz = b.getZ();
71 float bw = b.getW();
72
73 float dot = ax * bx + ay * by + az * bz + aw * bw;
74
75 if (dot < 0.0f) {
76 bx = -bx;
77 by = -by;
78 bz = -bz;
79 bw = -bw;
80 dot = -dot;
81 }
82
83 constexpr float epsilon = 1e-6f;
84 float scale0 = 1.0f - alpha;
85 float scale1 = alpha;
86
87 if ((1.0f - dot) > epsilon) {
88 const float theta = std::acos(std::clamp(dot, -1.0f, 1.0f));
89 const float invSinTheta = 1.0f / std::sin(theta);
90 scale0 = std::sin((1.0f - alpha) * theta) * invSinTheta;
91 scale1 = std::sin(alpha * theta) * invSinTheta;
92 }
93
94 Quaternion result(
95 scale0 * ax + scale1 * bx,
96 scale0 * ay + scale1 * by,
97 scale0 * az + scale1 * bz,
98 scale0 * aw + scale1 * bw);
99
100 return result.normalized();
101 }
102
103 void Skeleton::addTime(const float delta)
104 {
105 if (!_animation) {
106 return;
107 }
108
109 const auto& nodes = _animation->nodes();
110 const float duration = _animation->duration();
111
112 if ((_time == duration) && !_looping) {
113 return;
114 }
115
116 _time += delta;
117
118 if (_time > duration) {
119 _time = _looping ? 0.0f : duration;
120 for (const auto& node : nodes) {
121 _currKeyIndices[node.name()] = 0;
122 }
123 } else if (_time < 0.0f) {
124 _time = _looping ? duration : 0.0f;
125 for (const auto& node : nodes) {
126 _currKeyIndices[node.name()] = static_cast<int>(node.keys().size()) - 2;
127 }
128 }
129
130 const int offset = (delta >= 0.0f ? 1 : -1);
131
132 for (const auto& node : nodes) {
133 const std::string& nodeName = node.name();
134 const auto interpIt = _interpolatedKeyDict.find(nodeName);
135 if (interpIt == _interpolatedKeyDict.end()) {
136 continue;
137 }
138
139 InterpolatedKey& interpKey = _interpolatedKeys[interpIt->second];
140 const auto& keys = node.keys();
141 if (keys.empty()) {
142 continue;
143 }
144
145 bool foundKey = false;
146 if (keys.size() != 1) {
147 int currKeyIndex = _currKeyIndices[nodeName];
148 for (; currKeyIndex < static_cast<int>(keys.size()) - 1 && currKeyIndex >= 0; currKeyIndex += offset) {
149 const auto& k1 = keys[currKeyIndex];
150 const auto& k2 = keys[currKeyIndex + 1];
151
152 if ((k1.time <= _time) && (k2.time >= _time)) {
153 const float alpha = (k2.time > k1.time) ? ((_time - k1.time) / (k2.time - k1.time)) : 0.0f;
154
155 interpKey.pos = lerpVec3(k1.position, k2.position, alpha);
156 interpKey.quat = slerpQuat(k1.rotation, k2.rotation, alpha);
157 interpKey.scale = lerpVec3(k1.scale, k2.scale, alpha);
158 interpKey.written = true;
159
160 _currKeyIndices[nodeName] = currKeyIndex;
161 foundKey = true;
162 break;
163 }
164 }
165 }
166
167 if (keys.size() == 1 || (!foundKey && _time == 0.0f && _looping)) {
168 interpKey.pos = keys[0].position;
169 interpKey.quat = keys[0].rotation;
170 interpKey.scale = keys[0].scale;
171 interpKey.written = true;
172 }
173 }
174 }
175
176 void Skeleton::blend(const Skeleton* skel1, const Skeleton* skel2, const float alpha)
177 {
178 if (!skel1 || !skel2) {
179 return;
180 }
181
182 const size_t numNodes = std::min(_interpolatedKeys.size(), std::min(skel1->_interpolatedKeys.size(), skel2->_interpolatedKeys.size()));
183 for (size_t i = 0; i < numNodes; i++) {
184 const auto& key1 = skel1->_interpolatedKeys[i];
185 const auto& key2 = skel2->_interpolatedKeys[i];
186 auto& dstKey = _interpolatedKeys[i];
187
188 if (key1.written && key2.written) {
189 dstKey.quat = slerpQuat(key1.quat, key2.quat, alpha);
190 dstKey.pos = lerpVec3(key1.pos, key2.pos, alpha);
191 dstKey.scale = lerpVec3(key1.scale, key2.scale, alpha);
192 dstKey.written = true;
193 } else if (key1.written) {
194 dstKey.quat = key1.quat;
195 dstKey.pos = key1.pos;
196 dstKey.scale = key1.scale;
197 dstKey.written = true;
198 } else if (key2.written) {
199 dstKey.quat = key2.quat;
200 dstKey.pos = key2.pos;
201 dstKey.scale = key2.scale;
202 dstKey.written = true;
203 }
204 }
205 }
206
208 {
209 _graph = graph;
210
211 if (_graph) {
212 for (auto& interpKey : _interpolatedKeys) {
213 interpKey.setTarget(_graph->findByName(interpKey.name));
214 }
215 } else {
216 for (auto& interpKey : _interpolatedKeys) {
217 interpKey.setTarget(nullptr);
218 }
219 }
220 }
221
223 {
224 if (!_graph) {
225 return;
226 }
227
228 for (auto& interpKey : _interpolatedKeys) {
229 if (!interpKey.written) {
230 continue;
231 }
232
233 GraphNode* transform = interpKey.getTarget();
234 if (!transform) {
235 continue;
236 }
237
238 transform->setLocalPosition(interpKey.pos);
239 transform->setLocalRotation(interpKey.quat);
240 transform->setLocalScale(interpKey.scale);
241
242 interpKey.written = false;
243 }
244 }
245}
Hierarchical scene graph node with local/world transforms and parent-child relationships.
Definition graphNode.h:28
void setLocalPosition(float x, float y, float z)
void setLocalRotation(const Quaternion &rotation)
Definition graphNode.cpp:77
void setLocalScale(float x, float y, float z)
void blend(const Skeleton *skel1, const Skeleton *skel2, float alpha)
Definition skeleton.cpp:176
void setGraph(GraphNode *graph)
Definition skeleton.cpp:207
Skeleton(GraphNode *graph)
Definition skeleton.cpp:33
void addTime(float delta)
Definition skeleton.cpp:103
void setAnimation(Animation *value)
Definition skeleton.cpp:38
void setCurrentTime(float value)
Definition skeleton.cpp:44
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Definition vector3.h:29