载入中...
搜索中...
未找到
MotionRuntime.cpp
浏览该文件的文档.
2
4#include "common/Diagnostic.h"
5#include "common/Exception.h"
6
7#include <cmath>
8#include <utility>
9
10namespace eve::animation {
11namespace {
12
13[[nodiscard]] eve::Result<void> okApplied() {
15}
16
17[[nodiscard]] eve::Result<void> okNoOp() {
19}
20
21} // namespace
22
23eve::Result<void> MotionRuntime::validateDesc(float duration, float delay, int loops,
24 const std::string &ease) {
25 if (!(duration >= 0.f) || !std::isfinite(duration))
27 eve::DiagnosticCode::InvalidArgument, "Motion duration must be finite and >= 0"));
28 if (!(delay >= 0.f) || !std::isfinite(delay))
30 eve::DiagnosticCode::InvalidArgument, "Motion delay must be finite and >= 0"));
31 if (loops == 0 || loops < -1)
33 eve::DiagnosticCode::InvalidArgument, "Motion loops must be -1 or >= 1"));
34 try {
35 (void)evaluateMotionEase(0.5f, ease.c_str());
36 } catch (const Exception &) {
38 eve::DiagnosticCode::InvalidArgument, std::string("Motion ease kind is unknown: ") + ease));
39 }
40 return okApplied();
41}
42
43eve::Result<void> MotionRuntime::validateStyle(MotionStyle style, int frequency, float dampingRatio) {
44 if (style == MotionStyle::Tween) return okApplied();
45 if (frequency < 1)
47 eve::DiagnosticCode::InvalidArgument, "Motion frequency must be >= 1 for Punch/Shake"));
48 if (!(dampingRatio >= 0.f) || !std::isfinite(dampingRatio))
50 eve::DiagnosticCode::InvalidArgument, "Motion dampingRatio must be finite and >= 0"));
51 return okApplied();
52}
53
54
55void MotionRuntime::recycleFloat(FloatSlot &slot) {
56 slot.sink = nullptr;
57 slot.onUpdate = {};
58 slot.onComplete = {};
59 slot.onCancel = {};
60 const auto index = static_cast<std::uint32_t>(&slot - floats_.data());
61 freeFloats_.push_back(index);
62}
63
64void MotionRuntime::recycleVec2(Vec2Slot &slot) {
65 slot.sink = nullptr;
66 slot.onUpdate = {};
67 slot.onComplete = {};
68 slot.onCancel = {};
69 const auto index = static_cast<std::uint32_t>(&slot - vec2s_.data());
70 freeVec2s_.push_back(index);
71}
72
73void MotionRuntime::recycleVec3(Vec3Slot &slot) {
74 slot.sink = nullptr;
75 slot.onUpdate = {};
76 slot.onComplete = {};
77 slot.onCancel = {};
78 const auto index = static_cast<std::uint32_t>(&slot - vec3s_.data());
79 freeVec3s_.push_back(index);
80}
81
82void MotionRuntime::recycleColor(ColorSlot &slot) {
83 slot.sink = nullptr;
84 slot.onUpdate = {};
85 slot.onComplete = {};
86 slot.onCancel = {};
87 const auto index = static_cast<std::uint32_t>(&slot - colors_.data());
88 freeColors_.push_back(index);
89}
90
91void MotionRuntime::recycleQuat(QuatSlot &slot) {
92 slot.sink = nullptr;
93 slot.onUpdate = {};
94 slot.onComplete = {};
95 slot.onCancel = {};
96 const auto index = static_cast<std::uint32_t>(&slot - quats_.data());
97 freeQuats_.push_back(index);
98}
99
101 floats_.reserve(count);
102 freeFloats_.reserve(count);
103 while (floats_.size() < count) {
104 FloatSlot slot;
105 slot.phase = Phase::Inactive;
106 slot.generation = 1;
107 floats_.push_back(std::move(slot));
108 freeFloats_.push_back(static_cast<std::uint32_t>(floats_.size() - 1));
109 }
110}
111
113 vec2s_.reserve(count);
114 freeVec2s_.reserve(count);
115 while (vec2s_.size() < count) {
116 Vec2Slot slot;
117 slot.phase = Phase::Inactive;
118 slot.generation = 1;
119 vec2s_.push_back(std::move(slot));
120 freeVec2s_.push_back(static_cast<std::uint32_t>(vec2s_.size() - 1));
121 }
122}
123
125 vec3s_.reserve(count);
126 freeVec3s_.reserve(count);
127 while (vec3s_.size() < count) {
128 Vec3Slot slot;
129 slot.phase = Phase::Inactive;
130 slot.generation = 1;
131 vec3s_.push_back(std::move(slot));
132 freeVec3s_.push_back(static_cast<std::uint32_t>(vec3s_.size() - 1));
133 }
134}
135
137 colors_.reserve(count);
138 freeColors_.reserve(count);
139 while (colors_.size() < count) {
140 ColorSlot slot;
141 slot.phase = Phase::Inactive;
142 slot.generation = 1;
143 colors_.push_back(std::move(slot));
144 freeColors_.push_back(static_cast<std::uint32_t>(colors_.size() - 1));
145 }
146}
147
149 quats_.reserve(count);
150 freeQuats_.reserve(count);
151 while (quats_.size() < count) {
152 QuatSlot slot;
153 slot.phase = Phase::Inactive;
154 slot.generation = 1;
155 quats_.push_back(std::move(slot));
156 freeQuats_.push_back(static_cast<std::uint32_t>(quats_.size() - 1));
157 }
158}
159
160
161
162eve::Result<void> MotionRuntime::applyFloat(FloatSlot &slot, float linearT, bool fireUpdate) {
163 const float eased = evaluateMotionEase(linearT, slot.ease.c_str());
164 if (slot.style == MotionStyle::Tween) {
165 const float a = slot.reverse ? slot.to : slot.from;
166 const float b = slot.reverse ? slot.from : slot.to;
167 slot.current = lerpFloat(a, b, eased);
168 } else {
169 const float strength = slot.reverse ? -slot.to : slot.to;
170 float wave = evaluateMotionOscillation(eased, slot.frequency, slot.dampingRatio);
171 if (slot.style == MotionStyle::Shake)
172 wave *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 0);
173 slot.current = slot.from + strength * wave;
174 }
175 if (slot.sink) {
176 auto written = slot.sink->write(slot.current);
177 if (!written) {
178 if (slot.cancelOnError) {
179 slot.phase = Phase::Cancelled;
180 if (slot.onCancel) slot.onCancel();
181 bumpGeneration(slot.generation);
182 recycleFloat(slot);
183 }
184 return written;
185 }
186 }
187 if (fireUpdate && slot.onUpdate) slot.onUpdate(slot.current);
188 return okApplied();
189}
190
191
192eve::Result<void> MotionRuntime::applyVec2(Vec2Slot &slot, float linearT, bool fireUpdate) {
193 const float eased = evaluateMotionEase(linearT, slot.ease.c_str());
194 if (slot.style == MotionStyle::Tween) {
195 const MotionVec2 a = slot.reverse ? slot.to : slot.from;
196 const MotionVec2 b = slot.reverse ? slot.from : slot.to;
197 slot.current = lerpVec2(a, b, eased);
198 } else {
199 const float sx = slot.reverse ? -slot.to.x : slot.to.x;
200 const float sy = slot.reverse ? -slot.to.y : slot.to.y;
201 float wave = evaluateMotionOscillation(eased, slot.frequency, slot.dampingRatio);
202 float wx = wave;
203 float wy = wave;
204 if (slot.style == MotionStyle::Shake) {
205 wx *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 0);
206 wy *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 1);
207 }
208 slot.current = MotionVec2{slot.from.x + sx * wx, slot.from.y + sy * wy};
209 }
210 if (slot.sink) {
211 auto written = slot.sink->write(slot.current);
212 if (!written) {
213 if (slot.cancelOnError) {
214 slot.phase = Phase::Cancelled;
215 if (slot.onCancel) slot.onCancel();
216 bumpGeneration(slot.generation);
217 recycleVec2(slot);
218 }
219 return written;
220 }
221 }
222 if (fireUpdate && slot.onUpdate) slot.onUpdate(slot.current);
223 return okApplied();
224}
225
226
227eve::Result<void> MotionRuntime::applyVec3(Vec3Slot &slot, float linearT, bool fireUpdate) {
228 const float eased = evaluateMotionEase(linearT, slot.ease.c_str());
229 if (slot.style == MotionStyle::Tween) {
230 const MotionVec3 a = slot.reverse ? slot.to : slot.from;
231 const MotionVec3 b = slot.reverse ? slot.from : slot.to;
232 slot.current = lerpVec3(a, b, eased);
233 } else {
234 const float sx = slot.reverse ? -slot.to.x : slot.to.x;
235 const float sy = slot.reverse ? -slot.to.y : slot.to.y;
236 const float sz = slot.reverse ? -slot.to.z : slot.to.z;
237 float wave = evaluateMotionOscillation(eased, slot.frequency, slot.dampingRatio);
238 float wx = wave, wy = wave, wz = wave;
239 if (slot.style == MotionStyle::Shake) {
240 wx *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 0);
241 wy *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 1);
242 wz *= evaluateMotionShakeSign(slot.seed, slot.frequency, eased, 2);
243 }
244 slot.current = MotionVec3{slot.from.x + sx * wx, slot.from.y + sy * wy, slot.from.z + sz * wz};
245 }
246 if (slot.sink) {
247 auto written = slot.sink->write(slot.current);
248 if (!written) {
249 if (slot.cancelOnError) {
250 slot.phase = Phase::Cancelled;
251 if (slot.onCancel) slot.onCancel();
252 bumpGeneration(slot.generation);
253 recycleVec3(slot);
254 }
255 return written;
256 }
257 }
258 if (fireUpdate && slot.onUpdate) slot.onUpdate(slot.current);
259 return okApplied();
260}
261
262
263eve::Result<void> MotionRuntime::finishFloat(FloatSlot &slot) {
264 auto applied = applyFloat(slot, 1.f, true);
265 if (!applied) return applied;
266 if (slot.phase != Phase::Running) return applied;
267
268 ++slot.played;
269 if (slot.loops >= 0 && slot.played >= slot.loops) {
270 slot.phase = Phase::Completed;
271 if (slot.onComplete) slot.onComplete();
272 bumpGeneration(slot.generation);
273 recycleFloat(slot);
274 return okApplied();
275 }
276 if (slot.loopMode == MotionLoopMode::Yoyo) slot.reverse = !slot.reverse;
277 slot.elapsed = 0.f;
278 return applyFloat(slot, 0.f, true);
279}
280
281eve::Result<void> MotionRuntime::finishVec2(Vec2Slot &slot) {
282 auto applied = applyVec2(slot, 1.f, true);
283 if (!applied) return applied;
284 if (slot.phase != Phase::Running) return applied;
285
286 ++slot.played;
287 if (slot.loops >= 0 && slot.played >= slot.loops) {
288 slot.phase = Phase::Completed;
289 if (slot.onComplete) slot.onComplete();
290 bumpGeneration(slot.generation);
291 recycleVec2(slot);
292 return okApplied();
293 }
294 if (slot.loopMode == MotionLoopMode::Yoyo) slot.reverse = !slot.reverse;
295 slot.elapsed = 0.f;
296 return applyVec2(slot, 0.f, true);
297}
298
299eve::Result<void> MotionRuntime::finishVec3(Vec3Slot &slot) {
300 auto applied = applyVec3(slot, 1.f, true);
301 if (!applied) return applied;
302 if (slot.phase != Phase::Running) return applied;
303
304 ++slot.played;
305 if (slot.loops >= 0 && slot.played >= slot.loops) {
306 slot.phase = Phase::Completed;
307 if (slot.onComplete) slot.onComplete();
308 bumpGeneration(slot.generation);
309 recycleVec3(slot);
310 return okApplied();
311 }
312 if (slot.loopMode == MotionLoopMode::Yoyo) slot.reverse = !slot.reverse;
313 slot.elapsed = 0.f;
314 return applyVec3(slot, 0.f, true);
315}
316
317eve::Result<void> MotionRuntime::stepFloat(FloatSlot &slot, float dt) {
318 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return okNoOp();
319
320 if (slot.phase == Phase::Delayed) {
321 slot.delayLeft -= dt;
322 if (slot.delayLeft > 0.f) return okApplied();
323 dt = -slot.delayLeft;
324 slot.delayLeft = 0.f;
325 slot.phase = Phase::Running;
326 auto started = applyFloat(slot, 0.f, true);
327 if (!started) return started;
328 if (dt <= 0.f) return started;
329 }
330
331 if (slot.duration <= 0.f) return finishFloat(slot);
332
333 slot.elapsed += dt;
334 while (slot.phase == Phase::Running && slot.elapsed >= slot.duration) {
335 const float over = slot.elapsed - slot.duration;
336 auto finished = finishFloat(slot);
337 if (!finished) return finished;
338 if (slot.phase != Phase::Running) break;
339 slot.elapsed = over;
340 }
341 if (slot.phase == Phase::Running) return applyFloat(slot, slot.elapsed / slot.duration, true);
342 return okApplied();
343}
344
345eve::Result<void> MotionRuntime::stepVec2(Vec2Slot &slot, float dt) {
346 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return okNoOp();
347
348 if (slot.phase == Phase::Delayed) {
349 slot.delayLeft -= dt;
350 if (slot.delayLeft > 0.f) return okApplied();
351 dt = -slot.delayLeft;
352 slot.delayLeft = 0.f;
353 slot.phase = Phase::Running;
354 auto started = applyVec2(slot, 0.f, true);
355 if (!started) return started;
356 if (dt <= 0.f) return started;
357 }
358
359 if (slot.duration <= 0.f) return finishVec2(slot);
360
361 slot.elapsed += dt;
362 while (slot.phase == Phase::Running && slot.elapsed >= slot.duration) {
363 const float over = slot.elapsed - slot.duration;
364 auto finished = finishVec2(slot);
365 if (!finished) return finished;
366 if (slot.phase != Phase::Running) break;
367 slot.elapsed = over;
368 }
369 if (slot.phase == Phase::Running) return applyVec2(slot, slot.elapsed / slot.duration, true);
370 return okApplied();
371}
372
373eve::Result<void> MotionRuntime::stepVec3(Vec3Slot &slot, float dt) {
374 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return okNoOp();
375
376 if (slot.phase == Phase::Delayed) {
377 slot.delayLeft -= dt;
378 if (slot.delayLeft > 0.f) return okApplied();
379 dt = -slot.delayLeft;
380 slot.delayLeft = 0.f;
381 slot.phase = Phase::Running;
382 auto started = applyVec3(slot, 0.f, true);
383 if (!started) return started;
384 if (dt <= 0.f) return started;
385 }
386
387 if (slot.duration <= 0.f) return finishVec3(slot);
388
389 slot.elapsed += dt;
390 while (slot.phase == Phase::Running && slot.elapsed >= slot.duration) {
391 const float over = slot.elapsed - slot.duration;
392 auto finished = finishVec3(slot);
393 if (!finished) return finished;
394 if (slot.phase != Phase::Running) break;
395 slot.elapsed = over;
396 }
397 if (slot.phase == Phase::Running) return applyVec3(slot, slot.elapsed / slot.duration, true);
398 return okApplied();
399}
400
401const MotionRuntime::FloatSlot *MotionRuntime::resolveFloat(MotionHandle handle) const {
402 if (handle.isNull() || handle.index >= floats_.size()) return nullptr;
403 const auto &slot = floats_[handle.index];
404 if (slot.generation != handle.generation) return nullptr;
405 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return nullptr;
406 return &slot;
407}
408
409const MotionRuntime::Vec2Slot *MotionRuntime::resolveVec2(MotionHandle handle) const {
410 if (handle.isNull() || handle.index >= vec2s_.size()) return nullptr;
411 const auto &slot = vec2s_[handle.index];
412 if (slot.generation != handle.generation) return nullptr;
413 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return nullptr;
414 return &slot;
415}
416
417const MotionRuntime::Vec3Slot *MotionRuntime::resolveVec3(MotionHandle handle) const {
418 if (handle.isNull() || handle.index >= vec3s_.size()) return nullptr;
419 const auto &slot = vec3s_[handle.index];
420 if (slot.generation != handle.generation) return nullptr;
421 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return nullptr;
422 return &slot;
423}
424
425eve::Result<MotionHandle> MotionRuntime::occupyFloat(FloatSlot slot) {
426 slot.phase = slot.delayLeft > 0.f ? Phase::Delayed : Phase::Running;
427 slot.elapsed = 0.f;
428 slot.played = 0;
429 slot.reverse = false;
430 slot.current = slot.from;
431
432 std::uint32_t index = 0;
433 if (!freeFloats_.empty()) {
434 index = freeFloats_.back();
435 freeFloats_.pop_back();
436 const std::uint32_t gen = floats_[index].generation == 0 ? 1u : floats_[index].generation;
437 floats_[index] = std::move(slot);
438 floats_[index].generation = gen;
439 } else {
440 index = static_cast<std::uint32_t>(floats_.size());
441 floats_.push_back(std::move(slot));
442 }
443
444 MotionHandle handle{index, floats_[index].generation};
445 if (floats_[index].phase == Phase::Running) {
446 auto applied = applyFloat(floats_[index], 0.f, true);
447 if (!applied) return eve::Result<MotionHandle>::failure(applied.status());
448 if (floats_[index].phase != Phase::Running)
449 return eve::Result<MotionHandle>::failure(staleDiag());
450 handle.generation = floats_[index].generation;
451 }
453}
454
455
456eve::Result<MotionHandle> MotionRuntime::occupyVec2(Vec2Slot slot) {
457 slot.phase = slot.delayLeft > 0.f ? Phase::Delayed : Phase::Running;
458 slot.elapsed = 0.f;
459 slot.played = 0;
460 slot.reverse = false;
461 slot.current = slot.from;
462
463 std::uint32_t index = 0;
464 if (!freeVec2s_.empty()) {
465 index = freeVec2s_.back();
466 freeVec2s_.pop_back();
467 const std::uint32_t gen = vec2s_[index].generation == 0 ? 1u : vec2s_[index].generation;
468 vec2s_[index] = std::move(slot);
469 vec2s_[index].generation = gen;
470 } else {
471 index = static_cast<std::uint32_t>(vec2s_.size());
472 vec2s_.push_back(std::move(slot));
473 }
474
475 MotionHandle handle{index, vec2s_[index].generation};
476 if (vec2s_[index].phase == Phase::Running) {
477 auto applied = applyVec2(vec2s_[index], 0.f, true);
478 if (!applied) return eve::Result<MotionHandle>::failure(applied.status());
479 if (vec2s_[index].phase != Phase::Running)
480 return eve::Result<MotionHandle>::failure(staleDiag());
481 handle.generation = vec2s_[index].generation;
482 }
484}
485
486
487eve::Result<MotionHandle> MotionRuntime::occupyVec3(Vec3Slot slot) {
488 slot.phase = slot.delayLeft > 0.f ? Phase::Delayed : Phase::Running;
489 slot.elapsed = 0.f;
490 slot.played = 0;
491 slot.reverse = false;
492 slot.current = slot.from;
493
494 std::uint32_t index = 0;
495 if (!freeVec3s_.empty()) {
496 index = freeVec3s_.back();
497 freeVec3s_.pop_back();
498 const std::uint32_t gen = vec3s_[index].generation == 0 ? 1u : vec3s_[index].generation;
499 vec3s_[index] = std::move(slot);
500 vec3s_[index].generation = gen;
501 } else {
502 index = static_cast<std::uint32_t>(vec3s_.size());
503 vec3s_.push_back(std::move(slot));
504 }
505
506 MotionHandle handle{index, vec3s_[index].generation};
507 if (vec3s_[index].phase == Phase::Running) {
508 auto applied = applyVec3(vec3s_[index], 0.f, true);
509 if (!applied) return eve::Result<MotionHandle>::failure(applied.status());
510 if (vec3s_[index].phase != Phase::Running)
511 return eve::Result<MotionHandle>::failure(staleDiag());
512 handle.generation = vec3s_[index].generation;
513 }
515}
516
517
519 auto valid = validateDesc(desc.duration, desc.delay, desc.loops, desc.ease.empty() ? "linear" : desc.ease);
520 if (!valid) return eve::Result<MotionHandle>::failure(valid.status());
521 auto styleOk = validateStyle(desc.style, desc.frequency, desc.dampingRatio);
522 if (!styleOk) return eve::Result<MotionHandle>::failure(styleOk.status());
523
524 FloatSlot slot;
525 slot.from = desc.from;
526 slot.to = desc.to;
527 slot.duration = desc.duration;
528 slot.delayLeft = desc.delay;
529 slot.loops = desc.loops;
530 slot.loopMode = desc.loopMode;
531 slot.ease = desc.ease.empty() ? "linear" : desc.ease;
532 slot.sink = desc.sink;
533 slot.onUpdate = desc.onUpdate;
534 slot.onComplete = desc.onComplete;
535 slot.onCancel = desc.onCancel;
536 slot.cancelOnError = desc.cancelOnError;
537 slot.style = desc.style;
538 slot.frequency = desc.frequency;
539 slot.dampingRatio = desc.dampingRatio;
540 slot.seed = desc.seed;
541 return occupyFloat(std::move(slot));
542}
543
545 auto valid = validateDesc(desc.duration, desc.delay, desc.loops, desc.ease.empty() ? "linear" : desc.ease);
546 if (!valid) return eve::Result<MotionHandle>::failure(valid.status());
547 auto styleOk = validateStyle(desc.style, desc.frequency, desc.dampingRatio);
548 if (!styleOk) return eve::Result<MotionHandle>::failure(styleOk.status());
549
550 Vec2Slot slot;
551 slot.from = desc.from;
552 slot.to = desc.to;
553 slot.duration = desc.duration;
554 slot.delayLeft = desc.delay;
555 slot.loops = desc.loops;
556 slot.loopMode = desc.loopMode;
557 slot.ease = desc.ease.empty() ? "linear" : desc.ease;
558 slot.sink = desc.sink;
559 slot.onUpdate = desc.onUpdate;
560 slot.onComplete = desc.onComplete;
561 slot.onCancel = desc.onCancel;
562 slot.cancelOnError = desc.cancelOnError;
563 slot.style = desc.style;
564 slot.frequency = desc.frequency;
565 slot.dampingRatio = desc.dampingRatio;
566 slot.seed = desc.seed;
567 return occupyVec2(std::move(slot));
568}
569
571 auto valid = validateDesc(desc.duration, desc.delay, desc.loops, desc.ease.empty() ? "linear" : desc.ease);
572 if (!valid) return eve::Result<MotionHandle>::failure(valid.status());
573 auto styleOk = validateStyle(desc.style, desc.frequency, desc.dampingRatio);
574 if (!styleOk) return eve::Result<MotionHandle>::failure(styleOk.status());
575
576 Vec3Slot slot;
577 slot.from = desc.from;
578 slot.to = desc.to;
579 slot.duration = desc.duration;
580 slot.delayLeft = desc.delay;
581 slot.loops = desc.loops;
582 slot.loopMode = desc.loopMode;
583 slot.ease = desc.ease.empty() ? "linear" : desc.ease;
584 slot.sink = desc.sink;
585 slot.onUpdate = desc.onUpdate;
586 slot.onComplete = desc.onComplete;
587 slot.onCancel = desc.onCancel;
588 slot.cancelOnError = desc.cancelOnError;
589 slot.style = desc.style;
590 slot.frequency = desc.frequency;
591 slot.dampingRatio = desc.dampingRatio;
592 slot.seed = desc.seed;
593 return occupyVec3(std::move(slot));
594}
595
597 return resolveFloat(handle) || resolveVec2(handle) || resolveVec3(handle) ||
598 resolveColor(handle) || resolveQuat(handle);
599}
600
602 const auto *slot = resolveFloat(handle);
603 if (!slot) return eve::Result<float>::failure(staleDiag());
604 return eve::Result<float>::success(slot->current);
605}
606
608 const auto *slot = resolveVec2(handle);
609 if (!slot) return eve::Result<MotionVec2>::failure(staleDiag());
610 return eve::Result<MotionVec2>::success(slot->current);
611}
612
614 const auto *slot = resolveVec3(handle);
615 if (!slot) return eve::Result<MotionVec3>::failure(staleDiag());
616 return eve::Result<MotionVec3>::success(slot->current);
617}
618
620 if (auto *slot = const_cast<FloatSlot *>(resolveFloat(handle))) {
621 slot->loops = slot->played + 1;
622 slot->phase = Phase::Running;
623 slot->elapsed = slot->duration;
624 return finishFloat(*slot);
625 }
626 if (auto *slot = const_cast<Vec2Slot *>(resolveVec2(handle))) {
627 slot->loops = slot->played + 1;
628 slot->phase = Phase::Running;
629 slot->elapsed = slot->duration;
630 return finishVec2(*slot);
631 }
632 if (auto *slot = const_cast<Vec3Slot *>(resolveVec3(handle))) {
633 slot->loops = slot->played + 1;
634 slot->phase = Phase::Running;
635 slot->elapsed = slot->duration;
636 return finishVec3(*slot);
637 }
638 if (auto *slot = const_cast<ColorSlot *>(resolveColor(handle))) {
639 slot->loops = slot->played + 1;
640 slot->phase = Phase::Running;
641 slot->elapsed = slot->duration;
642 return finishColor(*slot);
643 }
644 if (auto *slot = const_cast<QuatSlot *>(resolveQuat(handle))) {
645 slot->loops = slot->played + 1;
646 slot->phase = Phase::Running;
647 slot->elapsed = slot->duration;
648 return finishQuat(*slot);
649 }
650 return eve::Result<void>::failure(staleDiag());
651}
652
654 if (auto *slot = const_cast<FloatSlot *>(resolveFloat(handle))) {
655 slot->phase = Phase::Cancelled;
656 if (slot->onCancel) slot->onCancel();
657 bumpGeneration(slot->generation);
658 recycleFloat(*slot);
659 return okApplied();
660 }
661 if (auto *slot = const_cast<Vec2Slot *>(resolveVec2(handle))) {
662 slot->phase = Phase::Cancelled;
663 if (slot->onCancel) slot->onCancel();
664 bumpGeneration(slot->generation);
665 recycleVec2(*slot);
666 return okApplied();
667 }
668 if (auto *slot = const_cast<Vec3Slot *>(resolveVec3(handle))) {
669 slot->phase = Phase::Cancelled;
670 if (slot->onCancel) slot->onCancel();
671 bumpGeneration(slot->generation);
672 recycleVec3(*slot);
673 return okApplied();
674 }
675 if (auto *slot = const_cast<ColorSlot *>(resolveColor(handle))) {
676 slot->phase = Phase::Cancelled;
677 if (slot->onCancel) slot->onCancel();
678 bumpGeneration(slot->generation);
679 recycleColor(*slot);
680 return okApplied();
681 }
682 if (auto *slot = const_cast<QuatSlot *>(resolveQuat(handle))) {
683 slot->phase = Phase::Cancelled;
684 if (slot->onCancel) slot->onCancel();
685 bumpGeneration(slot->generation);
686 recycleQuat(*slot);
687 return okApplied();
688 }
689 return eve::Result<void>::failure(staleDiag());
690}
691
692int MotionRuntime::activeCount() const noexcept {
693 int n = 0;
694 for (const auto &s : floats_)
695 if (s.phase == Phase::Delayed || s.phase == Phase::Running) ++n;
696 for (const auto &s : vec2s_)
697 if (s.phase == Phase::Delayed || s.phase == Phase::Running) ++n;
698 for (const auto &s : vec3s_)
699 if (s.phase == Phase::Delayed || s.phase == Phase::Running) ++n;
700 for (const auto &s : colors_)
701 if (s.phase == Phase::Delayed || s.phase == Phase::Running) ++n;
702 for (const auto &s : quats_)
703 if (s.phase == Phase::Delayed || s.phase == Phase::Running) ++n;
704 return n;
705}
706
708 auto seconds = detail::secondsForStep(step, hasLastTick_, lastTick_, "MotionRuntime");
709 if (!seconds) return eve::Result<void>::failure(seconds.status());
710 const float dt = std::move(seconds).takeValue();
711
712 for (auto &slot : floats_) {
713 auto r = stepFloat(slot, dt);
714 if (!r) return r;
715 }
716 for (auto &slot : vec2s_) {
717 auto r = stepVec2(slot, dt);
718 if (!r) return r;
719 }
720 for (auto &slot : vec3s_) {
721 auto r = stepVec3(slot, dt);
722 if (!r) return r;
723 }
724 for (auto &slot : colors_) {
725 auto r = stepColor(slot, dt);
726 if (!r) return r;
727 }
728 for (auto &slot : quats_) {
729 auto r = stepQuat(slot, dt);
730 if (!r) return r;
731 }
732
733 lastTick_ = step.tick;
734 hasLastTick_ = true;
735 return okApplied();
736}
737
738
739eve::Result<void> MotionRuntime::applyColor(ColorSlot &slot, float linearT, bool fireUpdate) {
740 const float eased = evaluateMotionEase(linearT, slot.ease.c_str());
741 const MotionColor a = slot.reverse ? slot.to : slot.from;
742 const MotionColor b = slot.reverse ? slot.from : slot.to;
743 slot.current = lerpMotionColor(a, b, eased);
744 if (slot.sink) {
745 auto written = slot.sink->write(slot.current);
746 if (!written) {
747 if (slot.cancelOnError) {
748 slot.phase = Phase::Cancelled;
749 if (slot.onCancel) slot.onCancel();
750 bumpGeneration(slot.generation);
751 recycleColor(slot);
752 }
753 return written;
754 }
755 }
756 if (fireUpdate && slot.onUpdate) slot.onUpdate(slot.current);
757 return okApplied();
758}
759
760eve::Result<void> MotionRuntime::applyQuat(QuatSlot &slot, float linearT, bool fireUpdate) {
761 const float eased = evaluateMotionEase(linearT, slot.ease.c_str());
762 const MotionQuat a = slot.reverse ? slot.to : slot.from;
763 const MotionQuat b = slot.reverse ? slot.from : slot.to;
764 slot.current = slerpMotionQuat(a, b, eased);
765 if (slot.sink) {
766 auto written = slot.sink->write(slot.current);
767 if (!written) {
768 if (slot.cancelOnError) {
769 slot.phase = Phase::Cancelled;
770 if (slot.onCancel) slot.onCancel();
771 bumpGeneration(slot.generation);
772 recycleQuat(slot);
773 }
774 return written;
775 }
776 }
777 if (fireUpdate && slot.onUpdate) slot.onUpdate(slot.current);
778 return okApplied();
779}
780
781eve::Result<void> MotionRuntime::finishColor(ColorSlot &slot) {
782 auto applied = applyColor(slot, 1.f, true);
783 if (!applied) return applied;
784 if (slot.phase != Phase::Running) return applied;
785
786 ++slot.played;
787 if (slot.loops >= 0 && slot.played >= slot.loops) {
788 slot.phase = Phase::Completed;
789 if (slot.onComplete) slot.onComplete();
790 bumpGeneration(slot.generation);
791 recycleColor(slot);
792 return okApplied();
793 }
794 if (slot.loopMode == MotionLoopMode::Yoyo) slot.reverse = !slot.reverse;
795 slot.elapsed = 0.f;
796 return applyColor(slot, 0.f, true);
797}
798
799eve::Result<void> MotionRuntime::finishQuat(QuatSlot &slot) {
800 auto applied = applyQuat(slot, 1.f, true);
801 if (!applied) return applied;
802 if (slot.phase != Phase::Running) return applied;
803
804 ++slot.played;
805 if (slot.loops >= 0 && slot.played >= slot.loops) {
806 slot.phase = Phase::Completed;
807 if (slot.onComplete) slot.onComplete();
808 bumpGeneration(slot.generation);
809 recycleQuat(slot);
810 return okApplied();
811 }
812 if (slot.loopMode == MotionLoopMode::Yoyo) slot.reverse = !slot.reverse;
813 slot.elapsed = 0.f;
814 return applyQuat(slot, 0.f, true);
815}
816
817eve::Result<void> MotionRuntime::stepColor(ColorSlot &slot, float dt) {
818 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return okNoOp();
819
820 if (slot.phase == Phase::Delayed) {
821 slot.delayLeft -= dt;
822 if (slot.delayLeft > 0.f) return okApplied();
823 dt = -slot.delayLeft;
824 slot.delayLeft = 0.f;
825 slot.phase = Phase::Running;
826 auto started = applyColor(slot, 0.f, true);
827 if (!started) return started;
828 if (dt <= 0.f) return started;
829 }
830
831 if (slot.duration <= 0.f) return finishColor(slot);
832
833 slot.elapsed += dt;
834 while (slot.phase == Phase::Running && slot.elapsed >= slot.duration) {
835 const float over = slot.elapsed - slot.duration;
836 auto finished = finishColor(slot);
837 if (!finished) return finished;
838 if (slot.phase != Phase::Running) break;
839 slot.elapsed = over;
840 }
841 if (slot.phase == Phase::Running) return applyColor(slot, slot.elapsed / slot.duration, true);
842 return okApplied();
843}
844
845eve::Result<void> MotionRuntime::stepQuat(QuatSlot &slot, float dt) {
846 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return okNoOp();
847
848 if (slot.phase == Phase::Delayed) {
849 slot.delayLeft -= dt;
850 if (slot.delayLeft > 0.f) return okApplied();
851 dt = -slot.delayLeft;
852 slot.delayLeft = 0.f;
853 slot.phase = Phase::Running;
854 auto started = applyQuat(slot, 0.f, true);
855 if (!started) return started;
856 if (dt <= 0.f) return started;
857 }
858
859 if (slot.duration <= 0.f) return finishQuat(slot);
860
861 slot.elapsed += dt;
862 while (slot.phase == Phase::Running && slot.elapsed >= slot.duration) {
863 const float over = slot.elapsed - slot.duration;
864 auto finished = finishQuat(slot);
865 if (!finished) return finished;
866 if (slot.phase != Phase::Running) break;
867 slot.elapsed = over;
868 }
869 if (slot.phase == Phase::Running) return applyQuat(slot, slot.elapsed / slot.duration, true);
870 return okApplied();
871}
872
873const MotionRuntime::ColorSlot *MotionRuntime::resolveColor(MotionHandle handle) const {
874 if (handle.isNull() || handle.index >= colors_.size()) return nullptr;
875 const auto &slot = colors_[handle.index];
876 if (slot.generation != handle.generation) return nullptr;
877 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return nullptr;
878 return &slot;
879}
880
881const MotionRuntime::QuatSlot *MotionRuntime::resolveQuat(MotionHandle handle) const {
882 if (handle.isNull() || handle.index >= quats_.size()) return nullptr;
883 const auto &slot = quats_[handle.index];
884 if (slot.generation != handle.generation) return nullptr;
885 if (slot.phase != Phase::Delayed && slot.phase != Phase::Running) return nullptr;
886 return &slot;
887}
888
889eve::Result<MotionHandle> MotionRuntime::occupyColor(ColorSlot slot) {
890 slot.phase = slot.delayLeft > 0.f ? Phase::Delayed : Phase::Running;
891 slot.elapsed = 0.f;
892 slot.played = 0;
893 slot.reverse = false;
894 slot.current = slot.from;
895
896 std::uint32_t index = 0;
897 if (!freeColors_.empty()) {
898 index = freeColors_.back();
899 freeColors_.pop_back();
900 const std::uint32_t gen = colors_[index].generation == 0 ? 1u : colors_[index].generation;
901 colors_[index] = std::move(slot);
902 colors_[index].generation = gen;
903 } else {
904 index = static_cast<std::uint32_t>(colors_.size());
905 colors_.push_back(std::move(slot));
906 }
907
908 MotionHandle handle{index, colors_[index].generation};
909 if (colors_[index].phase == Phase::Running) {
910 auto applied = applyColor(colors_[index], 0.f, true);
911 if (!applied) return eve::Result<MotionHandle>::failure(applied.status());
912 if (colors_[index].phase != Phase::Running)
913 return eve::Result<MotionHandle>::failure(staleDiag());
914 handle.generation = colors_[index].generation;
915 }
917}
918
919
920eve::Result<MotionHandle> MotionRuntime::occupyQuat(QuatSlot slot) {
921 slot.phase = slot.delayLeft > 0.f ? Phase::Delayed : Phase::Running;
922 slot.elapsed = 0.f;
923 slot.played = 0;
924 slot.reverse = false;
925 slot.current = slot.from;
926
927 std::uint32_t index = 0;
928 if (!freeQuats_.empty()) {
929 index = freeQuats_.back();
930 freeQuats_.pop_back();
931 const std::uint32_t gen = quats_[index].generation == 0 ? 1u : quats_[index].generation;
932 quats_[index] = std::move(slot);
933 quats_[index].generation = gen;
934 } else {
935 index = static_cast<std::uint32_t>(quats_.size());
936 quats_.push_back(std::move(slot));
937 }
938
939 MotionHandle handle{index, quats_[index].generation};
940 if (quats_[index].phase == Phase::Running) {
941 auto applied = applyQuat(quats_[index], 0.f, true);
942 if (!applied) return eve::Result<MotionHandle>::failure(applied.status());
943 if (quats_[index].phase != Phase::Running)
944 return eve::Result<MotionHandle>::failure(staleDiag());
945 handle.generation = quats_[index].generation;
946 }
948}
949
950
952 auto valid = validateDesc(desc.duration, desc.delay, desc.loops, desc.ease.empty() ? "linear" : desc.ease);
953 if (!valid) return eve::Result<MotionHandle>::failure(valid.status());
954
955 ColorSlot slot;
956 slot.from = desc.from;
957 slot.to = desc.to;
958 slot.duration = desc.duration;
959 slot.delayLeft = desc.delay;
960 slot.loops = desc.loops;
961 slot.loopMode = desc.loopMode;
962 slot.ease = desc.ease.empty() ? "linear" : desc.ease;
963 slot.sink = desc.sink;
964 slot.onUpdate = desc.onUpdate;
965 slot.onComplete = desc.onComplete;
966 slot.onCancel = desc.onCancel;
967 slot.cancelOnError = desc.cancelOnError;
968 return occupyColor(std::move(slot));
969}
970
972 auto valid = validateDesc(desc.duration, desc.delay, desc.loops, desc.ease.empty() ? "linear" : desc.ease);
973 if (!valid) return eve::Result<MotionHandle>::failure(valid.status());
974
975 QuatSlot slot;
976 slot.from = desc.from;
977 slot.to = desc.to;
978 slot.duration = desc.duration;
979 slot.delayLeft = desc.delay;
980 slot.loops = desc.loops;
981 slot.loopMode = desc.loopMode;
982 slot.ease = desc.ease.empty() ? "linear" : desc.ease;
983 slot.sink = desc.sink;
984 slot.onUpdate = desc.onUpdate;
985 slot.onComplete = desc.onComplete;
986 slot.onCancel = desc.onCancel;
987 slot.cancelOnError = desc.cancelOnError;
988 return occupyQuat(std::move(slot));
989}
990
992 const auto *slot = resolveColor(handle);
993 if (!slot) return eve::Result<MotionColor>::failure(staleDiag());
994 return eve::Result<MotionColor>::success(slot->current);
995}
996
998 const auto *slot = resolveQuat(handle);
999 if (!slot) return eve::Result<MotionQuat>::failure(staleDiag());
1000 return eve::Result<MotionQuat>::success(slot->current);
1001}
1002
1003} // namespace eve::animation
float duration
const std::string & s
float phase
Definition CaveMesh.cpp:58
Stable, structured diagnostics shared by engine modules.
scene::NodeDesc desc
glm::vec3 n
Definition Grass.cpp:63
double r
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
Dense motion storage advanced by Animation::advance(SimulationStep).
PrimitiveHandle handle
std::uint32_t count
float step
Definition TreeMesh.cpp:314
uint32_t index
float wz
float wx
float wy
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
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
void ensureVec2Capacity(std::size_t count)
eve::Result< MotionQuat > quatValue(MotionHandle handle) const
bool isActive(MotionHandle handle) const noexcept
void ensureQuatCapacity(std::size_t count)
void ensureFloatCapacity(std::size_t count)
Pre-size float pool slots (Inactive) and reserve storage.
eve::Result< MotionVec3 > vec3Value(MotionHandle handle) const
eve::Result< MotionHandle > spawnColor(const ColorDesc &desc)
void ensureVec3Capacity(std::size_t count)
eve::Result< MotionColor > colorValue(MotionHandle handle) const
eve::Result< MotionHandle > spawnFloat(const FloatDesc &desc)
int activeCount() const noexcept
eve::Result< void > advance(const eve::SimulationStep &step)
eve::Result< MotionHandle > spawnVec2(const Vec2Desc &desc)
eve::Result< void > complete(MotionHandle handle)
eve::Result< MotionVec2 > vec2Value(MotionHandle handle) const
eve::Result< float > floatValue(MotionHandle handle) const
void ensureColorCapacity(std::size_t count)
eve::Result< MotionHandle > spawnVec3(const Vec3Desc &desc)
eve::Result< MotionHandle > spawnQuat(const QuatDesc &desc)
eve::Result< void > cancel(MotionHandle handle)
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.
MotionQuat slerpMotionQuat(MotionQuat a, MotionQuat b, float t)
Spherical linear interpolation for MotionQuat (shortest path).
float evaluateMotionOscillation(float t, int frequency, float dampingRatio)
LitMotion-style damped sine envelope in [0,1] progress.
MotionColor lerpMotionColor(MotionColor a, MotionColor b, float t)
Component-wise lerp for MotionColor.
float evaluateMotionShakeSign(std::uint32_t seed, int frequency, float t, int axis)
Deterministic shake sign in [-1,1] for axis (0..n).
@ Yoyo
Alternate direction each cycle.
float evaluateMotionEase(float t, const char *kind)
Evaluate an ease curve; kinds match Math.ease / Tween.
MotionStyle
Evaluation style for a motion slot.
Definition MotionTypes.h:77
@ Tween
Lerp/slerp from -> to.
@ Shake
Punch with per-axis deterministic random sign.
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Compact RGBA color used by motion adapters.
Definition MotionTypes.h:52
Generation-checked slot identity for a live motion.
Definition MotionTypes.h:20
bool started
Definition Graphics.cpp:183