载入中...
搜索中...
未找到
AnimLayerMixer.cpp
浏览该文件的文档.
2
9#include "common/Exception.h"
10
11#include <algorithm>
12#include <cmath>
13
14namespace eve::animation {
15namespace {
16
17float layerBoneWeight(const AnimBoneMask* mask, float layerWeight, int boneIndex) {
18 const float maskWeight = mask ? mask->getBoneWeight(boneIndex) : 1.f;
19 return clampf(layerWeight * maskWeight, 0.f, 1.f);
20}
21
22eve::Result<AnimAdditiveReference> parseAdditiveReference(const std::string& reference) {
23 if (reference == "bind" || reference == "bind_pose" || reference == "BindPose")
25 if (reference == "identity" || reference == "Identity")
28 eve::DiagnosticCode::InvalidArgument, "animation additive reference must be \"bind\" or \"identity\""));
29}
30
31const char* additiveReferenceName(AnimAdditiveReference reference) {
32 return reference == AnimAdditiveReference::Identity ? "identity" : "bind";
33}
34
35} // namespace
36
37AnimBoneMask::AnimBoneMask(AnimSkeleton* skeleton) : skeleton_(skeleton) {
38 if (!skeleton_) throw Exception("AnimBoneMask: skeleton is null");
39 weights_.assign(static_cast<size_t>(skeleton_->getBoneCount()), 0.f);
40}
41
42void AnimBoneMask::setAll(float weight) { std::fill(weights_.begin(), weights_.end(), clampf(weight, 0.f, 1.f)); }
43
44bool AnimBoneMask::setBoneWeight(int boneIndex, float weight) {
45 if (boneIndex < 0 || boneIndex >= getBoneCount()) return false;
46 weights_[static_cast<size_t>(boneIndex)] = clampf(weight, 0.f, 1.f);
47 return true;
48}
49
50bool AnimBoneMask::setBoneWeightByName(const std::string& boneName, float weight) {
51 return skeleton_ && setBoneWeight(skeleton_->findBone(boneName), weight);
52}
53
54bool AnimBoneMask::setBoneAndChildren(const std::string& boneName, float weight) {
55 if (!skeleton_) return false;
56 const int root = skeleton_->findBone(boneName);
57 if (root < 0) return false;
58 const float clamped = clampf(weight, 0.f, 1.f);
59 for (int bone = 0; bone < skeleton_->getBoneCount(); ++bone) {
60 int current = bone;
61 while (current >= 0 && current != root) current = skeleton_->getParent(current);
62 if (current == root) weights_[static_cast<size_t>(bone)] = clamped;
63 }
64 return true;
65}
66
67float AnimBoneMask::getBoneWeight(int boneIndex) const {
68 if (boneIndex < 0 || boneIndex >= getBoneCount()) return 0.f;
69 return weights_[static_cast<size_t>(boneIndex)];
70}
71
72AnimLayerMixer::AnimLayerMixer(AnimSkeleton* skeleton) : skeleton_(skeleton) {
73 if (!skeleton_) throw Exception("AnimLayerMixer: skeleton is null");
74 pose_.resize(skeleton_->getBoneCount());
75 skeleton_->applyBindPose(&pose_);
76}
77
78bool AnimLayerMixer::sourceAlreadyAttached(const IAnimPoseSource* source) const {
79 if (!source) return false;
80 if (baseSource_ == source) return true;
81 for (const Layer& layer : layers_)
82 if (layer.source == source) return true;
83 return false;
84}
85
86eve::Result<void> AnimLayerMixer::attachSource(IAnimPoseSource* source, const char* role) {
87 if (!source)
89 std::string("animation mixer ") + role + " is null"));
90 if (source == this)
92 eve::DiagnosticCode::InvalidArgument, "animation mixer cannot attach itself as a pose source"));
93 if (source->getSkeleton() != skeleton_)
95 eve::DiagnosticCode::InvalidArgument, std::string("animation mixer ") + role + " skeleton mismatch"));
96 if (sourceAlreadyAttached(source))
98 eve::DiagnosticCode::Conflict, std::string("animation mixer ") + role + " is already attached"));
100}
101
103 if (!source) {
104 baseSource_ = nullptr;
106 }
107 // Allow replacing the current base with itself without conflict.
109 if (source == this)
111 eve::DiagnosticCode::InvalidArgument, "animation mixer cannot attach itself as a pose source"));
112 if (source->getSkeleton() != skeleton_)
114 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "animation mixer base skeleton mismatch"));
115 for (const Layer& layer : layers_)
116 if (layer.source == source)
118 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "animation mixer base is already a layer"));
119 baseSource_ = source;
121}
122
124 auto result = setBaseSource(player);
125 if (!result) {
126 result.ignore("AnimLayerMixer.setBasePlayer compatibility facade");
127 return false;
128 }
129 return true;
130}
131
133 auto result = setBaseSource(graph);
134 if (!result) {
135 result.ignore("AnimLayerMixer.setBaseGraph compatibility facade");
136 return false;
137 }
138 return true;
139}
140
142 auto result = setBaseSource(stateMachine);
143 if (!result) {
144 result.ignore("AnimLayerMixer.setBaseStateMachine compatibility facade");
145 return false;
146 }
147 return true;
148}
149
150AnimPlayer* AnimLayerMixer::getBasePlayer() const { return dynamic_cast<AnimPlayer*>(baseSource_); }
151
153 const std::string& mode) {
154 if (name.empty())
156 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "animation mixer layer name is empty"));
157 if (findLayer(name))
159 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "animation mixer layer name already exists"));
160 if (mode != "override" && mode != "additive")
162 eve::DiagnosticCode::InvalidArgument, "animation mixer layer mode must be \"override\" or \"additive\""));
163 if (mask && mask->getSkeleton() != skeleton_)
165 "animation mixer layer mask skeleton mismatch"));
166 auto attached = attachSource(source, "layer");
167 if (!attached) return eve::Result<int>::failure(attached.status());
168
169 layers_.push_back({name, source, mask, 1.f, mode == "additive", true, AnimAdditiveReference::BindPose});
170 return eve::Result<int>::success(static_cast<int>(layers_.size()) - 1);
171}
172
173int AnimLayerMixer::addLayer(const std::string& name, AnimPlayer* player, AnimBoneMask* mask, const std::string& mode) {
174 auto result = addPoseLayer(name, player, mask, mode);
175 if (!result) {
176 result.ignore("AnimLayerMixer.addLayer compatibility facade");
177 return -1;
178 }
179 return std::move(result).takeValue();
180}
181
183 const std::string& mode) {
184 auto result = addPoseLayer(name, graph, mask, mode);
185 if (!result) {
186 result.ignore("AnimLayerMixer.addGraphLayer compatibility facade");
187 return -1;
188 }
189 return std::move(result).takeValue();
190}
191
193 const std::string& mode) {
194 auto result = addPoseLayer(name, stateMachine, mask, mode);
195 if (!result) {
196 result.ignore("AnimLayerMixer.addStateMachineLayer compatibility facade");
197 return -1;
198 }
199 return std::move(result).takeValue();
200}
201
202AnimLayerMixer::Layer* AnimLayerMixer::findLayer(const std::string& name) {
203 for (auto& layer : layers_)
204 if (layer.name == name) return &layer;
205 return nullptr;
206}
207
208const AnimLayerMixer::Layer* AnimLayerMixer::findLayer(const std::string& name) const {
209 for (const auto& layer : layers_)
210 if (layer.name == name) return &layer;
211 return nullptr;
212}
213
214bool AnimLayerMixer::removeLayer(const std::string& name) {
215 const auto it =
216 std::find_if(layers_.begin(), layers_.end(), [&](const Layer& layer) { return layer.name == name; });
217 if (it == layers_.end()) return false;
218 layers_.erase(it);
219 return true;
220}
221
222bool AnimLayerMixer::setLayerWeight(const std::string& name, float weight) {
223 Layer* layer = findLayer(name);
224 if (!layer) return false;
225 layer->weight = clampf(weight, 0.f, 1.f);
226 return true;
227}
228
229bool AnimLayerMixer::setLayerEnabled(const std::string& name, bool enabled) {
230 Layer* layer = findLayer(name);
231 if (!layer) return false;
232 layer->enabled = enabled;
233 return true;
234}
235
237 Layer* layer = findLayer(name);
238 if (!layer)
240 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "animation mixer layer not found"));
241 if (!layer->additive)
243 eve::DiagnosticCode::InvalidArgument, "animation mixer additive reference requires an additive layer"));
244 auto parsed = parseAdditiveReference(reference);
245 if (!parsed) return eve::Result<void>::failure(parsed.status());
246 layer->additiveReference = std::move(parsed).takeValue();
248}
249
250bool AnimLayerMixer::setLayerAdditiveReferenceCompat(const std::string& name, const std::string& reference) {
252 if (!result) {
253 result.ignore("AnimLayerMixer.setLayerAdditiveReferenceCompat");
254 return false;
255 }
256 return true;
257}
258
259std::string AnimLayerMixer::getLayerName(int index) const {
260 if (index < 0 || index >= getLayerCount()) return {};
261 return layers_[static_cast<size_t>(index)].name;
262}
263
264float AnimLayerMixer::getLayerWeight(const std::string& name) const {
265 const Layer* layer = findLayer(name);
266 return layer ? layer->weight : 0.f;
267}
268
269bool AnimLayerMixer::getLayerEnabled(const std::string& name) const {
270 const Layer* layer = findLayer(name);
271 return layer && layer->enabled;
272}
273
274std::string AnimLayerMixer::getLayerMode(const std::string& name) const {
275 const Layer* layer = findLayer(name);
276 if (!layer) return {};
277 return layer->additive ? "additive" : "override";
278}
279
280std::string AnimLayerMixer::getLayerAdditiveReference(const std::string& name) const {
281 const Layer* layer = findLayer(name);
282 if (!layer) return {};
283 return additiveReferenceName(layer->additiveReference);
284}
285
286void AnimLayerMixer::collectEvents(const std::string& layerName, IAnimPoseSource* source) {
287 if (!source) return;
288 for (int i = 0; i < source->getEventCount(); ++i)
289 events_.push_back({layerName, source->getEventName(i), source->getEventPayload(i)});
290}
291
292void AnimLayerMixer::applyOverride(const Layer& layer) {
293 AnimPose* layerPose = layer.source->getPose();
294 for (int bone = 0; bone < pose_.getBoneCount(); ++bone) {
295 const float weight = layerBoneWeight(layer.mask, layer.weight, bone);
296 if (weight <= 0.f) continue;
297 pose_.local(bone) = blendTRS(pose_.local(bone), layerPose->local(bone), weight);
298 }
299}
300
301void AnimLayerMixer::applyAdditive(const Layer& layer) {
302 AnimPose* layerPose = layer.source->getPose();
303 for (int bone = 0; bone < pose_.getBoneCount(); ++bone) {
304 const float weight = layerBoneWeight(layer.mask, layer.weight, bone);
305 if (weight <= 0.f) continue;
306 const TransformTRS& reference = layer.additiveReference == AnimAdditiveReference::Identity
308 : skeleton_->bindLocal(bone);
309 applyAdditiveTRS(pose_.local(bone), layerPose->local(bone), reference, weight);
310 }
311}
312
313void AnimLayerMixer::compose() {
314 events_.clear();
315 if (baseSource_) {
316 pose_.copyFrom(baseSource_->getPose());
317 collectEvents("base", baseSource_);
318 } else {
319 skeleton_->applyBindPose(&pose_);
320 }
321 for (const Layer& layer : layers_) {
322 if (!layer.enabled || !layer.source) continue;
323 if (layer.weight <= 0.f) continue;
324 collectEvents(layer.name, layer.source);
325 if (layer.additive)
326 applyAdditive(layer);
327 else
328 applyOverride(layer);
329 }
330 pose_.computeWorld(skeleton_);
331}
332
334 auto seconds = detail::secondsForStep(step, hasLastTick_, lastTick_, "AnimLayerMixer");
335 if (!seconds) return eve::Result<void>::failure(seconds.status());
336 (void)std::move(seconds).takeValue();
337
338 // The mixer owns the evaluation boundary: every attached source consumes the
339 // same injected step (including disabled layers, so their clocks stay in sync).
340 if (baseSource_ && baseSource_->hasCurrentTick() && step.tick <= baseSource_->currentTick())
342 eve::DiagnosticCode::Conflict, "animation mixer base source already consumed this tick"));
343 for (const Layer& layer : layers_) {
344 if (layer.source && layer.source->hasCurrentTick() && step.tick <= layer.source->currentTick())
346 eve::DiagnosticCode::Conflict, "animation mixer layer source already consumed this tick"));
347 }
348 if (baseSource_) {
349 auto result = baseSource_->advance(step);
350 if (!result) return eve::Result<void>::failure(result.status());
351 }
352 for (const Layer& layer : layers_) {
353 if (!layer.source) continue;
354 auto result = layer.source->advance(step);
355 if (!result) return eve::Result<void>::failure(result.status());
356 }
357 compose();
358 lastTick_ = step.tick;
359 hasLastTick_ = true;
361}
362
364 auto step = detail::legacyStep(dt, hasLastTick_, lastTick_, "AnimLayerMixer");
365 if (!step) {
366 step.ignore("legacy AnimLayerMixer update");
367 return;
368 }
369 advance(std::move(step).takeValue()).ignore("legacy AnimLayerMixer update");
370}
371
372std::string AnimLayerMixer::getEventLayer(int index) const {
373 if (index < 0 || index >= getEventCount()) return {};
374 return events_[static_cast<size_t>(index)].layer;
375}
376
377std::string AnimLayerMixer::getEventName(int index) const {
378 if (index < 0 || index >= getEventCount()) return {};
379 return events_[static_cast<size_t>(index)].name;
380}
381
383 if (index < 0 || index >= getEventCount()) return {};
384 return events_[static_cast<size_t>(index)].payload;
385}
386
387} // namespace eve::animation
Value::Object payload
int root
Definition AnimSmr.cpp:119
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
int mask
std::uint32_t bone
std::string name
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
TileLayer * layer
double current
float step
Definition TreeMesh.cpp:314
uint32_t index
const UnitySourceAsset & source
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
Per-bone weights used by override and additive animation layers.
bool setBoneWeight(int boneIndex, float weight)
Set one bone's clamped weight.
bool setBoneAndChildren(const std::string &boneName, float weight)
Set a bone and all descendants to the same clamped weight.
void setAll(float weight)
Set every bone to the same clamped weight.
bool setBoneWeightByName(const std::string &boneName, float weight)
Set a named bone's clamped weight.
float getBoneWeight(int boneIndex) const
Return one bone's weight, or zero for an invalid index.
int getBoneCount() const
Return the number of bones represented by this mask.
AnimBoneMask(AnimSkeleton *skeleton)
Create a zero-weight mask for a skeleton; the skeleton is not owned.
Composable runtime pose graph supporting clips, blends, additive layers, per-bone masks,...
Definition AnimGraph.h:26
std::string getLayerMode(const std::string &name) const
Return "override" / "additive", or empty for unknown names.
AnimLayerMixer(AnimSkeleton *skeleton)
Create an empty mixer for a skeleton; the skeleton is not owned.
bool setBaseStateMachine(AnimStateMachine *stateMachine)
Compatibility facade over setBaseSource(AnimStateMachine*).
void update(float dt)
Legacy seconds facade; explicitly forwards to advance().
int addStateMachineLayer(const std::string &name, AnimStateMachine *stateMachine, AnimBoneMask *mask, const std::string &mode)
Compatibility facade over addPoseLayer(AnimStateMachine*).
std::string getEventName(int index) const override
Returns the event name.
bool removeLayer(const std::string &name)
Remove a layer by name.
int addLayer(const std::string &name, AnimPlayer *player, AnimBoneMask *mask, const std::string &mode)
Compatibility facade over addPoseLayer(AnimPlayer*).
int getLayerCount() const
Return the number of configured layers.
bool getLayerEnabled(const std::string &name) const
Return whether a layer is enabled; false for unknown names.
eve::Result< void > setBaseSource(IAnimPoseSource *source)
Set the borrowed base pose source evaluated before layers.
bool setBaseGraph(AnimGraph *graph)
Compatibility facade over setBaseSource(AnimGraph*).
eve::Result< void > advance(const eve::SimulationStep &step) override
Advance all referenced sources (including disabled layers) and compose.
eve::Result< void > setLayerAdditiveReference(const std::string &name, const std::string &reference)
Set additive reference for an additive layer: "bind" or "identity".
eve::Result< int > addPoseLayer(const std::string &name, IAnimPoseSource *source, AnimBoneMask *mask, const std::string &mode)
Append a pose layer. mode is "override" or "additive". A null mask affects every bone....
std::string getLayerAdditiveReference(const std::string &name) const
Return "bind" / "identity", or empty for unknown names.
bool setLayerAdditiveReferenceCompat(const std::string &name, const std::string &reference)
Compatibility facade over setLayerAdditiveReference.
int addGraphLayer(const std::string &name, AnimGraph *graph, AnimBoneMask *mask, const std::string &mode)
Compatibility facade over addPoseLayer(AnimGraph*).
std::string getLayerName(int index) const
Return a layer name, or empty for an invalid index.
int getEventCount() const override
Returns the event count.
bool setLayerEnabled(const std::string &name, bool enabled)
Enable or disable a layer without removing it. Disabled layers still advance.
bool setBasePlayer(AnimPlayer *player)
Compatibility facade over setBaseSource(AnimPlayer*).
std::string getEventPayload(int index) const override
Returns the event payload.
std::string getEventLayer(int index) const
Source layer name; "base" identifies the base source.
AnimPlayer * getBasePlayer() const
Return the base when it is an AnimPlayer; otherwise null. @ownership Borrowed @lifetime Valid while t...
float getLayerWeight(const std::string &name) const
Return a layer weight, or zero for an unknown name.
bool setLayerWeight(const std::string &name, float weight)
Set a layer's global clamped weight.
Single-clip (or cross-fading) 3D animation player. Script type: AnimPlayer.
Definition AnimPlayer.h:30
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 computeWorld(const AnimSkeleton *skeleton)
Compute world transforms from local pose + skeleton hierarchy. World values readable via getWorld* af...
Definition AnimPose.cpp:203
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.
int findBone(const std::string &name) const
Finds bone.
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
Clip-driven animation state machine with float/bool/trigger parameters and cross-fade transitions....
Borrowed pose provider consumed by AnimLayerMixer.
virtual eve::SimulationTick currentTick() const noexcept=0
Last scheduler tick consumed by advance.
virtual bool hasCurrentTick() const noexcept=0
Whether advance has consumed at least one checked step.
virtual AnimPose * getPose()=0
Most recent local pose; valid after a successful advance. @ownership Borrowed @lifetime Valid while t...
virtual eve::Result< void > advance(const eve::SimulationStep &step)=0
Consume one scheduler step and refresh getPose().
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
bool enabled
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
static TransformTRS identity()
Identity.
Definition AnimMath.h:15