载入中...
搜索中...
未找到
AnimStateMachine.cpp
浏览该文件的文档.
4
5#include "common/Capability.h"
6#include "common/Exception.h"
8
9#include <algorithm>
10#include <cmath>
11#include <utility>
12
13namespace eve::animation {
14namespace {
15
16std::vector<AnimStateMachine*>& machines() {
17 static std::vector<AnimStateMachine*> instances;
18 return instances;
19}
20
21double stateNumber(const StateValue& v) { return v.isInt() ? static_cast<double>(v.asInt()) : v.asDouble(); }
22
24class AnimStateProvider : public eve::caps::IStateProvider {
25public:
26 const char* stateKind() const override { return "anim"; }
27
28 bool captureState(StateValue& out) override {
29 StateValue arr = StateValue::array();
30 for (AnimStateMachine* m : machines()) {
31 StateValue sv;
32 if (m->captureState(sv)) arr.pushBack(std::move(sv));
33 }
34 out = std::move(arr);
35 return true;
36 }
37
38 bool restoreState(const StateValue& in, std::string* err) override {
39 if (!in.isArray()) {
40 if (err) *err = "anim: expected array of state machines";
41 return false;
42 }
43 const size_t n = std::min(in.arraySize(), machines().size());
44 for (size_t i = 0; i < n; ++i) {
45 if (!machines()[i]->restoreState(in.at(i), err)) return false;
46 }
47 return true;
48 }
49
50 bool resetToDefaults() override {
51 for (AnimStateMachine* m : machines()) m->resetToDefaults();
52 return true;
53 }
54};
55
56struct Register {
57 Register() {
58 static AnimStateProvider provider;
60 }
61} g_register;
62
63} // namespace
64
65AnimStateMachine::AnimStateMachine(AnimSkeleton *skeleton) : skeleton_(skeleton) {
66 if (!skeleton_) throw Exception("AnimStateMachine: skeleton is null");
67 pose_.resize(skeleton_->getBoneCount());
68 fromPose_.resize(skeleton_->getBoneCount());
69 toPose_.resize(skeleton_->getBoneCount());
70 skeleton_->applyBindPose(&pose_);
71 machines().push_back(this);
72}
73
75 machines().erase(std::remove(machines().begin(), machines().end(), this), machines().end());
76}
77
78void AnimStateMachine::requireState(const std::string &name) const {
79 if (states_.find(name) == states_.end()) {
80 throw Exception("AnimStateMachine: unknown state '%s'", name.c_str());
81 }
82}
83
84AnimStateMachine::Transition &AnimStateMachine::requireTransition(int id) {
85 if (id < 0 || id >= static_cast<int>(transitions_.size())) {
86 throw Exception("AnimStateMachine: invalid transition id %d", id);
87 }
88 return transitions_[static_cast<size_t>(id)];
89}
90
91AnimStateMachine::FloatOp AnimStateMachine::parseFloatOp(const std::string &op) {
92 if (op == ">") return FloatOp::Gt;
93 if (op == ">=") return FloatOp::Ge;
94 if (op == "<") return FloatOp::Lt;
95 if (op == "<=") return FloatOp::Le;
96 if (op == "==") return FloatOp::Eq;
97 if (op == "!=") return FloatOp::Ne;
98 throw Exception("AnimStateMachine: unknown float op '%s'", op.c_str());
99}
100
101void AnimStateMachine::addState(const std::string &name, AnimClip *clip) {
102 if (name.empty()) throw Exception("AnimStateMachine.addState: name empty");
103 if (!clip) throw Exception("AnimStateMachine.addState: clip is null");
104 if (states_.count(name)) {
105 throw Exception("AnimStateMachine.addState: duplicate state '%s'", name.c_str());
106 }
107 states_[name] = State{name, clip};
108 stateOrder_.push_back(name);
109}
110
111void AnimStateMachine::setEntry(const std::string &name) {
112 requireState(name);
113 currentState_ = name;
114 stateTime_ = 0.f;
115 blending_ = false;
116 started_ = true;
117 states_[name].clip->sample(0.f, &pose_, skeleton_);
118}
119
120bool AnimStateMachine::hasState(const std::string &name) const {
121 return states_.count(name) > 0;
122}
123
124int AnimStateMachine::addTransition(const std::string &from, const std::string &to,
125 float blendSeconds) {
126 requireState(from);
127 requireState(to);
128 if (blendSeconds < 0.f) {
129 throw Exception("AnimStateMachine.addTransition: blendSeconds must be >= 0");
130 }
131 Transition tr;
132 tr.from = from;
133 tr.to = to;
134 tr.blendSeconds = blendSeconds;
135 transitions_.push_back(tr);
136 return static_cast<int>(transitions_.size()) - 1;
137}
138
139void AnimStateMachine::addFloatCondition(int transitionId, const std::string &param,
140 const std::string &op, float threshold) {
141 if (param.empty()) throw Exception("AnimStateMachine.addFloatCondition: param empty");
142 Transition &tr = requireTransition(transitionId);
143 Condition c;
144 c.kind = CondKind::Float;
145 c.param = param;
146 c.op = parseFloatOp(op);
147 c.threshold = threshold;
148 tr.conditions.push_back(c);
149 floats_.emplace(param, 0.f);
150}
151
152void AnimStateMachine::addBoolCondition(int transitionId, const std::string &param, bool value) {
153 if (param.empty()) throw Exception("AnimStateMachine.addBoolCondition: param empty");
154 Transition &tr = requireTransition(transitionId);
155 Condition c;
156 c.kind = CondKind::Bool;
157 c.param = param;
158 c.boolValue = value;
159 tr.conditions.push_back(c);
160 bools_.emplace(param, false);
161}
162
163void AnimStateMachine::addTriggerCondition(int transitionId, const std::string &param) {
164 if (param.empty()) throw Exception("AnimStateMachine.addTriggerCondition: param empty");
165 Transition &tr = requireTransition(transitionId);
166 Condition c;
167 c.kind = CondKind::Trigger;
168 c.param = param;
169 tr.conditions.push_back(c);
170 triggers_.emplace(param, false);
171}
172
173void AnimStateMachine::setExitTime(int transitionId, float normalizedTime) {
174 Transition &tr = requireTransition(transitionId);
175 tr.exitTime = clampf(normalizedTime, 0.f, 1.f);
176 tr.hasExitTime = true;
177}
178
179void AnimStateMachine::setHasExitTime(int transitionId, bool enabled) {
180 requireTransition(transitionId).hasExitTime = enabled;
181}
182
183void AnimStateMachine::setFloat(const std::string &name, float value) { floats_[name] = value; }
184
185float AnimStateMachine::getFloat(const std::string &name) const {
186 auto it = floats_.find(name);
187 return it == floats_.end() ? 0.f : it->second;
188}
189
190void AnimStateMachine::setBool(const std::string &name, bool value) { bools_[name] = value; }
191
192bool AnimStateMachine::getBool(const std::string &name) const {
193 auto it = bools_.find(name);
194 return it == bools_.end() ? false : it->second;
195}
196
197void AnimStateMachine::setTrigger(const std::string &name) { triggers_[name] = true; }
198
199void AnimStateMachine::resetTrigger(const std::string &name) { triggers_[name] = false; }
200
202
203bool AnimStateMachine::conditionsMet(const Transition &tr) const {
204 for (const Condition &c : tr.conditions) {
205 switch (c.kind) {
206 case CondKind::Float: {
207 const float v = getFloat(c.param);
208 bool ok = false;
209 switch (c.op) {
210 case FloatOp::Gt: ok = v > c.threshold; break;
211 case FloatOp::Ge: ok = v >= c.threshold; break;
212 case FloatOp::Lt: ok = v < c.threshold; break;
213 case FloatOp::Le: ok = v <= c.threshold; break;
214 case FloatOp::Eq: ok = std::fabs(v - c.threshold) < 1e-5f; break;
215 case FloatOp::Ne: ok = std::fabs(v - c.threshold) >= 1e-5f; break;
216 }
217 if (!ok) return false;
218 break;
219 }
220 case CondKind::Bool: {
221 if (getBool(c.param) != c.boolValue) return false;
222 break;
223 }
224 case CondKind::Trigger: {
225 auto it = triggers_.find(c.param);
226 if (it == triggers_.end() || !it->second) return false;
227 break;
228 }
229 }
230 }
231 return true;
232}
233
234bool AnimStateMachine::tryTransition() {
235 if (currentState_.empty() || blending_) return false;
236 const State &cur = states_.at(currentState_);
237 const float dur = cur.clip ? cur.clip->getDuration() : 0.f;
238 const float norm = dur > 1e-8f ? clampf(stateTime_ / dur, 0.f, 1.f) : 1.f;
239
240 for (const Transition &tr : transitions_) {
241 if (tr.from != currentState_) continue;
242 if (tr.hasExitTime && norm < tr.exitTime) continue;
243 if (!conditionsMet(tr)) continue;
244
245 // Consume triggers used by this transition.
246 for (const Condition &c : tr.conditions) {
247 if (c.kind == CondKind::Trigger) triggers_[c.param] = false;
248 }
249
250 nextState_ = tr.to;
251 blendDuration_ = tr.blendSeconds;
252 blendElapsed_ = 0.f;
253 stateTime_ = 0.f;
254 if (blendDuration_ <= 1e-6f) {
255 currentState_ = nextState_;
256 blending_ = false;
257 states_.at(currentState_).clip->sample(0.f, &pose_, skeleton_);
258 } else {
259 // Switch logical state immediately; pose cross-fades from previous.
260 fromPose_.copyFrom(&pose_);
261 currentState_ = nextState_;
262 blending_ = true;
263 states_.at(currentState_).clip->sample(0.f, &toPose_, skeleton_);
264 pose_.blendFrom(&fromPose_, &toPose_, 0.f);
265 }
266 return true;
267 }
268 return false;
269}
270
272 if (dt < 0.f) throw Exception("AnimStateMachine.update: dt must be >= 0");
273 if (!started_) {
274 if (!stateOrder_.empty()) setEntry(stateOrder_.front());
275 else return;
276 }
277 if (currentState_.empty()) return;
278
279 if (blending_) {
280 blendElapsed_ += dt;
281 stateTime_ += dt;
282 const State &dst = states_.at(currentState_);
283 dst.clip->sample(stateTime_, &toPose_, skeleton_);
284 float t = blendDuration_ > 1e-8f ? blendElapsed_ / blendDuration_ : 1.f;
285 if (t >= 1.f) {
286 blending_ = false;
287 pose_.copyFrom(&toPose_);
288 } else {
289 // Keep fromPose frozen at transition start for stable crossfade.
290 pose_.blendFrom(&fromPose_, &toPose_, t);
291 }
292 // Allow chained transitions after blend completes next frame.
293 if (!blending_) tryTransition();
294 return;
295 }
296
297 stateTime_ += dt;
298 const State &st = states_.at(currentState_);
299 st.clip->sample(stateTime_, &pose_, skeleton_);
300 tryTransition();
301}
302
304 out = StateValue::object();
305 out.set("currentState", StateValue::string(currentState_));
306 out.set("nextState", StateValue::string(nextState_));
307 out.set("stateTime", StateValue::number(stateTime_));
308 out.set("blendDuration", StateValue::number(blendDuration_));
309 out.set("blendElapsed", StateValue::number(blendElapsed_));
310 out.set("blending", StateValue::boolean(blending_));
311 out.set("started", StateValue::boolean(started_));
312
314 for (const auto& kv : floats_) floats.set(kv.first, StateValue::number(kv.second));
315 out.set("floats", std::move(floats));
316
318 for (const auto& kv : bools_) bools.set(kv.first, StateValue::boolean(kv.second));
319 out.set("bools", std::move(bools));
320
321 StateValue triggers = StateValue::object();
322 for (const auto& kv : triggers_) triggers.set(kv.first, StateValue::boolean(kv.second));
323 out.set("triggers", std::move(triggers));
324 return true;
325}
326
327bool AnimStateMachine::restoreState(const StateValue& in, std::string* err) {
328 if (!in.isObject()) {
329 if (err) *err = "anim: state is not an object";
330 return false;
331 }
332 const StateValue* current = in.find("currentState");
333 const StateValue* started = in.find("started");
334 if (!current || !current->isString() || !started || !started->isBool()) {
335 if (err) *err = "anim: missing currentState/started";
336 return false;
337 }
338 const std::string stateName = current->asString();
339 const bool wasStarted = started->asBool();
340 if (wasStarted && !stateName.empty() && !hasState(stateName)) {
341 if (err) *err = "anim: unknown state '" + stateName + "'";
342 return false;
343 }
344
345 floats_.clear();
346 bools_.clear();
347 triggers_.clear();
348
349 if (const StateValue* floats = in.find("floats"); floats && floats->isObject()) {
350 for (const auto& key : floats->keys()) {
351 const StateValue* v = floats->find(key);
352 if (v && (v->isInt() || v->isFloat())) floats_[key] = static_cast<float>(stateNumber(*v));
353 }
354 }
355 if (const StateValue* bools = in.find("bools"); bools && bools->isObject()) {
356 for (const auto& key : bools->keys()) {
357 const StateValue* v = bools->find(key);
358 if (v && v->isBool()) bools_[key] = v->asBool();
359 }
360 }
361 if (const StateValue* triggers = in.find("triggers"); triggers && triggers->isObject()) {
362 for (const auto& key : triggers->keys()) {
363 const StateValue* v = triggers->find(key);
364 if (v && v->isBool()) triggers_[key] = v->asBool();
365 }
366 }
367
368 currentState_ = stateName;
369 if (const StateValue* ns = in.find("nextState"); ns && ns->isString()) nextState_ = ns->asString();
370 if (const StateValue* st = in.find("stateTime"); st && (st->isInt() || st->isFloat()))
371 stateTime_ = static_cast<float>(stateNumber(*st));
372 if (const StateValue* bd = in.find("blendDuration"); bd && (bd->isInt() || bd->isFloat()))
373 blendDuration_ = static_cast<float>(stateNumber(*bd));
374 if (const StateValue* be = in.find("blendElapsed"); be && (be->isInt() || be->isFloat()))
375 blendElapsed_ = static_cast<float>(stateNumber(*be));
376 if (const StateValue* bl = in.find("blending"); bl && bl->isBool()) blending_ = bl->asBool();
377 started_ = wasStarted;
378
379 if (started_ && !currentState_.empty()) {
380 states_.at(currentState_).clip->sample(stateTime_, &pose_, skeleton_);
381 }
382 return true;
383}
384
386 floats_.clear();
387 bools_.clear();
388 triggers_.clear();
389 blending_ = false;
390 nextState_.clear();
391 stateTime_ = 0.f;
392 blendDuration_ = 0.f;
393 blendElapsed_ = 0.f;
394 if (!stateOrder_.empty()) {
395 setEntry(stateOrder_.front());
396 return true;
397 }
398 started_ = false;
399 currentState_.clear();
400 return true;
401}
402
403} // namespace eve::animation
std::string value
std::string id
glm::vec3 n
Definition Grass.cpp:64
uint32_t c
float tr
const char * name
Definition RockMesh.cpp:21
bool enabled
int v
float m[16]
JSON-compatible state value tree used by state hot reload.
Definition StateValue.h:20
static StateValue object()
Empty object.
Definition StateValue.h:39
static StateValue boolean(bool v)
Boolean value.
static StateValue array()
Empty array.
Definition StateValue.h:37
bool asBool() const
Boolean payload; only valid when kind() == Kind::Bool.
Definition StateValue.h:57
bool isString() const
Definition StateValue.h:48
bool isBool() const
Definition StateValue.h:47
bool isObject() const
Definition StateValue.h:49
const std::string & asString() const
String payload; only valid when kind() == Kind::String.
Definition StateValue.h:59
static StateValue number(double v)
Floating-point value.
const StateValue * find(const std::string &key) const
Look up key; nullptr when absent.
static StateValue string(std::string v)
String value.
void set(const std::string &key, StateValue v)
Insert or replace key; only valid on objects.
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:17
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:15
void blendFrom(const AnimPose *a, const AnimPose *b, float t)
Definition AnimPose.cpp:62
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 setHasExitTime(int transitionId, bool enabled)
float getFloat(const std::string &name) const
int addTransition(const std::string &from, const std::string &to, float blendSeconds)
Add transition from → to with blend duration.
void addTriggerCondition(int transitionId, const std::string &param)
bool captureState(StateValue &out) const
Serialize runtime state (params, current/next state, blend).
bool hasState(const std::string &name) const
AnimStateMachine(AnimSkeleton *skeleton)
void resetTrigger(const std::string &name)
void addBoolCondition(int transitionId, const std::string &param, bool value)
void setTrigger(const std::string &name)
void addFloatCondition(int transitionId, const std::string &param, const std::string &op, float threshold)
Comparison op: ">", ">=", "<", "<=", "==", "!=".
void setBool(const std::string &name, bool value)
void setFloat(const std::string &name, float value)
bool restoreState(const StateValue &in, std::string *err=nullptr)
Restore runtime state captured by captureState().
void addState(const std::string &name, AnimClip *clip)
bool getBool(const std::string &name) const
void setEntry(const std::string &name)
void setExitTime(int transitionId, float normalizedTime)
Optional: require normalized local time in [0,1] >= exitTime before transition.
bool resetToDefaults()
Clear params and re-seat the entry state (restore fallback).
Runtime-state serialization for state hot reload.
float clampf(float v, float lo, float hi)
Definition AnimMath.h:31
I * query()
Definition Capability.h:77