载入中...
搜索中...
未找到
MontagePlayer.cpp
浏览该文件的文档.
2
8
9#include <algorithm>
10#include <cmath>
11#include <set>
12#include <utility>
13
14namespace eve::animation {
15namespace {
16
17RootMotionLockAxes locksFromMask(MontageRootMotionMask mask) noexcept {
19 if (!mask.translationX) locks = locks | RootMotionLockAxes::X;
20 if (!mask.translationY) locks = locks | RootMotionLockAxes::Y;
21 if (!mask.translationZ) locks = locks | RootMotionLockAxes::Z;
22 return locks;
23}
24
25MontageRootMotionMask maskFromPolicy(const RootMotionPolicy& policy) noexcept {
26 MontageRootMotionMask mask;
27 mask.translationX = !hasLock(policy.lockAxes, RootMotionLockAxes::X);
28 mask.translationY = !hasLock(policy.lockAxes, RootMotionLockAxes::Y);
29 mask.translationZ = !hasLock(policy.lockAxes, RootMotionLockAxes::Z);
30 mask.rotation = !policy.lockRotation;
31 return mask;
32}
33
34void multiplyRotation(TransformTRS& total, float x, float y, float z, float w) {
35 const float tx = total.qx;
36 const float ty = total.qy;
37 const float tz = total.qz;
38 const float tw = total.qw;
39 total.qx = tw * x + tx * w + ty * z - tz * y;
40 total.qy = tw * y - tx * z + ty * w + tz * x;
41 total.qz = tw * z + tx * y - ty * x + tz * w;
42 total.qw = tw * w - tx * x - ty * y - tz * z;
43}
44
45} // namespace
46
48 : skeleton_(skeleton), player_(std::make_unique<AnimPlayer>(&skeleton)) {
49 pushRootMotionPolicyToPlayer();
50}
51
53
55 auto valid = timeline.validate();
56 if (!valid) return Result<void>::failure(valid.status());
57 std::set<std::string> seen;
58 std::vector<MontageClipAsset> clips;
59 for (const auto& section : timeline.animationSections) {
60 if (!seen.insert(section.animationUri).second) continue;
61 auto loaded = provider.load(section.animationUri, skeleton_);
62 if (!loaded) return Result<void>::failure(loaded.status());
63 clips.push_back({section.animationUri, std::move(loaded).takeValue()});
64 }
65 return prepare(std::move(timeline), std::move(clips));
66}
67
68Result<void> MontagePlayer::prepare(action::ActionTimeline timeline, std::vector<MontageClipAsset>&& clips) {
69 auto valid = timeline.validate();
70 if (!valid) return Result<void>::failure(valid.status());
71
72 std::set<std::string> uris;
73 for (std::size_t index = 0; index < clips.size(); ++index) {
74 if (clips[index].uri.empty() || !clips[index].clip)
76 "montage clip asset is incomplete",
77 "clips[" + std::to_string(index) + "]"));
78 if (!std::isfinite(clips[index].clip->getDuration()) || clips[index].clip->getDuration() <= 0.0f)
80 "montage clip duration must be positive",
81 "clips[" + std::to_string(index) + "]"));
82 if (!uris.insert(clips[index].uri).second)
84 "montage clip URI is duplicated",
85 "clips[" + std::to_string(index) + "].uri"));
86 }
87 for (std::size_t index = 0; index < timeline.animationSections.size(); ++index) {
88 const auto& section = timeline.animationSections[index];
89 if (!uris.contains(section.animationUri))
91 Diagnostic::error(DiagnosticCode::InvalidArgument, "animation section clip was not supplied",
92 "animationSections[" + std::to_string(index) + "].animationUri"));
93 AnimClip* clip = nullptr;
94 const auto asset = std::find_if(clips.begin(), clips.end(),
95 [&](const auto& candidate) { return candidate.uri == section.animationUri; });
96 if (asset != clips.end()) clip = asset->clip.get();
97 const double sourceStart = section.sourceStart.seconds();
98 const double sourceEnd = section.sourceEnd.isZero() ? clip->getDuration() : section.sourceEnd.seconds();
99 if (sourceStart >= clip->getDuration() || sourceEnd > clip->getDuration() || sourceEnd <= sourceStart)
101 DiagnosticCode::InvalidArgument, "animation section trim is outside the supplied clip",
102 "animationSections[" + std::to_string(index) + "].sourceStartNs"));
103 }
104
105 timeline_ = std::move(timeline);
106 clips_ = std::move(clips);
107 activeSection_ = nullptr;
108 player_->stop();
109 time_ = Duration::zero();
110 lastTick_ = SimulationTick::zero();
111 hasLastTick_ = false;
112 started_ = false;
113 playing_ = false;
114 blendingOut_ = false;
115 weight_ = 1.0;
116 executionId_ = {};
117 applyTimelineRootMotionChannels();
119}
120
122 if (!timeline_)
124 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
125
126 std::set<std::string> seen;
127 std::vector<MontageClipAsset> replacements;
128 for (const auto& section : timeline_->animationSections) {
129 if (!seen.insert(section.animationUri).second) continue;
130 auto loaded = provider.load(section.animationUri, skeleton_);
131 if (!loaded) return Result<void>::failure(loaded.status());
132 replacements.push_back({section.animationUri, std::move(loaded).takeValue()});
133 }
134
135 MontagePlayer candidate(skeleton_);
136 auto validated = candidate.prepare(*timeline_, std::move(replacements));
137 if (!validated) return validated;
138
139 clips_ = std::move(candidate.clips_);
140 player_->stop();
141 activeSection_ = nullptr;
142 if ((playing_ || blendingOut_) && time_ < timeline_->duration) {
143 if (const auto* section = sectionAt(time_))
144 activate(*section, Duration::fromNanoseconds(time_.nanoseconds() - section->start.nanoseconds()));
145 }
147}
148
150 if (!timeline_)
152 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
153 auto candidate = *timeline_;
154 candidate.montage = settings;
155 auto valid = candidate.validate();
156 if (!valid) return Result<void>::failure(valid.status());
157 timeline_->montage = std::move(settings);
158 applyTimelineRootMotionChannels();
160}
161
162Result<void> MontagePlayer::setSectionSplits(std::vector<Duration> splitTimestamps) {
163 if (!timeline_)
165 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
166 auto candidate = *timeline_;
167 candidate.splitTimestamps = std::move(splitTimestamps);
168 auto valid = candidate.validate();
169 if (!valid) return Result<void>::failure(valid.status());
170 timeline_->splitTimestamps = std::move(candidate.splitTimestamps);
172}
173
174Result<void> MontagePlayer::replaceClip(std::string_view uri, std::unique_ptr<AnimClip> clip) {
175 if (!timeline_)
177 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
178 if (uri.empty() || !clip)
180 Diagnostic::error(DiagnosticCode::InvalidArgument, "replacement montage clip is incomplete", "clip"));
181 if (!std::isfinite(clip->getDuration()) || clip->getDuration() <= 0.0f)
183 DiagnosticCode::InvalidArgument, "replacement montage clip duration must be positive", "clip.duration"));
184 const auto asset = std::find_if(clips_.begin(), clips_.end(), [&](const auto& value) { return value.uri == uri; });
185 if (asset == clips_.end())
187 "replacement URI is not referenced by the montage", "uri"));
188 for (std::size_t index = 0; index < timeline_->animationSections.size(); ++index) {
189 const auto& section = timeline_->animationSections[index];
190 if (section.animationUri != uri) continue;
191 const double sourceStart = section.sourceStart.seconds();
192 const double sourceEnd = section.sourceEnd.isZero() ? clip->getDuration() : section.sourceEnd.seconds();
193 if (sourceStart >= clip->getDuration() || sourceEnd > clip->getDuration() || sourceEnd <= sourceStart)
195 DiagnosticCode::InvalidArgument, "animation section trim is outside the replacement clip",
196 "animationSections[" + std::to_string(index) + "].sourceStartNs"));
197 }
198
199 asset->clip = std::move(clip);
200 if ((playing_ || blendingOut_) && activeSection_ && activeSection_->animationUri == uri) {
201 player_->stop();
202 const auto* section = sectionAt(time_);
203 activeSection_ = nullptr;
204 if (section) activate(*section, Duration::fromNanoseconds(time_.nanoseconds() - section->start.nanoseconds()));
205 }
207}
208
210 if (!timeline_)
212 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
213 if (executionId.isZero())
215 Diagnostic::error(DiagnosticCode::InvalidArgument, "action execution id must not be zero", "executionId"));
216 player_->stop();
217 activeSection_ = nullptr;
218 time_ = Duration::zero();
219 hasLastTick_ = false;
220 started_ = false;
221 playing_ = true;
222 blendingOut_ = false;
223 weight_ = timeline_->montage.defaultBlendIn.isZero() ? 1.0 : 0.0;
224 executionId_ = executionId;
226}
227
229 if (!timeline_ || !playing_)
231 DiagnosticCode::InvalidArgument, "montage must be prepared and playing before rebinding", "montage"));
232 if (executionId.isZero())
234 Diagnostic::error(DiagnosticCode::InvalidArgument, "action execution id must not be zero", "executionId"));
235 executionId_ = executionId;
237}
238
239void MontagePlayer::stop() noexcept {
240 player_->stop();
241 activeSection_ = nullptr;
242 time_ = Duration::zero();
243 hasLastTick_ = false;
244 started_ = false;
245 playing_ = false;
246 blendingOut_ = false;
247 weight_ = 0.0;
248 executionId_ = {};
249}
250
251AnimClip* MontagePlayer::clipFor(std::string_view uri) const noexcept {
252 const auto found = std::find_if(clips_.begin(), clips_.end(), [&](const auto& asset) { return asset.uri == uri; });
253 return found == clips_.end() ? nullptr : found->clip.get();
254}
255
256const action::ActionAnimationSection* MontagePlayer::sectionAt(Duration time) const noexcept {
257 if (!timeline_) return nullptr;
258 for (const auto& section : timeline_->animationSections)
259 if (time >= section.start && time < section.end) return &section;
260 return nullptr;
261}
262
263void MontagePlayer::activate(const action::ActionAnimationSection& section, Duration localTime) {
264 AnimClip* clip = clipFor(section.animationUri);
265 const float authoredSeconds = static_cast<float>(
266 Duration::fromNanoseconds(section.end.nanoseconds() - section.start.nanoseconds()).seconds());
267 const float sourceStart = static_cast<float>(section.sourceStart.seconds());
268 const float sourceEnd =
269 section.sourceEnd.isZero() ? clip->getDuration() : static_cast<float>(section.sourceEnd.seconds());
270 const float speed = (sourceEnd - sourceStart) / authoredSeconds;
271 player_->setBlendCurve(section.blendCurve == action::ActionBlendCurve::Linear ? AnimBlendCurve::Linear
273 if (!player_->isPlaying())
274 player_->play(clip);
275 else
276 player_->crossFade(clip, static_cast<float>(section.blendIn.seconds()));
277 player_->setLoop(false);
278 player_->setSpeed(speed);
279 player_->setTime(sourceStart + static_cast<float>(localTime.seconds()) * speed);
280 activeSection_ = &section;
281}
282
283void MontagePlayer::accumulateRootMotion(TransformTRS& total) const {
284 total.px += player_->getRootMotionX();
285 total.py += player_->getRootMotionY();
286 total.pz += player_->getRootMotionZ();
287 multiplyRotation(total, player_->getRootMotionRotationX(), player_->getRootMotionRotationY(),
288 player_->getRootMotionRotationZ(), player_->getRootMotionRotationW());
289}
290
291void MontagePlayer::pushRootMotionPolicyToPlayer() noexcept {
292 player_->setRootMotionPolicy(rootMotionPolicy_)
293 .ignore("montage root-motion policy sync uses an already-validated policy snapshot");
294}
295
296void MontagePlayer::applyTimelineRootMotionChannels() noexcept {
297 MontageRootMotionMask mask;
298 mask.translationX = timeline_->montage.rootMotionHorizontal;
299 mask.translationZ = timeline_->montage.rootMotionHorizontal;
300 mask.translationY = timeline_->montage.rootMotionVertical;
301 mask.rotation = timeline_->montage.rootMotionRotation;
302 rootMotionMask_ = mask;
303 rootMotionPolicy_.lockAxes = locksFromMask(mask);
304 rootMotionPolicy_.lockRotation = !mask.rotation;
305 pushRootMotionPolicyToPlayer();
306}
307
309 rootMotionMask_ = mask;
310 rootMotionPolicy_.lockAxes = locksFromMask(mask);
311 rootMotionPolicy_.lockRotation = !mask.rotation;
312 pushRootMotionPolicyToPlayer();
313}
314
316 auto validated = validateRootMotionPolicy(policy);
317 if (!validated) return validated;
318 rootMotionPolicy_ = policy;
319 rootMotionMask_ = maskFromPolicy(policy);
320 auto pushed = player_->setRootMotionPolicy(policy);
321 if (!pushed) return pushed;
323}
324
326 auto applied = player_->setRootMotionCharacterYaw(yawRadians);
327 if (!applied) return applied;
328 rootMotionPolicy_.characterYaw = yawRadians;
330}
331
333 if (!timeline_)
335 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
336 if (!playing_)
338 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage is not playing", "montage"));
339 if (advance.id != executionId_)
341 DiagnosticCode::InvalidArgument, "action advance belongs to another execution", "advance.id"));
342 if (advance.totalElapsed < time_ || advance.totalElapsed > timeline_->duration)
344 Diagnostic::error(DiagnosticCode::InvalidArgument, "action advance time is outside the presentation cursor",
345 "advance.totalElapsed"));
346 if (hasLastTick_ && tick <= lastTick_)
348 Diagnostic::error(DiagnosticCode::Conflict, "montage tick must advance monotonically", "tick"));
349
350 MontageAdvance result;
351 result.previous = time_;
352 result.current = advance.totalElapsed;
353
354 if (!started_ && result.current == time_) {
355 if (const auto* section = sectionAt(time_)) activate(*section, Duration::zero());
356 }
357
358 Duration cursor = time_;
359 while (cursor < result.current) {
360 const action::ActionAnimationSection* section = sectionAt(cursor);
361 if (!section) {
362 auto next = std::find_if(timeline_->animationSections.begin(), timeline_->animationSections.end(),
363 [&](const auto& candidate) { return candidate.start > cursor; });
364 cursor =
365 next == timeline_->animationSections.end() ? result.current : std::min(next->start, result.current);
366 activeSection_ = nullptr;
367 if (gapPolicy_ == MontageGapPolicy::BindPose) player_->stop();
368 continue;
369 }
370 if (activeSection_ != section)
371 activate(*section, Duration::fromNanoseconds(cursor.nanoseconds() - section->start.nanoseconds()));
372 const Duration sliceEnd = std::min(section->end, result.current);
373 const Duration slice = Duration::fromNanoseconds(sliceEnd.nanoseconds() - cursor.nanoseconds());
374 player_->update(static_cast<float>(slice.seconds()));
375 accumulateRootMotion(result.rootMotion);
376 cursor = sliceEnd;
377 if (cursor == section->end) activeSection_ = nullptr;
378 }
379
380 result.events = advance.timelineEvents;
381 result.activeBlocks = advance.activeBlocks;
382 result.sectionId = activeSection_ ? std::optional<LogicalId>(activeSection_->id) : std::nullopt;
384 const double currentSeconds = result.current.seconds();
385 if (!timeline_->montage.defaultBlendIn.isZero()) {
386 const double t = std::clamp(currentSeconds / timeline_->montage.defaultBlendIn.seconds(), 0.0, 1.0);
387 weight_ = t * t * (3.0 - 2.0 * t);
388 } else {
389 weight_ = 1.0;
390 }
391 if (!timeline_->montage.looping && !timeline_->montage.defaultBlendOut.isZero()) {
392 const double start = timeline_->duration.seconds() - timeline_->montage.defaultBlendOut.seconds() +
393 timeline_->montage.blendOutOffset.seconds();
394 if (currentSeconds >= start) {
395 const double t =
396 std::clamp((currentSeconds - start) / timeline_->montage.defaultBlendOut.seconds(), 0.0, 1.0);
397 weight_ = std::min(weight_, 1.0 - t * t * (3.0 - 2.0 * t));
398 }
399 }
400 result.weight = weight_;
401 // Root motion is already policy-filtered (locks / facing / rotation) by the owned AnimPlayer.
402 if (receiver_) receiver_->applyMontageRootMotion(result.rootMotion);
403
404 time_ = result.current;
405 lastTick_ = tick;
406 hasLastTick_ = true;
407 started_ = true;
408 if (result.completed) playing_ = false;
411}
412
413std::vector<action::ActionTimelineEvent> MontagePlayer::realignStateEvents(Duration target) const {
414 std::vector<action::ActionTimelineEvent> events;
415 for (const auto& track : timeline_->tracks) {
416 if (track.muted) continue;
417 for (const auto& state : track.states) {
418 const bool wasActive = time_ >= state.start && time_ < state.end;
419 const bool isActive = target >= state.start && target < state.end;
420 if (wasActive == isActive) continue;
421 events.push_back(
423 track.id, state.id, state.type, target, state.payload});
424 }
425 }
426 return events;
427}
428
429Result<std::vector<MontageActiveBlock>> MontagePlayer::activeBlocksAt(Duration target) const {
430 return timeline_->activeBlocks(target);
431}
432
435 if (!timeline_)
437 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
438 if (!playing_)
440 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage is not playing", "montage"));
441 if (executionId != executionId_)
443 Diagnostic::error(DiagnosticCode::InvalidArgument, "jump belongs to another execution", "executionId"));
444 if (target < Duration::zero() || target > timeline_->duration)
446 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage jump is outside the timeline", "target"));
447 if (hasLastTick_ && tick <= lastTick_)
449 Diagnostic::error(DiagnosticCode::Conflict, "montage tick must advance monotonically", "tick"));
450
451 MontageAdvance result;
452 result.previous = time_;
453 result.current = target;
454 result.events = realignStateEvents(target);
455 auto active = activeBlocksAt(target);
456 if (!active) return Result<MontageAdvance>::failure(active.status());
457 result.activeBlocks = std::move(active).takeValue();
458 player_->stop();
459 activeSection_ = nullptr;
460 if (const auto* section = sectionAt(target)) {
461 activate(*section, Duration::fromNanoseconds(target.nanoseconds() - section->start.nanoseconds()));
462 result.sectionId = section->id;
463 }
464 time_ = target;
465 lastTick_ = tick;
466 hasLastTick_ = true;
467 started_ = true;
469}
470
473 if (!timeline_)
475 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
476 auto range = timeline_->sectionRange(sectionIndex);
477 if (!range) return Result<MontageAdvance>::failure(range.status());
478 return jumpToTime(executionId, range.value().first, tick);
479}
480
482 std::size_t sectionIndex, double progress,
484 if (!std::isfinite(progress) || progress < 0.0 || progress > 1.0)
486 DiagnosticCode::InvalidArgument, "physical section progress must be in [0, 1]", "progress"));
487 if (!timeline_)
489 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
490 auto range = timeline_->sectionRange(sectionIndex);
491 if (!range) return Result<MontageAdvance>::failure(range.status());
492 const auto span = range.value().second.nanoseconds() - range.value().first.nanoseconds();
493 const Duration target = Duration::fromNanoseconds(range.value().first.nanoseconds() +
494 static_cast<std::int64_t>(std::llround(span * progress)));
496}
497
499 if (!timeline_)
501 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
502 const auto found = std::upper_bound(timeline_->splitTimestamps.begin(), timeline_->splitTimestamps.end(), time_);
504 static_cast<std::size_t>(std::distance(timeline_->splitTimestamps.begin(), found)));
505}
506
508 if (!timeline_)
510 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
511 auto range = timeline_->sectionRange(sectionIndex);
512 if (!range) return Result<double>::failure(range.status());
513 const double span = static_cast<double>(range.value().second.nanoseconds() - range.value().first.nanoseconds());
514 const double elapsed = static_cast<double>(time_.nanoseconds() - range.value().first.nanoseconds());
515 return Result<double>::success(std::clamp(elapsed / span, 0.0, 1.0));
516}
517
519 if (!timeline_)
521 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
523 if (hasLastTick_ && tick <= lastTick_)
525 Diagnostic::error(DiagnosticCode::Conflict, "montage tick must advance monotonically", "tick"));
526 MontageAdvance result;
527 result.previous = time_;
528 result.current = time_;
529 result.completed = true;
530 for (const auto& track : timeline_->tracks) {
531 if (track.muted) continue;
532 for (const auto& state : track.states) {
533 if (time_ < state.start || time_ >= state.end) continue;
534 result.events.push_back(
535 {action::ActionTimelineEventKind::StateExit, track.id, state.id, state.type, time_, state.payload});
536 }
537 }
538 player_->stop();
539 activeSection_ = nullptr;
540 playing_ = false;
541 blendingOut_ = false;
542 weight_ = 0.0;
543 result.weight = 0.0;
544 lastTick_ = tick;
545 hasLastTick_ = true;
547}
548
550 if (duration < Duration::zero())
552 DiagnosticCode::InvalidArgument, "montage blend-out duration must be non-negative", "duration"));
553 if (!timeline_)
555 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage has not been prepared", "montage"));
556 if (duration.isZero() && playing_) return interrupt(tick);
557 if (!playing_ && weight_ <= 0.0)
559 if (hasLastTick_ && tick <= lastTick_)
561 Diagnostic::error(DiagnosticCode::Conflict, "montage tick must advance monotonically", "tick"));
562 MontageAdvance result;
563 result.previous = result.current = time_;
564 result.weight = weight_;
565 if (duration.isZero()) {
566 player_->stop();
567 activeSection_ = nullptr;
568 playing_ = false;
569 blendingOut_ = false;
570 weight_ = 0.0;
571 result.weight = 0.0;
572 result.completed = true;
573 lastTick_ = tick;
574 hasLastTick_ = true;
576 }
577 for (const auto& track : timeline_->tracks) {
578 if (track.muted) continue;
579 for (const auto& state : track.states)
580 if (time_ >= state.start && time_ < state.end)
581 result.events.push_back(
582 {action::ActionTimelineEventKind::StateExit, track.id, state.id, state.type, time_, state.payload});
583 }
584 blendingOut_ = true;
585 blendOutDuration_ = duration;
586 blendOutElapsed_ = Duration::zero();
587 blendOutStartWeight_ = weight_;
588 lastTick_ = tick;
589 hasLastTick_ = true;
591}
592
594 if (!blendingOut_)
596 Diagnostic::error(DiagnosticCode::InvalidArgument, "montage is not blending out", "montage"));
597 if (delta < Duration::zero())
599 DiagnosticCode::InvalidArgument, "montage blend-out delta must be non-negative", "delta"));
600 if (tick <= lastTick_)
602 Diagnostic::error(DiagnosticCode::Conflict, "montage tick must advance monotonically", "tick"));
603 auto elapsed = blendOutElapsed_.tryAdd(delta);
604 if (!elapsed) return Result<MontageAdvance>::failure(elapsed.status());
605 blendOutElapsed_ = std::min(std::move(elapsed).takeValue(), blendOutDuration_);
606 const double t = std::clamp(blendOutElapsed_.seconds() / blendOutDuration_.seconds(), 0.0, 1.0);
607 weight_ = blendOutStartWeight_ * (1.0 - t * t * (3.0 - 2.0 * t));
608 MontageAdvance result;
609 result.previous = result.current = time_;
610 result.weight = weight_;
611 result.completed = t >= 1.0;
612 lastTick_ = tick;
613 if (result.completed) {
614 player_->stop();
615 activeSection_ = nullptr;
616 blendingOut_ = false;
617 playing_ = false;
618 }
621}
622
623AnimPose& MontagePlayer::pose() { return *player_->getPose(); }
624
625} // namespace eve::animation
LogicalId target
double value
Duration start
bool & active
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float duration
eve::action::ActionExecutionId executionId
int mask
glm::vec4 clip
Range range
std::vector< Track > tracks
bool valid
Deterministic presentation-only action montage playback.
std::string uri
float t
bool found
Battle::Events events
SimulationTick tick
std::size_t cursor
TerrainThermalSettings settings
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
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
constexpr std::int64_t nanoseconds() const noexcept
Return the exact signed nanosecond representation.
Definition Time.h:69
Result< Duration > tryAdd(Duration other) const
Add two durations with overflow validation.
Definition Time.cpp:30
static constexpr Duration fromNanoseconds(std::int64_t nanoseconds) noexcept
Construct an exact duration from nanoseconds.
Definition Time.h:55
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
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
Single-clip (or cross-fading) 3D animation player. Script type: AnimPlayer.
Definition AnimPlayer.h:30
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
Synchronous ownership-transfer port used to resolve montage clip URIs.
virtual Result< std::unique_ptr< AnimClip > > load(std::string_view uri, const AnimSkeleton &skeleton)=0
Resolve one URI into a clip compatible with the borrowed skeleton.
virtual void applyMontageRootMotion(const TransformTRS &delta) noexcept=0
Consume one borrowed filtered root-motion delta.
Presentation-only montage player over an ActionTimeline.
Result< void > setSettings(action::ActionMontageSettings settings)
Apply validated montage-wide settings without rebuilding clip topology or playback time.
Result< void > replaceClip(std::string_view uri, std::unique_ptr< AnimClip > clip)
Transactionally replace one URI-backed clip and preserve the current cursor and slot weight.
Result< double > physicalSectionProgress(std::size_t sectionIndex) const
Return current normalized progress within an objective physical section.
Result< void > reloadClips(IMontageClipProvider &provider)
Transactionally reload every referenced clip while preserving playback state.
MontagePlayer(AnimSkeleton &skeleton)
Borrow a skeleton that must outlive this player.
~MontagePlayer()
Montage player.
Result< void > setSectionSplits(std::vector< Duration > splitTimestamps)
Atomically replace objective physical-section split timestamps without rebuilding clip topology.
Result< MontageAdvance > advanceBlendOut(Duration delta, SimulationTick tick)
Advance an active blend-out without advancing gameplay or montage time.
Result< MontageAdvance > jumpToTime(action::ActionExecutionId executionId, Duration target, SimulationTick tick)
Atomically realign presentation and active state blocks to an externally authoritative timestamp.
Result< MontageAdvance > jumpToSection(action::ActionExecutionId executionId, std::size_t sectionIndex, SimulationTick tick)
Jump presentation to the start of one objective physical section.
Result< void > setRootMotionPolicy(const RootMotionPolicy &policy)
Replace the canonical root-motion policy used by presentation and the owned AnimPlayer.
Result< MontageAdvance > evaluateSectionProgress(action::ActionExecutionId executionId, std::size_t sectionIndex, double progress, SimulationTick tick)
Evaluate a normalized physical-section progress and atomically realign presentation.
Result< MontageAdvance > interrupt(SimulationTick tick)
Interrupt playback and emit paired exits for every active state block.
Result< void > prepare(action::ActionTimeline timeline, std::vector< MontageClipAsset > &&clips)
Validate and atomically replace the complete montage presentation.
Result< void > play(action::ActionExecutionId executionId)
Bind presentation playback to one authoritative ActionRuntime execution.
Result< MontageAdvance > beginBlendOut(Duration duration, SimulationTick tick)
Enter continuous blend-out and emit exits for every active state block.
Result< MontageAdvance > present(const action::ActionAdvance &advance, SimulationTick tick)
Present one ActionRuntime-owned advance without advancing gameplay time locally.
AnimPose & pose()
Borrow the current evaluated pose.
void setRootMotionMask(MontageRootMotionMask mask) noexcept
Compatibility setter for axis enable bits.
Result< std::size_t > physicalSectionIndex() const
Resolve the objective physical section containing the current cursor.
void stop() noexcept
Stop playback and reset the derived cursor.
Result< void > rebindExecution(action::ActionExecutionId executionId)
Rebind an already playing presentation after its external gameplay authority is recreated.
Result< void > setRootMotionCharacterYaw(float yawRadians)
Update CharacterFacing yaw without replacing bake/lock/apply-space.
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
constexpr bool isZero() const noexcept
Returns whether this value is zero.
RootMotionLockAxes
Per-axis translation locks for extracted root-motion deltas.
@ BindPose
Restore the skeleton bind pose on entering a gap.
eve::Result< void > validateRootMotionPolicy(const RootMotionPolicy &policy)
Validate policy fields.
AnimBlendCurve
Deterministic built-in curve used by cross-fade pose interpolation.
Definition AnimPlayer.h:18
constexpr bool hasLock(RootMotionLockAxes mask, RootMotionLockAxes axis) noexcept
True when mask contains the given axis bit.
Result payload for one deterministic advance call.
Definition Action.h:175
ActionPhase phase
Current phase after all transitions in this call.
Definition Action.h:179
Duration totalElapsed
Total simulation duration consumed by the execution.
Definition Action.h:185
std::vector< ActionTimelineEvent > timelineEvents
Owning timeline boundaries crossed by this advance.
Definition Action.h:187
std::vector< ActionActiveBlock > activeBlocks
Owning samples of timeline states active after this advance.
Definition Action.h:189
ActionExecutionId id
Execution advanced by this call.
Definition Action.h:177
Objective animation segment placed on the montage presentation lane.
Montage-wide presentation settings persisted with the action timeline.
Canonical action timeline asset shared by runtime and editor.
Result< void > validate() const
Validate ids, ranges, uniqueness and deterministic ordering.
std::vector< ActionAnimationSection > animationSections
Owning observation returned by one montage presentation step.
std::vector< action::ActionTimelineEvent > events
std::optional< LogicalId > sectionId
std::vector< MontageActiveBlock > activeBlocks
Compatibility-only axis enable mask for montage root motion.
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.