VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
animTrack.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3#include "animTrack.h"
4
5#include <algorithm>
6#include <cmath>
7
8namespace visutwin::canvas
9{
10 AnimTrack::AnimTrack(std::string name, const float duration) : _name(std::move(name)), _duration(duration)
11 {
12 }
13
14 Vector3 AnimTrack::lerpVec3(const Vector3& a, const Vector3& b, const float alpha)
15 {
16 return a + (b - a) * alpha;
17 }
18
19 Quaternion AnimTrack::slerpQuat(const Quaternion& a, const Quaternion& b, const float alpha)
20 {
21 float ax = a.getX();
22 float ay = a.getY();
23 float az = a.getZ();
24 float aw = a.getW();
25
26 float bx = b.getX();
27 float by = b.getY();
28 float bz = b.getZ();
29 float bw = b.getW();
30
31 float dot = ax * bx + ay * by + az * bz + aw * bw;
32 if (dot < 0.0f) {
33 bx = -bx;
34 by = -by;
35 bz = -bz;
36 bw = -bw;
37 dot = -dot;
38 }
39
40 constexpr float epsilon = 1e-6f;
41 float scale0 = 1.0f - alpha;
42 float scale1 = alpha;
43
44 if ((1.0f - dot) > epsilon) {
45 const float theta = std::acos(std::clamp(dot, -1.0f, 1.0f));
46 const float invSinTheta = 1.0f / std::sin(theta);
47 scale0 = std::sin((1.0f - alpha) * theta) * invSinTheta;
48 scale1 = std::sin(alpha * theta) * invSinTheta;
49 }
50
51 return Quaternion(
52 scale0 * ax + scale1 * bx,
53 scale0 * ay + scale1 * by,
54 scale0 * az + scale1 * bz,
55 scale0 * aw + scale1 * bw).normalized();
56 }
57
58 float AnimTrack::hermite(const float t, const float p0, const float m0, const float p1, const float m1)
59 {
60 const float t2 = t * t;
61 const float t3 = t2 * t;
62 return (2.0f * t3 - 3.0f * t2 + 1.0f) * p0 +
63 (t3 - 2.0f * t2 + t) * m0 +
64 (-2.0f * t3 + 3.0f * t2) * p1 +
65 (t3 - t2) * m1;
66 }
67
68 void AnimTrack::eval(float time, std::unordered_map<std::string, AnimTransform>& transforms) const
69 {
70 if (_curves.empty()) {
71 return;
72 }
73
74 const float t = std::clamp(time, 0.0f, _duration);
75
76 for (const auto& curve : _curves) {
77 if (curve.inputIndex >= _inputs.size() || curve.outputIndex >= _outputs.size()) {
78 continue;
79 }
80
81 const auto& input = _inputs[curve.inputIndex];
82 const auto& output = _outputs[curve.outputIndex];
83 if (input.data.empty() || output.data.empty()) {
84 continue;
85 }
86
87 const size_t keyCount = input.count();
88 if (keyCount == 0) {
89 continue;
90 }
91
92 // Binary search for bracketing keyframe pair.
93 size_t i0 = 0;
94 float alpha = 0.0f;
95
96 if (t <= input.data[0]) {
97 i0 = 0;
98 alpha = 0.0f;
99 } else if (t >= input.data[keyCount - 1]) {
100 i0 = keyCount - 1;
101 alpha = 0.0f;
102 } else {
103 size_t lo = 0, hi = keyCount - 1;
104 while (lo + 1 < hi) {
105 const size_t mid = (lo + hi) / 2;
106 if (input.data[mid] <= t) {
107 lo = mid;
108 } else {
109 hi = mid;
110 }
111 }
112 i0 = lo;
113 const float dt = input.data[i0 + 1] - input.data[i0];
114 alpha = (dt > 0.0f) ? (t - input.data[i0]) / dt : 0.0f;
115 }
116
117 auto& transform = transforms[curve.nodeName];
118 const int comp = output.components;
119
120 if (curve.interpolation == AnimInterpolation::STEP) {
121 // STEP: use value at i0 directly (no interpolation).
122 const float* v = &output.data[i0 * static_cast<size_t>(comp)];
123
124 if (curve.propertyPath == "localPosition") {
125 transform.position = Vector3(v[0], v[1], v[2]);
126 transform.hasPosition = true;
127 } else if (curve.propertyPath == "localRotation") {
128 transform.rotation = Quaternion(v[0], v[1], v[2], v[3]);
129 transform.hasRotation = true;
130 } else if (curve.propertyPath == "localScale") {
131 transform.scale = Vector3(v[0], v[1], v[2]);
132 transform.hasScale = true;
133 }
134
135 } else if (curve.interpolation == AnimInterpolation::LINEAR) {
136 const size_t i1 = std::min(i0 + 1, keyCount - 1);
137 const float* v0 = &output.data[i0 * static_cast<size_t>(comp)];
138 const float* v1 = &output.data[i1 * static_cast<size_t>(comp)];
139
140 if (curve.propertyPath == "localPosition") {
141 transform.position = lerpVec3(
142 Vector3(v0[0], v0[1], v0[2]),
143 Vector3(v1[0], v1[1], v1[2]), alpha);
144 transform.hasPosition = true;
145 } else if (curve.propertyPath == "localRotation") {
146 transform.rotation = slerpQuat(
147 Quaternion(v0[0], v0[1], v0[2], v0[3]),
148 Quaternion(v1[0], v1[1], v1[2], v1[3]), alpha);
149 transform.hasRotation = true;
150 } else if (curve.propertyPath == "localScale") {
151 transform.scale = lerpVec3(
152 Vector3(v0[0], v0[1], v0[2]),
153 Vector3(v1[0], v1[1], v1[2]), alpha);
154 transform.hasScale = true;
155 }
156
157 } else if (curve.interpolation == AnimInterpolation::CUBIC) {
158 // glTF CUBICSPLINE: output has 3 values per keyframe (in-tangent, value, out-tangent).
159 // Each group = comp * 3 floats.
160 const size_t i1 = std::min(i0 + 1, keyCount - 1);
161 const float timeDelta = input.data[std::min(i0 + 1, keyCount - 1)] - input.data[i0];
162 const int stride3 = comp * 3;
163 const float* g0 = &output.data[i0 * static_cast<size_t>(stride3)];
164 const float* g1 = &output.data[i1 * static_cast<size_t>(stride3)];
165 const float* val0 = g0 + comp; // value at i0
166 const float* outTan0 = g0 + comp * 2; // out-tangent at i0
167 const float* val1 = g1 + comp; // value at i1
168 const float* inTan1 = g1; // in-tangent at i1
169
170 // Hermite spline per component.
171 float result[4];
172 for (int c = 0; c < comp && c < 4; ++c) {
173 result[c] = hermite(alpha, val0[c], outTan0[c] * timeDelta, val1[c], inTan1[c] * timeDelta);
174 }
175
176 if (curve.propertyPath == "localPosition") {
177 transform.position = Vector3(result[0], result[1], result[2]);
178 transform.hasPosition = true;
179 } else if (curve.propertyPath == "localRotation") {
180 transform.rotation = Quaternion(result[0], result[1], result[2], result[3]).normalized();
181 transform.hasRotation = true;
182 } else if (curve.propertyPath == "localScale") {
183 transform.scale = Vector3(result[0], result[1], result[2]);
184 transform.hasScale = true;
185 }
186 }
187 }
188 }
189}
const std::string & name() const
Definition animTrack.h:59
void eval(float time, std::unordered_map< std::string, AnimTransform > &transforms) const
Definition animTrack.cpp:68
Unit quaternion for rotation representation with SIMD-accelerated slerp and multiply.
Definition quaternion.h:20
Quaternion normalized() const
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Definition vector3.h:29