载入中...
搜索中...
未找到
AnimGraph.cpp
浏览该文件的文档.
2
7#include "common/Exception.h"
8
9#include <algorithm>
10#include <cmath>
11#include <limits>
12
13namespace eve::animation {
14
15AnimGraph::AnimGraph(AnimSkeleton* skeleton) : skeleton_(skeleton) {
16 if (!skeleton_) throw Exception("AnimGraph: skeleton is null");
17 output_.resize(skeleton_->getBoneCount());
18}
19
20int AnimGraph::addNode(Kind kind) {
21 nodes_.emplace_back();
22 Node& node = nodes_.back();
23 node.kind = kind;
24 node.mask.assign(static_cast<size_t>(skeleton_->getBoneCount()), 1.f);
25 node.cache.resize(skeleton_->getBoneCount());
26 node.scratch.resize(skeleton_->getBoneCount());
27 return static_cast<int>(nodes_.size()) - 1;
28}
29
30AnimGraph::Node& AnimGraph::requireNode(int node) {
31 if (node < 0 || node >= getNodeCount()) throw Exception("AnimGraph: invalid node %d", node);
32 return nodes_[static_cast<size_t>(node)];
33}
34const AnimGraph::Node& AnimGraph::requireNode(int node) const {
35 if (node < 0 || node >= getNodeCount()) throw Exception("AnimGraph: invalid node %d", node);
36 return nodes_[static_cast<size_t>(node)];
37}
38void AnimGraph::requireChild(int child) const { (void)requireNode(child); }
39
41 if (!clip) throw Exception("AnimGraph.addClip: clip is null");
42 const int id = addNode(Kind::Clip);
43 nodes_[static_cast<size_t>(id)].clip = clip;
44 return id;
45}
46int AnimGraph::addBlend(int a, int b, float weight) {
47 requireChild(a);
48 requireChild(b);
49 const int id = addNode(Kind::Blend);
50 Node& n = nodes_[static_cast<size_t>(id)];
51 n.a = a;
52 n.b = b;
53 n.weight = clampf(weight, 0.f, 1.f);
54 return id;
55}
56int AnimGraph::addAdditive(int base, int additive, float weight) {
57 requireChild(base);
58 requireChild(additive);
59 const int id = addNode(Kind::Additive);
60 Node& n = nodes_[static_cast<size_t>(id)];
61 n.a = base;
62 n.b = additive;
63 n.weight = clampf(weight, 0.f, 1.f);
64 return id;
65}
66int AnimGraph::addLayer(int base, int overlay, float weight) {
67 requireChild(base);
68 requireChild(overlay);
69 const int id = addNode(Kind::Layer);
70 Node& n = nodes_[static_cast<size_t>(id)];
71 n.a = base;
72 n.b = overlay;
73 n.weight = clampf(weight, 0.f, 1.f);
74 std::fill(n.mask.begin(), n.mask.end(), 0.f);
75 return id;
76}
77int AnimGraph::addOneShot(int base, int shot, float fadeIn, float fadeOut) {
78 requireChild(base);
79 requireChild(shot);
80 if (fadeIn < 0.f || fadeOut < 0.f) throw Exception("AnimGraph.addOneShot: fades must be >= 0");
81 const int id = addNode(Kind::OneShot);
82 Node& n = nodes_[static_cast<size_t>(id)];
83 n.a = base;
84 n.b = shot;
85 n.fadeIn = fadeIn;
86 n.fadeOut = fadeOut;
87 return id;
88}
89int AnimGraph::addBlendSpace1D() { return addNode(Kind::BlendSpace1D); }
90int AnimGraph::addBlendSpace2D() { return addNode(Kind::BlendSpace2D); }
91
92void AnimGraph::addBlendSpace1DPoint(int node, float x, int child) {
93 Node& n = requireNode(node);
94 requireChild(child);
95 if (n.kind != Kind::BlendSpace1D) throw Exception("AnimGraph: node is not BlendSpace1D");
96 n.points.push_back({x, 0.f, child});
97 std::sort(n.points.begin(), n.points.end(), [](const Point& a, const Point& b) { return a.x < b.x; });
98}
99void AnimGraph::addBlendSpace2DPoint(int node, float x, float y, int child) {
100 Node& n = requireNode(node);
101 requireChild(child);
102 if (n.kind != Kind::BlendSpace2D) throw Exception("AnimGraph: node is not BlendSpace2D");
103 n.points.push_back({x, y, child});
104}
105
106void AnimGraph::setBoneMask(int node, int boneIndex, float weight, bool includeChildren) {
107 Node& n = requireNode(node);
108 if (boneIndex < 0 || boneIndex >= skeleton_->getBoneCount())
109 throw Exception("AnimGraph: invalid bone %d", boneIndex);
110 n.mask[static_cast<size_t>(boneIndex)] = clampf(weight, 0.f, 1.f);
111 if (includeChildren) {
112 for (int i = boneIndex + 1; i < skeleton_->getBoneCount(); ++i) {
113 int p = skeleton_->getParent(i);
114 while (p >= 0 && p != boneIndex) p = skeleton_->getParent(p);
115 if (p == boneIndex) n.mask[static_cast<size_t>(i)] = clampf(weight, 0.f, 1.f);
116 }
117 }
118}
120 std::fill(requireNode(node).mask.begin(), requireNode(node).mask.end(), 0.f);
121}
123 requireChild(node);
124 root_ = node;
125}
126void AnimGraph::setWeight(int node, float weight) { requireNode(node).weight = clampf(weight, 0.f, 1.f); }
127void AnimGraph::setPosition1D(int node, float x) { requireNode(node).x = x; }
128void AnimGraph::setPosition2D(int node, float x, float y) {
129 Node& n = requireNode(node);
130 n.x = x;
131 n.y = y;
132}
133void AnimGraph::setSpeed(int node, float speed) {
134 Node& n = requireNode(node);
135 if (n.kind != Kind::Clip) throw Exception("AnimGraph.setSpeed: node is not a clip");
136 n.speed = speed;
137}
139 Node& n = requireNode(node);
140 if (n.kind != Kind::OneShot) throw Exception("AnimGraph.trigger: node is not one-shot");
141 n.active = true;
142 n.time = 0.f;
143 requireNode(n.b).time = 0.f;
144}
145bool AnimGraph::isOneShotActive(int node) const { return requireNode(node).active; }
146
147void AnimGraph::blendMasked(AnimPose& out, const AnimPose& base, const AnimPose& overlay, float weight,
148 const std::vector<float>* mask) const {
149 out.resize(base.getBoneCount());
150 for (int i = 0; i < base.getBoneCount(); ++i) {
151 const float w = clampf(weight * (mask ? (*mask)[static_cast<size_t>(i)] : 1.f), 0.f, 1.f);
152 out.local(i) = blendTRS(base.local(i), overlay.local(i), w);
153 }
154}
155
157 Node& n = requireNode(node);
158 if (n.kind != Kind::Additive)
160 eve::DiagnosticCode::InvalidArgument, "AnimGraph.setAdditiveReference requires an Additive node"));
161 if (reference == "bind" || reference == "bind_pose" || reference == "BindPose")
162 n.additiveReference = AnimAdditiveReference::BindPose;
163 else if (reference == "identity" || reference == "Identity")
164 n.additiveReference = AnimAdditiveReference::Identity;
165 else
167 eve::DiagnosticCode::InvalidArgument, "AnimGraph additive reference must be \"bind\" or \"identity\""));
169}
170
172 auto result = setAdditiveReference(node, reference);
173 if (!result) {
174 result.ignore("AnimGraph.setAdditiveReferenceCompat");
175 return false;
176 }
177 return true;
178}
179
180std::string AnimGraph::getAdditiveReference(int node) const {
181 if (node < 0 || node >= getNodeCount()) return {};
182 const Node& n = nodes_[static_cast<size_t>(node)];
183 if (n.kind != Kind::Additive) return {};
184 return n.additiveReference == AnimAdditiveReference::Identity ? "identity" : "bind";
185}
186
187void AnimGraph::applyAdditive(AnimPose& out, const AnimPose& base, const AnimPose& delta, float weight,
188 const std::vector<float>* mask, AnimAdditiveReference reference) const {
189 out.resize(base.getBoneCount());
190 const TransformTRS identity = TransformTRS::identity();
191 for (int i = 0; i < base.getBoneCount(); ++i) {
192 const float w = clampf(weight * (mask ? (*mask)[static_cast<size_t>(i)] : 1.f), 0.f, 1.f);
193 out.local(i) = base.local(i);
194 if (w <= 0.f) continue;
195 const TransformTRS& ref = reference == AnimAdditiveReference::BindPose ? skeleton_->bindLocal(i) : identity;
196 applyAdditiveTRS(out.local(i), delta.local(i), ref, w);
197 }
198}
199
200const AnimPose& AnimGraph::evaluateBlendSpace(Node& node, bool twoDimensional) {
201 if (node.points.empty()) {
202 skeleton_->applyBindPose(&node.cache);
203 return node.cache;
204 }
205 if (node.points.size() == 1) {
206 node.cache.copyFrom(&evaluate(node.points.front().child));
207 return node.cache;
208 }
209 if (!twoDimensional) {
210 if (node.x <= node.points.front().x) {
211 node.cache.copyFrom(&evaluate(node.points.front().child));
212 return node.cache;
213 }
214 if (node.x >= node.points.back().x) {
215 node.cache.copyFrom(&evaluate(node.points.back().child));
216 return node.cache;
217 }
218 for (size_t i = 0; i + 1 < node.points.size(); ++i) {
219 const Point &a = node.points[i], &b = node.points[i + 1];
220 if (node.x >= a.x && node.x <= b.x) {
221 blendMasked(node.cache, evaluate(a.child), evaluate(b.child), (node.x - a.x) / (b.x - a.x), nullptr);
222 return node.cache;
223 }
224 }
225 }
226 // Inverse-distance weights are stable outside the convex hull and degrade
227 // gracefully for authoring-time sparse 2D spaces.
228 std::vector<float> weights(node.points.size());
229 float sum = 0.f;
230 for (size_t i = 0; i < node.points.size(); ++i) {
231 const float dx = node.x - node.points[i].x, dy = node.y - node.points[i].y;
232 weights[i] = 1.f / std::max(1e-6f, dx * dx + dy * dy);
233 sum += weights[i];
234 }
235 node.cache.copyFrom(&evaluate(node.points.front().child));
236 float accumulated = weights[0] / sum;
237 for (size_t i = 1; i < node.points.size(); ++i) {
238 const float w = weights[i] / sum;
239 const float relative = w / (accumulated + w);
240 node.scratch.copyFrom(&node.cache);
241 blendMasked(node.cache, node.scratch, evaluate(node.points[i].child), relative, nullptr);
242 accumulated += w;
243 }
244 return node.cache;
245}
246
247const AnimPose& AnimGraph::evaluate(int id) {
248 Node& node = requireNode(id);
249 if (node.cacheGeneration == generation_) return node.cache;
250 node.cacheGeneration = generation_;
251 switch (node.kind) {
252 case Kind::Clip: node.clip->sample(node.time, &node.cache, skeleton_); break;
253 case Kind::Blend: blendMasked(node.cache, evaluate(node.a), evaluate(node.b), node.weight, nullptr); break;
254 case Kind::Layer: blendMasked(node.cache, evaluate(node.a), evaluate(node.b), node.weight, &node.mask); break;
255 case Kind::Additive:
256 applyAdditive(node.cache, evaluate(node.a), evaluate(node.b), node.weight, &node.mask,
257 node.additiveReference);
258 break;
259 case Kind::OneShot: {
260 const AnimPose& base = evaluate(node.a);
261 if (!node.active)
262 node.cache.copyFrom(&base);
263 else {
264 const float duration = requireNode(node.b).clip ? requireNode(node.b).clip->getDuration() : 0.f;
265 float w = 1.f;
266 if (node.fadeIn > 0.f) w = std::min(w, node.time / node.fadeIn);
267 if (node.fadeOut > 0.f) w = std::min(w, (duration - node.time) / node.fadeOut);
268 blendMasked(node.cache, base, evaluate(node.b), clampf(w, 0.f, 1.f), &node.mask);
269 }
270 break;
271 }
272 case Kind::BlendSpace1D: return evaluateBlendSpace(node, false);
273 case Kind::BlendSpace2D: return evaluateBlendSpace(node, true);
274 }
275 return node.cache;
276}
277
278void AnimGraph::updateUnchecked(float dt) {
279 if (dt < 0.f) throw Exception("AnimGraph.update: dt must be >= 0");
280 if (root_ < 0) throw Exception("AnimGraph.update: root is not set");
281 for (Node& node : nodes_) {
282 if (node.kind == Kind::Clip) node.time = node.clip->wrapTime(node.time + dt * node.speed);
283 if (node.kind == Kind::OneShot && node.active) {
284 node.time += dt;
285 const Node& shot = requireNode(node.b);
286 if (shot.clip && node.time >= shot.clip->getDuration()) node.active = false;
287 }
288 }
289 if (++generation_ == 0) {
290 generation_ = 1;
291 for (Node& n : nodes_) n.cacheGeneration = 0;
292 }
293 output_.copyFrom(&evaluate(root_));
294}
295
297 auto seconds = detail::secondsForStep(step, hasLastTick_, lastTick_, "AnimGraph");
298 if (!seconds) return eve::Result<void>::failure(seconds.status());
299 updateUnchecked(std::move(seconds).takeValue());
300 lastTick_ = step.tick;
301 hasLastTick_ = true;
303}
304
305void AnimGraph::update(float dt) {
306 auto step = detail::legacyStep(dt, hasLastTick_, lastTick_, "AnimGraph");
307 if (!step) {
308 step.ignore("legacy AnimGraph update");
309 return;
310 }
311 advance(std::move(step).takeValue()).ignore("legacy AnimGraph update");
312}
313
314} // namespace eve::animation
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float duration
Vec3 relative
Definition AnimSmr.cpp:164
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
int mask
glm::vec4 p[6]
glm::vec4 clip
glm::vec3 n
Definition Grass.cpp:63
TokenKind kind
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string id
Definition PlayHost.cpp:108
const RoadNode * node
float dy
float dx
float weights[3]
float step
Definition TreeMesh.cpp:314
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
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
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:145
AnimGraph(AnimSkeleton *skeleton)
Anim graph.
Definition AnimGraph.cpp:15
int addClip(AnimClip *clip)
Adds clip.
Definition AnimGraph.cpp:40
void setPosition2D(int node, float x, float y)
Sets the position 2 d.
int addAdditive(int base, int additive, float weight=1.f)
Adds additive.
Definition AnimGraph.cpp:56
void addBlendSpace2DPoint(int node, float x, float y, int child)
Adds blend space 2 d point.
Definition AnimGraph.cpp:99
void setSpeed(int node, float speed)
Sets the speed.
int addBlendSpace2D()
Adds blend space 2 d.
Definition AnimGraph.cpp:90
void setWeight(int node, float weight)
Sets the weight.
void trigger(int node)
Trigger.
int addBlendSpace1D()
Adds blend space 1 d.
Definition AnimGraph.cpp:89
bool isOneShotActive(int node) const
True when one shot active.
int addBlend(int a, int b, float weight=0.5f)
Adds blend.
Definition AnimGraph.cpp:46
void setBoneMask(int node, int boneIndex, float weight, bool includeChildren=false)
Sets the bone mask.
void clearBoneMask(int node)
Clears bone mask.
int addLayer(int base, int overlay, float weight=1.f)
Adds layer.
Definition AnimGraph.cpp:66
void setPosition1D(int node, float x)
Sets the position 1 d.
int addOneShot(int base, int shot, float fadeIn=0.1f, float fadeOut=0.1f)
Adds one shot.
Definition AnimGraph.cpp:77
void setRoot(int node)
Sets the root.
eve::Result< void > setAdditiveReference(int node, const std::string &reference)
Set additive reference for an Additive node: "bind" or "identity". Defaults to identity (sample is al...
void addBlendSpace1DPoint(int node, float x, int child)
Adds blend space 1 d point.
Definition AnimGraph.cpp:92
eve::Result< void > advance(const eve::SimulationStep &step) override
Evaluate the graph using one scheduler-owned deterministic step.
void update(float dt)
Legacy seconds facade; explicitly forwards to advance().
std::string getAdditiveReference(int node) const
Return "bind" / "identity", or empty for non-additive / invalid nodes.
bool setAdditiveReferenceCompat(int node, const std::string &reference)
Compatibility facade over setAdditiveReference.
int getNodeCount() const
Returns the node count.
Definition AnimGraph.h:65
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.
int getParent(int boneIndex) const
Returns the parent.
const TransformTRS & bindLocal(int boneIndex) const
Binds local.
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
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
AnimAdditiveReference
Reference pose used when converting an additive sample into a delta.
void applyAdditiveTRS(TransformTRS &base, const TransformTRS &sample, const TransformTRS &reference, float weight)
Apply an additive sample onto base using reference.
Definition AnimMath.h:114
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Local TRS used by skeletal animation (quaternion xyzw).
Definition AnimMath.h:9
static TransformTRS identity()
Identity.
Definition AnimMath.h:15