载入中...
搜索中...
未找到
ControlPose.cpp
浏览该文件的文档.
2
4#include "common/Exception.h"
5
6#include <algorithm>
7#include <cmath>
8
9namespace eve::animation {
10
11ControlPose::ControlPose(AnimSkeleton *skeleton) : skeleton_(skeleton) {
12 refreshCoeffs();
13 const int n = skeleton_ ? skeleton_->getBoneCount() : 0;
14 ensureBones(n);
15 if (skeleton_ && n > 0) {
16 skeleton_->applyBindPose(&pose_);
17 skeleton_->applyBindPose(&target_);
18 readTargetIntoState(&target_, true);
19 hasTarget_ = true;
20 writePoseFromState();
21 }
22}
23
24void ControlPose::setFrequency(float frequencyHz) {
25 frequencyHz_ = frequencyHz;
26 refreshCoeffs();
27}
28
29void ControlPose::setDamping(float dampingZeta) {
30 dampingZeta_ = dampingZeta;
31 refreshCoeffs();
32}
33
34void ControlPose::setResponse(float response) {
35 response_ = response;
36 refreshCoeffs();
37}
38
39void ControlPose::setIntegrator(const std::string &kind) {
40 if (kind == "secondOrder") {
41 integrator_ = Integrator::SecondOrder;
42 } else if (kind == "spring") {
43 integrator_ = Integrator::Spring;
44 } else if (kind == "pd") {
45 integrator_ = Integrator::Pd;
46 } else {
47 throw Exception("ControlPose::setIntegrator: unknown kind '%s' (expected secondOrder|spring|pd)",
48 kind.c_str());
49 }
50}
51
52std::string ControlPose::getIntegrator() const {
53 switch (integrator_) {
54 case Integrator::SecondOrder: return "secondOrder";
55 case Integrator::Spring: return "spring";
56 case Integrator::Pd: return "pd";
57 }
58 return "secondOrder";
59}
60
61void ControlPose::refreshCoeffs() {
62 coeffs_ = makeSecondOrderCoeffs(frequencyHz_, dampingZeta_, response_);
63 pdGainsFromOmegaZeta(hzToOmega(frequencyHz_), dampingZeta_, kp_, kd_);
64}
65
66void ControlPose::ensureBones(int count) {
67 if (count < 0) count = 0;
68 // AnimPose::resize always resets locals to identity — only call when size changes.
69 if (pose_.getBoneCount() != count) pose_.resize(count);
70 if (target_.getBoneCount() != count) target_.resize(count);
71 if (static_cast<int>(bones_.size()) != count) {
72 bones_.resize(static_cast<size_t>(count));
73 }
74}
75
76void ControlPose::setBoneWeight(int boneIndex, float weight) {
77 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return;
78 bones_[static_cast<size_t>(boneIndex)].weight = clampf(weight, 0.f, 1.f);
79}
80
81float ControlPose::getBoneWeight(int boneIndex) const {
82 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return 0.f;
83 return bones_[static_cast<size_t>(boneIndex)].weight;
84}
85
86void ControlPose::readTargetIntoState(const AnimPose *target, bool snap) {
87 if (!target) return;
88 const int n = target->getBoneCount();
89 ensureBones(n);
90 for (int i = 0; i < n; ++i) {
91 const TransformTRS &t = target->local(i);
92 BoneState &b = bones_[static_cast<size_t>(i)];
93
94 auto assign = [snap](ScalarState &s, float value) {
95 s.x = value;
96 if (!s.hasPrev || snap) {
97 s.y = value;
98 s.yd = 0.f;
99 s.xp = value;
100 s.hasPrev = true;
101 }
102 };
103
104 assign(b.px, t.px);
105 assign(b.py, t.py);
106 assign(b.pz, t.pz);
107
108 // Shortest-path quaternion: flip target if needed relative to current.
109 float tqx = t.qx, tqy = t.qy, tqz = t.qz, tqw = t.qw;
110 if (b.qw.hasPrev) {
111 const float dot = b.qx.y * tqx + b.qy.y * tqy + b.qz.y * tqz + b.qw.y * tqw;
112 if (dot < 0.f) {
113 tqx = -tqx;
114 tqy = -tqy;
115 tqz = -tqz;
116 tqw = -tqw;
117 }
118 }
119 assign(b.qx, tqx);
120 assign(b.qy, tqy);
121 assign(b.qz, tqz);
122 assign(b.qw, tqw);
123
124 assign(b.sx, t.sx);
125 assign(b.sy, t.sy);
126 assign(b.sz, t.sz);
127 }
128}
129
131 if (!target) {
132 throw Exception("ControlPose::setTargetPose: target is null");
133 }
134 target_.copyFrom(target);
135 readTargetIntoState(&target_, false);
136 hasTarget_ = true;
137 writePoseFromState();
138}
139
141 if (!hasTarget_) return;
142 readTargetIntoState(&target_, true);
143 writePoseFromState();
144}
145
147 writePoseFromState();
148 return &pose_;
149}
150
152
153void ControlPose::stepScalar(float dt, float xd, ScalarState &s) {
154 const float omega = hzToOmega(frequencyHz_);
155 switch (integrator_) {
156 case Integrator::SecondOrder:
157 stepSecondOrder(dt, s.x, xd, coeffs_, s.y, s.yd);
158 break;
159 case Integrator::Spring:
160 stepDampedSpring(dt, s.x, omega, dampingZeta_, s.y, s.yd);
161 break;
162 case Integrator::Pd:
163 stepPd(dt, s.x, xd, kp_, kd_, s.y, s.yd);
164 break;
165 }
166}
167
168void ControlPose::writePoseFromState() {
169 const int n = static_cast<int>(bones_.size());
170 pose_.resize(n);
171 for (int i = 0; i < n; ++i) {
172 const BoneState &b = bones_[static_cast<size_t>(i)];
173 TransformTRS trs;
174 trs.px = b.px.y;
175 trs.py = b.py.y;
176 trs.pz = b.pz.y;
177 trs.qx = b.qx.y;
178 trs.qy = b.qy.y;
179 trs.qz = b.qz.y;
180 trs.qw = b.qw.y;
181 trs.sx = b.sx.y;
182 trs.sy = b.sy.y;
183 trs.sz = b.sz.y;
184 trs.normalizeRotation();
185
186 if (b.weight < 1.f - 1e-6f) {
187 // Blend dynamics output toward hard target.
188 TransformTRS hard = target_.local(i);
189 // Shortest path for hard target vs dynamics result.
190 const float dot =
191 trs.qx * hard.qx + trs.qy * hard.qy + trs.qz * hard.qz + trs.qw * hard.qw;
192 if (dot < 0.f) {
193 hard.qx = -hard.qx;
194 hard.qy = -hard.qy;
195 hard.qz = -hard.qz;
196 hard.qw = -hard.qw;
197 }
198 trs = blendTRS(hard, trs, b.weight);
199 }
200 pose_.local(i) = trs;
201 }
202}
203
204void ControlPose::update(float dt) {
205 if (dt <= 0.f || !hasTarget_) return;
206
207 // Refresh targets from stored target_ (weights / integrator may have changed).
208 const int n = target_.getBoneCount();
209 ensureBones(n);
210 for (int i = 0; i < n; ++i) {
211 const TransformTRS &t = target_.local(i);
212 BoneState &b = bones_[static_cast<size_t>(i)];
213
214 auto prep = [](ScalarState &s, float value) {
215 if (!s.hasPrev) {
216 s.y = value;
217 s.yd = 0.f;
218 s.xp = value;
219 s.hasPrev = true;
220 }
221 s.x = value;
222 };
223
224 prep(b.px, t.px);
225 prep(b.py, t.py);
226 prep(b.pz, t.pz);
227
228 float tqx = t.qx, tqy = t.qy, tqz = t.qz, tqw = t.qw;
229 const float dot = b.qx.y * tqx + b.qy.y * tqy + b.qz.y * tqz + b.qw.y * tqw;
230 if (dot < 0.f) {
231 tqx = -tqx;
232 tqy = -tqy;
233 tqz = -tqz;
234 tqw = -tqw;
235 }
236 prep(b.qx, tqx);
237 prep(b.qy, tqy);
238 prep(b.qz, tqz);
239 prep(b.qw, tqw);
240
241 prep(b.sx, t.sx);
242 prep(b.sy, t.sy);
243 prep(b.sz, t.sz);
244
245 auto advance = [&](ScalarState &s) {
246 float xd = 0.f;
247 if (s.hasPrev && dt > 1e-8f) xd = (s.x - s.xp) / dt;
248 s.xp = s.x;
249 stepScalar(dt, xd, s);
250 };
251
252 if (b.weight <= 1e-6f) {
253 // Hard follow
254 auto hard = [](ScalarState &s) {
255 s.y = s.x;
256 s.yd = 0.f;
257 s.xp = s.x;
258 };
259 hard(b.px);
260 hard(b.py);
261 hard(b.pz);
262 hard(b.qx);
263 hard(b.qy);
264 hard(b.qz);
265 hard(b.qw);
266 hard(b.sx);
267 hard(b.sy);
268 hard(b.sz);
269 } else {
270 advance(b.px);
271 advance(b.py);
272 advance(b.pz);
273 advance(b.qx);
274 advance(b.qy);
275 advance(b.qz);
276 advance(b.qw);
277 // Renormalize quaternion state after component integration.
278 {
279 float len = std::sqrt(b.qx.y * b.qx.y + b.qy.y * b.qy.y + b.qz.y * b.qz.y +
280 b.qw.y * b.qw.y);
281 if (len > 1e-8f) {
282 const float inv = 1.f / len;
283 b.qx.y *= inv;
284 b.qy.y *= inv;
285 b.qz.y *= inv;
286 b.qw.y *= inv;
287 } else {
288 b.qx.y = 0.f;
289 b.qy.y = 0.f;
290 b.qz.y = 0.f;
291 b.qw.y = 1.f;
292 }
293 }
294 advance(b.sx);
295 advance(b.sy);
296 advance(b.sz);
297 }
298 }
299 writePoseFromState();
300}
301
302} // namespace eve::animation
Tok kind
std::string value
glm::vec3 n
Definition Grass.cpp:64
uint32_t b
uint32_t s
Definition Weather.cpp:28
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:15
int getBoneCount() const
Definition AnimPose.h:25
TransformTRS & local(int boneIndex)
Definition AnimPose.cpp:74
void copyFrom(const AnimPose *other)
Definition AnimPose.cpp:56
void resize(int boneCount)
Definition AnimPose.cpp:44
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
void setBoneWeight(int boneIndex, float weight)
Per-bone blend weight in [0,1]; 1 = full dynamics, 0 = hard snap to target.
void setDamping(float dampingZeta)
float getBoneWeight(int boneIndex) const
void setTargetPose(const AnimPose *target)
Copy target pose. Channels without prior state snap; existing state keeps momentum.
void snapToTarget()
Snap current state to the last target without changing the target.
ControlPose(AnimSkeleton *skeleton)
std::string getIntegrator() const
void setResponse(float response)
void setFrequency(float frequencyHz)
void setIntegrator(const std::string &kind)
TransformTRS blendTRS(const TransformTRS &a, const TransformTRS &b, float t)
Definition AnimMath.h:72
void stepPd(float dt, float target, float targetVel, float kp, float kd, float &y, float &yd)
Semi-implicit Euler PD step (unit mass): a = Kp (target − y) + Kd (targetVel − yd),...
void stepSecondOrder(float dt, float x, float xd, const SecondOrderCoeffs &c, float &y, float &yd)
Stable semi-implicit Euler step for a second-order tracker. Estimates input velocity from consecutive...
void pdGainsFromOmegaZeta(float omega, float zeta, float &kp, float &kd)
Unit-mass PD gains from natural frequency ω (rad/s) and damping ratio ζ.
SecondOrderCoeffs makeSecondOrderCoeffs(float frequencyHz, float dampingZeta, float response)
Build second-order coefficients from frequency (Hz), damping ζ, response r.
float hzToOmega(float frequencyHz)
ω (rad/s) from frequency in Hz.
float clampf(float v, float lo, float hi)
Definition AnimMath.h:31
void stepDampedSpring(float dt, float target, float angularFrequency, float dampingRatio, float &pos, float &vel)
Advance position/velocity relative to a set-point using closed-form spring step.
Local TRS used by skeletal animation (quaternion xyzw).
Definition AnimMath.h:9