载入中...
搜索中...
未找到
ControlPose.cpp
浏览该文件的文档.
2
5#include "common/Exception.h"
6
7#include <algorithm>
8#include <cmath>
9
10namespace eve::animation {
11
12ControlPose::ControlPose(AnimSkeleton *skeleton) : skeleton_(skeleton) {
13 refreshCoeffs();
14 const int n = skeleton_ ? skeleton_->getBoneCount() : 0;
15 ensureBones(n);
16 if (skeleton_ && n > 0) {
17 skeleton_->applyBindPose(&pose_);
18 skeleton_->applyBindPose(&target_);
19 readTargetIntoState(&target_, true);
20 hasTarget_ = true;
21 writePoseFromState();
22 }
23}
24
25void ControlPose::setFrequency(float frequencyHz) {
26 frequencyHz_ = frequencyHz;
27 refreshCoeffs();
28}
29
30void ControlPose::setDamping(float dampingZeta) {
31 dampingZeta_ = dampingZeta;
32 refreshCoeffs();
33}
34
35void ControlPose::setResponse(float response) {
36 response_ = response;
37 refreshCoeffs();
38}
39
40void ControlPose::setIntegrator(const std::string &kind) {
41 if (kind == "secondOrder") {
42 integrator_ = Integrator::SecondOrder;
43 } else if (kind == "spring") {
44 integrator_ = Integrator::Spring;
45 } else if (kind == "pd") {
46 integrator_ = Integrator::Pd;
47 } else {
48 throw Exception("ControlPose::setIntegrator: unknown kind '%s' (expected secondOrder|spring|pd)",
49 kind.c_str());
50 }
51}
52
53std::string ControlPose::getIntegrator() const {
54 switch (integrator_) {
55 case Integrator::SecondOrder: return "secondOrder";
56 case Integrator::Spring: return "spring";
57 case Integrator::Pd: return "pd";
58 }
59 return "secondOrder";
60}
61
62void ControlPose::refreshCoeffs() {
63 coeffs_ = makeSecondOrderCoeffs(frequencyHz_, dampingZeta_, response_);
64 pdGainsFromOmegaZeta(hzToOmega(frequencyHz_), dampingZeta_, kp_, kd_);
65}
66
67void ControlPose::ensureBones(int count) {
68 if (count < 0) count = 0;
69 // AnimPose::resize always resets locals to identity — only call when size changes.
70 if (pose_.getBoneCount() != count) pose_.resize(count);
71 if (target_.getBoneCount() != count) target_.resize(count);
72 if (static_cast<int>(bones_.size()) != count) {
73 bones_.resize(static_cast<size_t>(count));
74 }
75}
76
77void ControlPose::setBoneWeight(int boneIndex, float weight) {
78 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return;
79 bones_[static_cast<size_t>(boneIndex)].weight = clampf(weight, 0.f, 1.f);
80}
81
82float ControlPose::getBoneWeight(int boneIndex) const {
83 if (boneIndex < 0 || boneIndex >= static_cast<int>(bones_.size())) return 0.f;
84 return bones_[static_cast<size_t>(boneIndex)].weight;
85}
86
87void ControlPose::readTargetIntoState(const AnimPose *target, bool snap) {
88 if (!target) return;
89 const int n = target->getBoneCount();
90 ensureBones(n);
91 for (int i = 0; i < n; ++i) {
92 const TransformTRS &t = target->local(i);
93 BoneState &b = bones_[static_cast<size_t>(i)];
94
95 auto assign = [snap](ScalarState &s, float value) {
96 s.x = value;
97 if (!s.hasPrev || snap) {
98 s.y = value;
99 s.yd = 0.f;
100 s.xp = value;
101 s.hasPrev = true;
102 }
103 };
104
105 assign(b.px, t.px);
106 assign(b.py, t.py);
107 assign(b.pz, t.pz);
108
109 // Shortest-path quaternion: flip target if needed relative to current.
110 float tqx = t.qx, tqy = t.qy, tqz = t.qz, tqw = t.qw;
111 if (b.qw.hasPrev) {
112 const float dot = b.qx.y * tqx + b.qy.y * tqy + b.qz.y * tqz + b.qw.y * tqw;
113 if (dot < 0.f) {
114 tqx = -tqx;
115 tqy = -tqy;
116 tqz = -tqz;
117 tqw = -tqw;
118 }
119 }
120 assign(b.qx, tqx);
121 assign(b.qy, tqy);
122 assign(b.qz, tqz);
123 assign(b.qw, tqw);
124
125 assign(b.sx, t.sx);
126 assign(b.sy, t.sy);
127 assign(b.sz, t.sz);
128 }
129}
130
132 if (!target) {
133 throw Exception("ControlPose::setTargetPose: target is null");
134 }
135 target_.copyFrom(target);
136 readTargetIntoState(&target_, false);
137 hasTarget_ = true;
138 writePoseFromState();
139}
140
142 if (!hasTarget_) return;
143 readTargetIntoState(&target_, true);
144 writePoseFromState();
145}
146
148 writePoseFromState();
149 return &pose_;
150}
151
153
154void ControlPose::stepScalar(float dt, float xd, ScalarState &s) {
155 const float omega = hzToOmega(frequencyHz_);
156 switch (integrator_) {
157 case Integrator::SecondOrder:
158 stepSecondOrder(dt, s.x, xd, coeffs_, s.y, s.yd);
159 break;
160 case Integrator::Spring:
161 stepDampedSpring(dt, s.x, omega, dampingZeta_, s.y, s.yd);
162 break;
163 case Integrator::Pd:
164 stepPd(dt, s.x, xd, kp_, kd_, s.y, s.yd);
165 break;
166 }
167}
168
169void ControlPose::writePoseFromState() {
170 const int n = static_cast<int>(bones_.size());
171 pose_.resize(n);
172 for (int i = 0; i < n; ++i) {
173 const BoneState &b = bones_[static_cast<size_t>(i)];
174 TransformTRS trs;
175 trs.px = b.px.y;
176 trs.py = b.py.y;
177 trs.pz = b.pz.y;
178 trs.qx = b.qx.y;
179 trs.qy = b.qy.y;
180 trs.qz = b.qz.y;
181 trs.qw = b.qw.y;
182 trs.sx = b.sx.y;
183 trs.sy = b.sy.y;
184 trs.sz = b.sz.y;
185 trs.normalizeRotation();
186
187 if (b.weight < 1.f - 1e-6f) {
188 // Blend dynamics output toward hard target.
189 TransformTRS hard = target_.local(i);
190 // Shortest path for hard target vs dynamics result.
191 const float dot =
192 trs.qx * hard.qx + trs.qy * hard.qy + trs.qz * hard.qz + trs.qw * hard.qw;
193 if (dot < 0.f) {
194 hard.qx = -hard.qx;
195 hard.qy = -hard.qy;
196 hard.qz = -hard.qz;
197 hard.qw = -hard.qw;
198 }
199 trs = blendTRS(hard, trs, b.weight);
200 }
201 pose_.local(i) = trs;
202 }
203}
204
205void ControlPose::updateUnchecked(float dt) {
206 if (dt <= 0.f || !hasTarget_) return;
207
208 // Refresh targets from stored target_ (weights / integrator may have changed).
209 const int n = target_.getBoneCount();
210 ensureBones(n);
211 for (int i = 0; i < n; ++i) {
212 const TransformTRS &t = target_.local(i);
213 BoneState &b = bones_[static_cast<size_t>(i)];
214
215 auto prep = [](ScalarState &s, float value) {
216 if (!s.hasPrev) {
217 s.y = value;
218 s.yd = 0.f;
219 s.xp = value;
220 s.hasPrev = true;
221 }
222 s.x = value;
223 };
224
225 prep(b.px, t.px);
226 prep(b.py, t.py);
227 prep(b.pz, t.pz);
228
229 float tqx = t.qx, tqy = t.qy, tqz = t.qz, tqw = t.qw;
230 const float dot = b.qx.y * tqx + b.qy.y * tqy + b.qz.y * tqz + b.qw.y * tqw;
231 if (dot < 0.f) {
232 tqx = -tqx;
233 tqy = -tqy;
234 tqz = -tqz;
235 tqw = -tqw;
236 }
237 prep(b.qx, tqx);
238 prep(b.qy, tqy);
239 prep(b.qz, tqz);
240 prep(b.qw, tqw);
241
242 prep(b.sx, t.sx);
243 prep(b.sy, t.sy);
244 prep(b.sz, t.sz);
245
246 auto advance = [&](ScalarState &s) {
247 float xd = 0.f;
248 if (s.hasPrev && dt > 1e-8f) xd = (s.x - s.xp) / dt;
249 s.xp = s.x;
250 stepScalar(dt, xd, s);
251 };
252
253 if (b.weight <= 1e-6f) {
254 // Hard follow
255 auto hard = [](ScalarState &s) {
256 s.y = s.x;
257 s.yd = 0.f;
258 s.xp = s.x;
259 };
260 hard(b.px);
261 hard(b.py);
262 hard(b.pz);
263 hard(b.qx);
264 hard(b.qy);
265 hard(b.qz);
266 hard(b.qw);
267 hard(b.sx);
268 hard(b.sy);
269 hard(b.sz);
270 } else {
271 advance(b.px);
272 advance(b.py);
273 advance(b.pz);
274 advance(b.qx);
275 advance(b.qy);
276 advance(b.qz);
277 advance(b.qw);
278 // Renormalize quaternion state after component integration.
279 {
280 float len = std::sqrt(b.qx.y * b.qx.y + b.qy.y * b.qy.y + b.qz.y * b.qz.y +
281 b.qw.y * b.qw.y);
282 if (len > 1e-8f) {
283 const float inv = 1.f / len;
284 b.qx.y *= inv;
285 b.qy.y *= inv;
286 b.qz.y *= inv;
287 b.qw.y *= inv;
288 } else {
289 b.qx.y = 0.f;
290 b.qy.y = 0.f;
291 b.qz.y = 0.f;
292 b.qw.y = 1.f;
293 }
294 }
295 advance(b.sx);
296 advance(b.sy);
297 advance(b.sz);
298 }
299 }
300 writePoseFromState();
301}
302
304 auto seconds = detail::secondsForStep(step, hasLastTick_, lastTick_, "ControlPose");
305 if (!seconds) return eve::Result<void>::failure(seconds.status());
306 updateUnchecked(std::move(seconds).takeValue());
307 lastTick_ = step.tick;
308 hasLastTick_ = true;
310}
311
312void ControlPose::update(float dt) {
313 auto step = detail::legacyStep(dt, hasLastTick_, lastTick_, "ControlPose");
314 if (!step) {
315 step.ignore("legacy ControlPose update");
316 return;
317 }
318 advance(std::move(step).takeValue()).ignore("legacy ControlPose update");
319}
320
321} // namespace eve::animation
LogicalId target
double value
const std::string & s
glm::vec3 n
Definition Grass.cpp:63
TokenKind kind
MeleePoint3 b
Definition MeleeHit.cpp:41
float t
std::uint32_t count
float step
Definition TreeMesh.cpp:314
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:537
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
int getBoneCount() const
Returns the bone count.
Definition AnimPose.h:36
TransformTRS & local(int boneIndex)
Local.
Definition AnimPose.cpp:126
void copyFrom(const AnimPose *other)
Copies from.
Definition AnimPose.cpp:91
void resize(int boneCount)
Resize.
Definition AnimPose.cpp:78
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int getBoneCount() const
Returns the bone count.
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)
Sets the damping.
float getBoneWeight(int boneIndex) const
Returns the bone weight.
void update(float dt)
Legacy seconds facade; explicitly forwards to advance().
void setTargetPose(const AnimPose *target)
Copy target pose. Channels without prior state snap; existing state keeps momentum.
AnimPose * getPose()
Returns the pose.
void snapToTarget()
Snap current state to the last target without changing the target.
ControlPose(AnimSkeleton *skeleton)
Control pose.
std::string getIntegrator() const
Returns the integrator.
void setResponse(float response)
Sets the response.
void setFrequency(float frequencyHz)
Sets the frequency.
eve::Result< void > advance(const eve::SimulationStep &step)
Advance pose dynamics by one scheduler-owned deterministic step.
AnimPose * getTargetPose()
Returns the target pose.
void setIntegrator(const std::string &kind)
Sets the integrator.
eve::Result< eve::SimulationStep > legacyStep(float seconds, bool hasLastTick, eve::SimulationTick lastTick, const char *owner)
Convert a legacy seconds call to the next local scheduler step.
eve::Result< float > secondsForStep(const eve::SimulationStep &step, bool hasLastTick, eve::SimulationTick lastTick, const char *owner)
Validate a scheduler step before an animation object mutates state.
TransformTRS blendTRS(const TransformTRS &a, const TransformTRS &b, float t)
Blend trs.
Definition AnimMath.h:79
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)
Clampf.
Definition AnimMath.h:34
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.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Local TRS used by skeletal animation (quaternion xyzw).
Definition AnimMath.h:9