载入中...
搜索中...
未找到
AnimPlayer.cpp
浏览该文件的文档.
5
6#include "common/Exception.h"
7
8#include <algorithm>
9#include <cmath>
10
11namespace eve::animation {
12namespace {
13
14TransformTRS rootMotionDelta(const TransformTRS& from, const TransformTRS& to) {
15 TransformTRS delta = TransformTRS::identity();
16 delta.px = to.px - from.px;
17 delta.py = to.py - from.py;
18 delta.pz = to.pz - from.pz;
19 delta.qx = from.qw * to.qx - from.qx * to.qw - from.qy * to.qz + from.qz * to.qy;
20 delta.qy = from.qw * to.qy + from.qx * to.qz - from.qy * to.qw - from.qz * to.qx;
21 delta.qz = from.qw * to.qz - from.qx * to.qy + from.qy * to.qx - from.qz * to.qw;
22 delta.qw = from.qw * to.qw + from.qx * to.qx + from.qy * to.qy + from.qz * to.qz;
23 delta.normalizeRotation();
24 return delta;
25}
26
27} // namespace
28
29AnimPlayer::AnimPlayer(AnimSkeleton* skeleton) : skeleton_(skeleton) {
30 if (!skeleton_) throw Exception("AnimPlayer: skeleton is null");
31 pose_.resize(skeleton_->getBoneCount());
32 prevPose_.resize(skeleton_->getBoneCount());
33 sampledPose_.resize(skeleton_->getBoneCount());
34 rootPreviousPose_.resize(skeleton_->getBoneCount());
35 rootStartPose_.resize(skeleton_->getBoneCount());
36 rootEndPose_.resize(skeleton_->getBoneCount());
37 skeleton_->applyBindPose(&pose_);
38}
39
40bool AnimPlayer::effectiveLoop() const {
41 if (hasLoopOverride_) return loopOverride_;
42 return clip_ ? clip_->getLoop() : true;
43}
44
45bool AnimPlayer::getLoop() const { return effectiveLoop(); }
46
47void AnimPlayer::setSpeed(float speed) {
48 if (speed < 0.f) throw Exception("AnimPlayer.setSpeed: speed must be >= 0");
49 speed_ = speed;
50}
51
52void AnimPlayer::setTime(float seconds) {
53 if (seconds < 0.f) throw Exception("AnimPlayer.setTime: time must be >= 0");
54 time_ = seconds;
55 if (clip_) {
56 clip_->sample(time_, &pose_, skeleton_);
57 }
58}
59
61 if (!clip) throw Exception("AnimPlayer.play: clip is null");
62 clip_ = clip;
63 prevClip_ = nullptr;
64 blending_ = false;
65 time_ = 0.f;
66 playing_ = true;
67 paused_ = false;
68 clip_->sample(0.f, &pose_, skeleton_);
69 events_.clear();
70 for (int i = 0; i < clip_->getEventCount(); ++i) {
71 if (std::fabs(clip_->getEventTime(i)) <= 1e-7f)
72 events_.push_back({clip_->getEventName(i), clip_->getEventPayload(i)});
73 }
74}
75
76void AnimPlayer::crossFade(AnimClip* clip, float blendSeconds) {
77 if (!clip) throw Exception("AnimPlayer.crossFade: clip is null");
78 if (blendSeconds < 0.f) throw Exception("AnimPlayer.crossFade: blendSeconds must be >= 0");
79 if (!clip_ || blendSeconds <= 1e-6f) {
80 play(clip);
81 return;
82 }
83 prevClip_ = clip_;
84 prevTime_ = time_;
85 prevPose_.copyFrom(&pose_);
86 clip_ = clip;
87 time_ = 0.f;
88 blendDuration_ = blendSeconds;
89 blendElapsed_ = 0.f;
90 blending_ = true;
91 playing_ = true;
92 paused_ = false;
93}
94
96 playing_ = false;
97 paused_ = false;
98 blending_ = false;
99 clip_ = nullptr;
100 prevClip_ = nullptr;
101 time_ = 0.f;
102 if (skeleton_) skeleton_->applyBindPose(&pose_);
103 events_.clear();
104}
105
107 if (playing_) paused_ = true;
108}
109
111 if (playing_) paused_ = false;
112}
113
114AnimPose* AnimPlayer::getPose() { return &pose_; }
115
116void AnimPlayer::setRootMotionBone(int boneIndex) {
117 if (boneIndex < 0 || boneIndex >= skeleton_->getBoneCount())
118 throw Exception("AnimPlayer.setRootMotionBone: invalid bone %d", boneIndex);
119 rootMotionBone_ = boneIndex;
120}
121
123 auto validated = validateRootMotionPolicy(policy);
124 if (!validated) return validated;
125 rootMotionPolicy_ = policy;
127}
128
130 if (!std::isfinite(yawRadians))
132 "root motion characterYaw must be finite"));
133 rootMotionPolicy_.characterYaw = yawRadians;
135}
136
138 if (pendingEvents_.empty()) return {};
139 std::string event = std::move(pendingEvents_.front());
140 pendingEvents_.erase(pendingEvents_.begin());
141 return event;
142}
143
145 if (hz < 0.f) throw Exception("AnimPlayer.setUpdateRate: hz must be >= 0");
146 updateRate_ = hz;
147 updateAccumulator_ = 0.f;
148}
149
150std::string AnimPlayer::getEventName(int index) const {
151 if (index < 0 || index >= getEventCount()) return {};
152 return events_[static_cast<size_t>(index)].name;
153}
154
155std::string AnimPlayer::getEventPayload(int index) const {
156 if (index < 0 || index >= getEventCount()) return {};
157 return events_[static_cast<size_t>(index)].payload;
158}
159
160void AnimPlayer::dispatchEvents(float oldTime, float newTime) {
161 if (!clip_ || clip_->getEventCount() == 0 || newTime <= oldTime) return;
162 const float duration = clip_->getDuration();
163 if (effectiveLoop() && duration > 1e-8f) {
164 const int firstCycle = static_cast<int>(std::floor(oldTime / duration));
165 const int lastCycle = static_cast<int>(std::floor(newTime / duration));
166 for (int cycle = firstCycle; cycle <= lastCycle; ++cycle) {
167 for (int i = 0; i < clip_->getEventCount(); ++i) {
168 const float absoluteTime = static_cast<float>(cycle) * duration + clip_->getEventTime(i);
169 if (absoluteTime > oldTime && absoluteTime <= newTime)
170 events_.push_back({clip_->getEventName(i), clip_->getEventPayload(i)});
171 }
172 }
173 return;
174 }
175 const float endTime = duration > 0.f ? std::min(newTime, duration) : newTime;
176 for (int i = 0; i < clip_->getEventCount(); ++i) {
177 const float eventTime = clip_->getEventTime(i);
178 if (eventTime > oldTime && eventTime <= endTime)
179 events_.push_back({clip_->getEventName(i), clip_->getEventPayload(i)});
180 }
181}
182
183void AnimPlayer::updateUnchecked(float dt) {
184 if (dt < 0.f) throw Exception("AnimPlayer.update: dt must be >= 0");
185 events_.clear();
186 if (!playing_ || paused_ || !clip_) return;
187 if (updateRate_ > 0.f) {
188 updateAccumulator_ += dt;
189 const float interval = 1.f / updateRate_;
190 if (updateAccumulator_ + 1e-8f < interval) {
191 rootMotion_ = TransformTRS::identity();
192 return;
193 }
194 dt = updateAccumulator_;
195 updateAccumulator_ = 0.f;
196 }
197
198 rootMotion_ = TransformTRS::identity();
199 const float previousTime = time_;
200 clip_->sample(previousTime, &rootPreviousPose_, skeleton_);
201 time_ += dt * speed_;
202 clip_->collectEvents(previousTime, time_, effectiveLoop(), pendingEvents_);
203 dispatchEvents(previousTime, time_);
204 if (!effectiveLoop() && clip_->getDuration() > 0.f && time_ >= clip_->getDuration()) {
205 time_ = clip_->getDuration();
206 playing_ = false;
207 }
208
209 clip_->sample(time_, &sampledPose_, skeleton_);
210 const TransformTRS& fromRoot = rootPreviousPose_.local(rootMotionBone_);
211 const TransformTRS& toRoot = sampledPose_.local(rootMotionBone_);
212 TransformTRS rawMotion = rootMotionDelta(fromRoot, toRoot);
213 if (effectiveLoop() && clip_->getDuration() > 1e-8f) {
214 const float duration = clip_->getDuration();
215 const int cycles = static_cast<int>(std::floor(time_ / duration) - std::floor(previousTime / duration));
216 if (cycles != 0) {
217 clip_->sample(0.f, &rootStartPose_, skeleton_);
218 clip_->sampleClamped(duration, &rootEndPose_, skeleton_);
219 const TransformTRS& startRoot = rootStartPose_.local(rootMotionBone_);
220 const TransformTRS& endRoot = rootEndPose_.local(rootMotionBone_);
221 rawMotion.px += static_cast<float>(cycles) * (endRoot.px - startRoot.px);
222 rawMotion.py += static_cast<float>(cycles) * (endRoot.py - startRoot.py);
223 rawMotion.pz += static_cast<float>(cycles) * (endRoot.pz - startRoot.pz);
224 }
225 }
226
227 if (blending_ && prevClip_) {
228 blendElapsed_ += dt;
229 float t = blendDuration_ > 1e-8f ? blendElapsed_ / blendDuration_ : 1.f;
230 if (t >= 1.f) {
231 blending_ = false;
232 prevClip_ = nullptr;
233 pose_.copyFrom(&sampledPose_);
234 } else {
235 const TransformTRS outgoingFrom = prevPose_.local(rootMotionBone_);
236 prevTime_ += dt * speed_;
237 prevClip_->sample(prevTime_, &prevPose_, skeleton_);
238 const TransformTRS outgoingDelta = rootMotionDelta(outgoingFrom, prevPose_.local(rootMotionBone_));
239 if (blendCurve_ == AnimBlendCurve::EaseInOut) t = t * t * (3.0f - 2.0f * t);
240 pose_.blendFrom(&prevPose_, &sampledPose_, t);
241 rawMotion = blendTRS(outgoingDelta, rawMotion, t);
242 }
243 } else {
244 pose_.copyFrom(&sampledPose_);
245 }
246 rootMotion_ = applyRootMotionPolicy(rawMotion, rootMotionPolicy_);
247 bakeRootMotionIntoPose(pose_, rootMotionBone_, skeleton_->bindLocal(rootMotionBone_), rootMotionPolicy_)
248 .ignore("root motion bake skipped when bone/policy leaves pose unchanged");
249}
250
252 if (step.delta.nanoseconds() < 0)
254 "animation player delta must be non-negative"));
255 if (hasLastTick_ && step.tick <= lastTick_)
257 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "animation player tick must advance monotonically"));
258
259 const float dt = static_cast<float>(step.delta.seconds());
260 if (!std::isfinite(dt))
262 "animation player delta is outside float range"));
263 updateUnchecked(dt);
264 lastTick_ = step.tick;
265 hasLastTick_ = true;
267}
268
269void AnimPlayer::update(float dt) {
271 if (!duration) {
272 duration.ignore("legacy animation player update received an invalid duration");
273 return;
274 }
275 auto nextTick = hasLastTick_ ? lastTick_.incremented() : std::optional<eve::SimulationTick>(eve::SimulationTick(1));
276 if (!nextTick) {
278 "animation player simulation tick overflow"))
279 .ignore("legacy animation player update tick overflow");
280 return;
281 }
282 advance({*nextTick, std::move(duration).takeValue()}).ignore("legacy animation player update facade");
283}
284
285} // namespace eve::animation
Value::Object payload
float duration
std::string from
eve::Duration endTime
glm::vec4 clip
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string name
float t
float step
Definition TreeMesh.cpp:314
uint32_t index
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
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
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
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:393
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
float getDuration() const
Returns the duration.
Definition AnimClip.h:163
bool getLoop() const
Returns the loop.
Definition AnimClip.h:168
void sample(float time, AnimPose *out, const AnimSkeleton *skeleton=nullptr) const
Sample local pose at time (seconds). If skeleton non-null, missing tracks fall back to bind pose; oth...
Definition AnimClip.cpp:575
float getEventTime(int index) const
Return an event marker's time, or 0 for an invalid index.
Definition AnimClip.cpp:181
int getEventCount() const
Return the number of event markers.
Definition AnimClip.h:204
std::string getEventName(int index) const
Return an event marker's name, or empty for an invalid index.
Definition AnimClip.cpp:186
void collectEvents(float previousTime, float currentTime, bool loop, std::vector< std::string > &out) const
Collect notifies crossed by forward playback, including loop wrap.
Definition AnimClip.cpp:348
std::string getEventPayload(int index) const
Return an event marker's optional payload.
Definition AnimClip.cpp:191
void setSpeed(float speed)
Sets the speed.
std::string getEventName(int index) const override
Name of a dispatched event, or empty for invalid index.
eve::Result< void > advance(const eve::SimulationStep &step) override
Advance playback and sample into internal pose.
void setTime(float seconds)
Sets the time.
void setRootMotionBone(int boneIndex)
Select the bone whose per-frame motion is extracted (default 0).
AnimPlayer(AnimSkeleton *skeleton)
Anim player.
std::string consumeEvent()
Pop the oldest notify crossed since the previous update.
eve::Result< void > setRootMotionCharacterYaw(float yawRadians)
Set CharacterFacing yaw in radians without replacing the rest of the policy.
void update(float dt)
Legacy seconds facade retained for scripts and old callers.
void crossFade(AnimClip *clip, float blendSeconds)
Cross-fade to clip over blendSeconds (keeps sampling previous until done).
std::string getEventPayload(int index) const override
Payload of a dispatched event, or empty for invalid index.
int getEventCount() const override
Number of events crossed by the most recent play/update call.
Definition AnimPlayer.h:133
eve::Result< void > setRootMotionPolicy(const RootMotionPolicy &policy)
Replace the root-motion filter/bake/apply policy used by subsequent updates.
bool getLoop() const
Returns the loop.
AnimPose * getPose() override
Borrowed pointer accessor. @ownership Borrowed @lifetime Valid while the owning animation object rema...
void setUpdateRate(float hz)
Limit pose evaluation frequency for animation LOD; 0 updates every call.
void play(AnimClip *clip)
Play.
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
void blendFrom(const AnimPose *a, const AnimPose *b, float t)
Blend from.
Definition AnimPose.cpp:97
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.
const TransformTRS & bindLocal(int boneIndex) const
Binds local.
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
TransformTRS blendTRS(const TransformTRS &a, const TransformTRS &b, float t)
Blend trs.
Definition AnimMath.h:79
eve::Result< void > bakeRootMotionIntoPose(AnimPose &pose, int boneIndex, const TransformTRS &referenceRoot, const RootMotionPolicy &policy)
Remove extracted root motion from a pose when bake flags are set.
eve::Result< void > validateRootMotionPolicy(const RootMotionPolicy &policy)
Validate policy fields.
TransformTRS applyRootMotionPolicy(const TransformTRS &rawDelta, const RootMotionPolicy &policy)
Filter a raw root-bone delta into a controller-facing delta.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Policy applied after raw root-bone delta extraction. @ownership Value type; owned by AnimPlayer or th...
float characterYaw
Yaw in radians for CharacterFacing; ignored for BoneLocal.
static TransformTRS identity()
Identity.
Definition AnimMath.h:15