载入中...
搜索中...
未找到
AnimClip.cpp
浏览该文件的文档.
5#include "animation/AnimSmr.h"
6
7#include "common/Exception.h"
8
9#include <algorithm>
10#include <cctype>
11#include <cmath>
12#include <functional>
13#include <memory>
14#include <utility>
15
16namespace eve::animation {
17
18AnimClip::AnimClip(std::string name) : name_(std::move(name)) {}
19
21
22void AnimClip::setDuration(float seconds) {
23 if (seconds < 0.f) throw Exception("AnimClip.setDuration: duration must be >= 0");
24 for (const auto& event : events_)
25 if (event.t > seconds)
26 throw Exception("AnimClip.setDuration: event '%s' lies past duration", event.name.c_str());
27 for (const auto& marker : syncMarkers_)
28 if (marker.t > seconds)
29 throw Exception("AnimClip.setDuration: sync marker '%s' lies past duration", marker.name.c_str());
30 duration_ = seconds;
31}
32
34 if (hz <= 0.f) throw Exception("AnimClip.setSampleRate: hz must be > 0");
35 sampleRate_ = hz;
36}
37
38void AnimClip::ensureBone(int boneIndex) {
39 if (boneIndex < 0) throw Exception("AnimClip: invalid bone index %d", boneIndex);
40 if (boneIndex >= static_cast<int>(tracks_.size())) {
41 tracks_.resize(static_cast<size_t>(boneIndex) + 1);
42 }
43}
44
45void AnimClip::addPositionKey(int boneIndex, float time, float x, float y, float z) {
46 if (time < 0.f) throw Exception("AnimClip.addPositionKey: time must be >= 0");
47 ensureBone(boneIndex);
48 auto& keys = tracks_[static_cast<size_t>(boneIndex)].positions;
49 keys.push_back({time, x, y, z});
50 if (keys.size() > 1 && keys[keys.size() - 2].t >= time)
51 std::sort(keys.begin(), keys.end(), [](const Vec3Key& a, const Vec3Key& b) { return a.t < b.t; });
52 if (time > duration_) duration_ = time;
53}
54
55void AnimClip::addRotationKey(int boneIndex, float time, float x, float y, float z, float w) {
56 if (time < 0.f) throw Exception("AnimClip.addRotationKey: time must be >= 0");
57 ensureBone(boneIndex);
58 QuatKey k{time, x, y, z, w};
59 TransformTRS tmp;
60 tmp.qx = x;
61 tmp.qy = y;
62 tmp.qz = z;
63 tmp.qw = w;
65 k.x = tmp.qx;
66 k.y = tmp.qy;
67 k.z = tmp.qz;
68 k.w = tmp.qw;
69 auto& keys = tracks_[static_cast<size_t>(boneIndex)].rotations;
70 keys.push_back(k);
71 if (keys.size() > 1 && keys[keys.size() - 2].t >= time)
72 std::sort(keys.begin(), keys.end(), [](const QuatKey& a, const QuatKey& b) { return a.t < b.t; });
73 if (time > duration_) duration_ = time;
74}
75
76void AnimClip::addScaleKey(int boneIndex, float time, float x, float y, float z) {
77 if (time < 0.f) throw Exception("AnimClip.addScaleKey: time must be >= 0");
78 ensureBone(boneIndex);
79 auto& keys = tracks_[static_cast<size_t>(boneIndex)].scales;
80 keys.push_back({time, x, y, z});
81 if (keys.size() > 1 && keys[keys.size() - 2].t >= time)
82 std::sort(keys.begin(), keys.end(), [](const Vec3Key& a, const Vec3Key& b) { return a.t < b.t; });
83 if (time > duration_) duration_ = time;
84}
85
86bool AnimClip::setPositionKey(int boneIndex, int keyIndex, float time, float x, float y, float z) {
87 if (time < 0.f) throw Exception("AnimClip.setPositionKey: time must be >= 0");
88 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
89 auto& keys = tracks_[static_cast<size_t>(boneIndex)].positions;
90 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
91 keys[static_cast<size_t>(keyIndex)] = {time, x, y, z};
92 std::stable_sort(keys.begin(), keys.end(), [](const Vec3Key& a, const Vec3Key& b) { return a.t < b.t; });
93 if (time > duration_) duration_ = time;
94 return true;
95}
96
97bool AnimClip::setRotationKey(int boneIndex, int keyIndex, float time, float x, float y, float z, float w) {
98 if (time < 0.f) throw Exception("AnimClip.setRotationKey: time must be >= 0");
99 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
100 auto& keys = tracks_[static_cast<size_t>(boneIndex)].rotations;
101 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
102 TransformTRS normalized;
103 normalized.qx = x;
104 normalized.qy = y;
105 normalized.qz = z;
106 normalized.qw = w;
107 normalized.normalizeRotation();
108 keys[static_cast<size_t>(keyIndex)] = {time, normalized.qx, normalized.qy, normalized.qz, normalized.qw};
109 std::stable_sort(keys.begin(), keys.end(), [](const QuatKey& a, const QuatKey& b) { return a.t < b.t; });
110 if (time > duration_) duration_ = time;
111 return true;
112}
113
114bool AnimClip::setScaleKey(int boneIndex, int keyIndex, float time, float x, float y, float z) {
115 if (time < 0.f) throw Exception("AnimClip.setScaleKey: time must be >= 0");
116 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
117 auto& keys = tracks_[static_cast<size_t>(boneIndex)].scales;
118 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
119 keys[static_cast<size_t>(keyIndex)] = {time, x, y, z};
120 std::stable_sort(keys.begin(), keys.end(), [](const Vec3Key& a, const Vec3Key& b) { return a.t < b.t; });
121 if (time > duration_) duration_ = time;
122 return true;
123}
124
125bool AnimClip::removePositionKey(int boneIndex, int keyIndex) {
126 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
127 auto& keys = tracks_[static_cast<size_t>(boneIndex)].positions;
128 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
129 keys.erase(keys.begin() + keyIndex);
130 return true;
131}
132
133bool AnimClip::removeRotationKey(int boneIndex, int keyIndex) {
134 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
135 auto& keys = tracks_[static_cast<size_t>(boneIndex)].rotations;
136 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
137 keys.erase(keys.begin() + keyIndex);
138 return true;
139}
140
141bool AnimClip::removeScaleKey(int boneIndex, int keyIndex) {
142 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
143 auto& keys = tracks_[static_cast<size_t>(boneIndex)].scales;
144 if (keyIndex < 0 || keyIndex >= static_cast<int>(keys.size())) return false;
145 keys.erase(keys.begin() + keyIndex);
146 return true;
147}
148
149bool AnimClip::clearTrack(int boneIndex) {
150 if (boneIndex < 0 || boneIndex >= getTrackCount()) return false;
151 tracks_[static_cast<size_t>(boneIndex)] = {};
152 return true;
153}
154
155void AnimClip::addEvent(float time, const std::string& name, const std::string& payload) {
156 if (time < 0.f) throw Exception("AnimClip.addEvent: time must be >= 0");
157 if (duration_ > 0.f && time > duration_) throw Exception("AnimClip.addEvent: time lies past clip duration");
158 if (name.empty()) throw Exception("AnimClip.addEvent: name is empty");
159 events_.push_back({time, name, payload});
160 std::stable_sort(events_.begin(), events_.end(),
161 [](const EventMarker& a, const EventMarker& b) { return a.t < b.t; });
162}
163
164bool AnimClip::setEvent(int index, float time, const std::string& name, const std::string& payload) {
165 if (index < 0 || index >= getEventCount()) return false;
166 if (time < 0.f) throw Exception("AnimClip.setEvent: time must be >= 0");
167 if (duration_ > 0.f && time > duration_) throw Exception("AnimClip.setEvent: time lies past clip duration");
168 if (name.empty()) throw Exception("AnimClip.setEvent: name is empty");
169 events_[static_cast<size_t>(index)] = {time, name, payload};
170 std::stable_sort(events_.begin(), events_.end(),
171 [](const EventMarker& a, const EventMarker& b) { return a.t < b.t; });
172 return true;
173}
174
176 if (index < 0 || index >= getEventCount()) return false;
177 events_.erase(events_.begin() + index);
178 return true;
179}
180
182 if (index < 0 || index >= getEventCount()) return 0.f;
183 return events_[static_cast<size_t>(index)].t;
184}
185
186std::string AnimClip::getEventName(int index) const {
187 if (index < 0 || index >= getEventCount()) return {};
188 return events_[static_cast<size_t>(index)].name;
189}
190
191std::string AnimClip::getEventPayload(int index) const {
192 if (index < 0 || index >= getEventCount()) return {};
193 return events_[static_cast<size_t>(index)].payload;
194}
195
196bool AnimClip::hasEvent(std::string_view name) const noexcept {
197 return !name.empty() &&
198 std::any_of(events_.begin(), events_.end(), [name](const EventMarker& event) { return event.name == name; });
199}
200
201eve::Result<void> AnimClip::validateNotifyContract(const std::vector<std::string>& requiredNames) const {
202 for (std::size_t index = 0; index < requiredNames.size(); ++index) {
203 const std::string& name = requiredNames[index];
204 if (name.empty())
206 eve::DiagnosticCode::InvalidArgument, "animation notify contract contains an empty semantic name",
207 "requiredNames[" + std::to_string(index) + "]", {}, "animation"));
208 if (!hasEvent(name))
210 eve::DiagnosticCode::NotFound, "animation clip is missing required notify: " + name, "clip.events", {},
211 "animation"));
212 }
214}
215
216void AnimClip::addSyncMarker(float time, const std::string& name) {
217 if (time < 0.f) throw Exception("AnimClip.addSyncMarker: time must be >= 0");
218 if (duration_ > 0.f && time > duration_) throw Exception("AnimClip.addSyncMarker: time lies past clip duration");
219 if (name.empty()) throw Exception("AnimClip.addSyncMarker: name is empty");
220 syncMarkers_.push_back({time, name});
221 std::stable_sort(syncMarkers_.begin(), syncMarkers_.end(),
222 [](const SyncMarker& a, const SyncMarker& b) { return a.t < b.t; });
223}
224
225bool AnimClip::setSyncMarker(int index, float time, const std::string& name) {
226 if (index < 0 || index >= getSyncMarkerCount()) return false;
227 if (time < 0.f) throw Exception("AnimClip.setSyncMarker: time must be >= 0");
228 if (duration_ > 0.f && time > duration_) throw Exception("AnimClip.setSyncMarker: time lies past clip duration");
229 if (name.empty()) throw Exception("AnimClip.setSyncMarker: name is empty");
230 syncMarkers_[static_cast<size_t>(index)] = {time, name};
231 std::stable_sort(syncMarkers_.begin(), syncMarkers_.end(),
232 [](const SyncMarker& a, const SyncMarker& b) { return a.t < b.t; });
233 return true;
234}
235
237 if (index < 0 || index >= getSyncMarkerCount()) return false;
238 syncMarkers_.erase(syncMarkers_.begin() + index);
239 return true;
240}
241
243 if (index < 0 || index >= getSyncMarkerCount()) return 0.f;
244 return syncMarkers_[static_cast<size_t>(index)].t;
245}
246
247std::string AnimClip::getSyncMarkerName(int index) const {
248 if (index < 0 || index >= getSyncMarkerCount()) return {};
249 return syncMarkers_[static_cast<size_t>(index)].name;
250}
251
253 if (!target || duration_ <= 1e-8f || target->duration_ <= 1e-8f) return false;
254 std::vector<const SyncMarker*> sourceCommon;
255 std::vector<const SyncMarker*> targetCommon;
256 for (const SyncMarker& marker : syncMarkers_) {
257 if (std::any_of(target->syncMarkers_.begin(), target->syncMarkers_.end(),
258 [&](const SyncMarker& other) { return other.name == marker.name; }))
259 sourceCommon.push_back(&marker);
260 }
261 for (const SyncMarker& marker : target->syncMarkers_) {
262 if (std::any_of(syncMarkers_.begin(), syncMarkers_.end(),
263 [&](const SyncMarker& other) { return other.name == marker.name; }))
264 targetCommon.push_back(&marker);
265 }
266 if (sourceCommon.size() < 2 || targetCommon.size() < 2) return false;
267 const size_t sourcePairCount = loop_ ? sourceCommon.size() : sourceCommon.size() - 1;
268 const size_t targetPairCount = target->loop_ ? targetCommon.size() : targetCommon.size() - 1;
269 for (size_t sourceIndex = 0; sourceIndex < sourcePairCount; ++sourceIndex) {
270 const std::string& previous = sourceCommon[sourceIndex]->name;
271 const std::string& next = sourceCommon[(sourceIndex + 1) % sourceCommon.size()]->name;
272 for (size_t targetIndex = 0; targetIndex < targetPairCount; ++targetIndex)
273 if (targetCommon[targetIndex]->name == previous &&
274 targetCommon[(targetIndex + 1) % targetCommon.size()]->name == next)
275 return true;
276 }
277 return false;
278}
279
280float AnimClip::mapSyncTimeTo(float time, const AnimClip* target) const {
281 if (!target || duration_ <= 1e-8f || target->duration_ <= 1e-8f) return 0.f;
282 const float normalizedFallback = wrapTime(time) / duration_ * target->duration_;
283 if (!hasCompatibleSyncMarkers(target)) return normalizedFallback;
284
285 std::vector<const SyncMarker*> sourceCommon;
286 std::vector<const SyncMarker*> targetCommon;
287 for (const SyncMarker& marker : syncMarkers_) {
288 if (std::any_of(target->syncMarkers_.begin(), target->syncMarkers_.end(),
289 [&](const SyncMarker& other) { return other.name == marker.name; }))
290 sourceCommon.push_back(&marker);
291 }
292 for (const SyncMarker& marker : target->syncMarkers_) {
293 if (std::any_of(syncMarkers_.begin(), syncMarkers_.end(),
294 [&](const SyncMarker& other) { return other.name == marker.name; }))
295 targetCommon.push_back(&marker);
296 }
297
298 const float sourceTime = wrapTime(time);
299 if (!loop_ && (sourceTime < sourceCommon.front()->t || sourceTime > sourceCommon.back()->t))
300 return normalizedFallback;
301 size_t sourcePrevious = sourceCommon.size() - 1;
302 for (size_t i = 0; i < sourceCommon.size(); ++i) {
303 if (sourceCommon[i]->t <= sourceTime)
304 sourcePrevious = i;
305 else
306 break;
307 }
308 const size_t sourceNext = (sourcePrevious + 1) % sourceCommon.size();
309 const float sourceStart = sourceCommon[sourcePrevious]->t;
310 float sourceEnd = sourceCommon[sourceNext]->t;
311 float adjustedSourceTime = sourceTime;
312 if (sourceNext == 0) {
313 sourceEnd += duration_;
314 if (adjustedSourceTime < sourceStart) adjustedSourceTime += duration_;
315 }
316 const float span = sourceEnd - sourceStart;
317 if (span <= 1e-8f) return normalizedFallback;
318 const float alpha = clampf((adjustedSourceTime - sourceStart) / span, 0.f, 1.f);
319
320 const std::string& previousName = sourceCommon[sourcePrevious]->name;
321 const std::string& nextName = sourceCommon[sourceNext]->name;
322 int bestTarget = -1;
323 float bestDistance = target->duration_ + 1.f;
324 const float sourcePhase = sourceStart / duration_;
325 const size_t targetPairCount = target->loop_ ? targetCommon.size() : targetCommon.size() - 1;
326 for (size_t i = 0; i < targetPairCount; ++i) {
327 if (targetCommon[i]->name != previousName || targetCommon[(i + 1) % targetCommon.size()]->name != nextName)
328 continue;
329 const float targetPhase = targetCommon[i]->t / target->duration_;
330 const float distance = std::abs(targetPhase - sourcePhase);
331 const float circularDistance = std::min(distance, 1.f - distance);
332 if (circularDistance < bestDistance) {
333 bestDistance = circularDistance;
334 bestTarget = static_cast<int>(i);
335 }
336 }
337 if (bestTarget < 0) return normalizedFallback;
338 const size_t targetPrevious = static_cast<size_t>(bestTarget);
339 const size_t targetNext = (targetPrevious + 1) % targetCommon.size();
340 const float targetStart = targetCommon[targetPrevious]->t;
341 float targetEnd = targetCommon[targetNext]->t;
342 if (targetNext == 0) targetEnd += target->duration_;
343 float mapped = targetStart + alpha * (targetEnd - targetStart);
344 if (target->getLoop()) mapped = std::fmod(mapped, target->duration_);
345 return target->wrapTime(mapped);
346}
347
348void AnimClip::collectEvents(float previousTime, float currentTime, bool loop, std::vector<std::string>& out) const {
349 if (events_.empty() || duration_ <= 0.f || currentTime == previousTime) return;
350 const float from = wrapTime(previousTime);
351 const float to = wrapTime(currentTime);
352 auto appendRange = [&](float lo, float hi, bool includeLo) {
353 for (const EventMarker& event : events_) {
354 if ((includeLo ? event.t >= lo : event.t > lo) && event.t <= hi) out.push_back(event.name);
355 }
356 };
357 if (loop && (currentTime - previousTime >= duration_ || to < from)) {
358 appendRange(from, duration_, false);
359 appendRange(0.f, to, true);
360 } else {
361 appendRange(from, to, false);
362 }
363}
364
365int AnimClip::getPositionKeyCount(int boneIndex) const {
366 if (boneIndex < 0 || boneIndex >= static_cast<int>(tracks_.size())) return 0;
367 return static_cast<int>(tracks_[static_cast<size_t>(boneIndex)].positions.size());
368}
369
370int AnimClip::getRotationKeyCount(int boneIndex) const {
371 if (boneIndex < 0 || boneIndex >= static_cast<int>(tracks_.size())) return 0;
372 return static_cast<int>(tracks_[static_cast<size_t>(boneIndex)].rotations.size());
373}
374
375int AnimClip::getScaleKeyCount(int boneIndex) const {
376 if (boneIndex < 0 || boneIndex >= static_cast<int>(tracks_.size())) return 0;
377 return static_cast<int>(tracks_[static_cast<size_t>(boneIndex)].scales.size());
378}
379
380namespace {
381void requireKey(int boneIndex, int keyIndex, int count, const char* what) {
382 if (boneIndex < 0 || keyIndex < 0 || keyIndex >= count) {
383 throw Exception("AnimClip: invalid %s key bone=%d index=%d", what, boneIndex, keyIndex);
384 }
385}
386} // namespace
387
388float AnimClip::getPositionKeyTime(int boneIndex, int keyIndex) const {
389 requireKey(boneIndex, keyIndex, getPositionKeyCount(boneIndex), "position");
390 return tracks_[static_cast<size_t>(boneIndex)].positions[static_cast<size_t>(keyIndex)].t;
391}
392float AnimClip::getPositionKeyX(int boneIndex, int keyIndex) const {
393 requireKey(boneIndex, keyIndex, getPositionKeyCount(boneIndex), "position");
394 return tracks_[static_cast<size_t>(boneIndex)].positions[static_cast<size_t>(keyIndex)].x;
395}
396float AnimClip::getPositionKeyY(int boneIndex, int keyIndex) const {
397 requireKey(boneIndex, keyIndex, getPositionKeyCount(boneIndex), "position");
398 return tracks_[static_cast<size_t>(boneIndex)].positions[static_cast<size_t>(keyIndex)].y;
399}
400float AnimClip::getPositionKeyZ(int boneIndex, int keyIndex) const {
401 requireKey(boneIndex, keyIndex, getPositionKeyCount(boneIndex), "position");
402 return tracks_[static_cast<size_t>(boneIndex)].positions[static_cast<size_t>(keyIndex)].z;
403}
404
405float AnimClip::getRotationKeyTime(int boneIndex, int keyIndex) const {
406 requireKey(boneIndex, keyIndex, getRotationKeyCount(boneIndex), "rotation");
407 return tracks_[static_cast<size_t>(boneIndex)].rotations[static_cast<size_t>(keyIndex)].t;
408}
409float AnimClip::getRotationKeyX(int boneIndex, int keyIndex) const {
410 requireKey(boneIndex, keyIndex, getRotationKeyCount(boneIndex), "rotation");
411 return tracks_[static_cast<size_t>(boneIndex)].rotations[static_cast<size_t>(keyIndex)].x;
412}
413float AnimClip::getRotationKeyY(int boneIndex, int keyIndex) const {
414 requireKey(boneIndex, keyIndex, getRotationKeyCount(boneIndex), "rotation");
415 return tracks_[static_cast<size_t>(boneIndex)].rotations[static_cast<size_t>(keyIndex)].y;
416}
417float AnimClip::getRotationKeyZ(int boneIndex, int keyIndex) const {
418 requireKey(boneIndex, keyIndex, getRotationKeyCount(boneIndex), "rotation");
419 return tracks_[static_cast<size_t>(boneIndex)].rotations[static_cast<size_t>(keyIndex)].z;
420}
421float AnimClip::getRotationKeyW(int boneIndex, int keyIndex) const {
422 requireKey(boneIndex, keyIndex, getRotationKeyCount(boneIndex), "rotation");
423 return tracks_[static_cast<size_t>(boneIndex)].rotations[static_cast<size_t>(keyIndex)].w;
424}
425
426float AnimClip::getScaleKeyTime(int boneIndex, int keyIndex) const {
427 requireKey(boneIndex, keyIndex, getScaleKeyCount(boneIndex), "scale");
428 return tracks_[static_cast<size_t>(boneIndex)].scales[static_cast<size_t>(keyIndex)].t;
429}
430float AnimClip::getScaleKeyX(int boneIndex, int keyIndex) const {
431 requireKey(boneIndex, keyIndex, getScaleKeyCount(boneIndex), "scale");
432 return tracks_[static_cast<size_t>(boneIndex)].scales[static_cast<size_t>(keyIndex)].x;
433}
434float AnimClip::getScaleKeyY(int boneIndex, int keyIndex) const {
435 requireKey(boneIndex, keyIndex, getScaleKeyCount(boneIndex), "scale");
436 return tracks_[static_cast<size_t>(boneIndex)].scales[static_cast<size_t>(keyIndex)].y;
437}
438float AnimClip::getScaleKeyZ(int boneIndex, int keyIndex) const {
439 requireKey(boneIndex, keyIndex, getScaleKeyCount(boneIndex), "scale");
440 return tracks_[static_cast<size_t>(boneIndex)].scales[static_cast<size_t>(keyIndex)].z;
441}
442
443void AnimClip::applyPlanarRootMotion(int boneIndex, float speedX, float speedZ) {
444 ensureBone(boneIndex);
445 auto& keys = tracks_[static_cast<size_t>(boneIndex)].positions;
446 if (keys.empty()) {
447 // Create start/end keys from zero so motion matching sees a trajectory.
448 keys.push_back({0.f, 0.f, 0.f, 0.f});
449 keys.push_back({duration_, speedX * duration_, 0.f, speedZ * duration_});
450 return;
451 }
452 // Importers commonly reduce a constant in-place root track to one key at
453 // t=0. Preserve its endpoint values before adding travel, otherwise every
454 // added displacement is zero and the clip remains stationary.
455 if (keys.front().t > 0.f) {
456 auto start = keys.front();
457 start.t = 0.f;
458 keys.insert(keys.begin(), start);
459 }
460 if (keys.back().t < duration_) {
461 auto end = keys.back();
462 end.t = duration_;
463 keys.push_back(end);
464 }
465 for (auto& k : keys) {
466 k.x += speedX * k.t;
467 k.z += speedZ * k.t;
468 }
469}
470
471float AnimClip::wrapTime(float time) const {
472 if (duration_ <= 1e-8f) return 0.f;
473 if (loop_) {
474 time = std::fmod(time, duration_);
475 if (time < 0.f) time += duration_;
476 return time;
477 }
478 return clampf(time, 0.f, duration_);
479}
480
481std::unique_ptr<AnimClip> AnimClip::clone() const {
482 auto result = std::make_unique<AnimClip>(name_);
483 result->duration_ = duration_;
484 result->loop_ = loop_;
485 result->sampleRate_ = sampleRate_;
486 result->tracks_ = tracks_;
487 result->events_ = events_;
488 result->syncMarkers_ = syncMarkers_;
489 return result;
490}
491
492void AnimClip::sampleVec3(const std::vector<Vec3Key>& keys, float time, float& x, float& y, float& z, bool& ok) {
493 ok = false;
494 if (keys.empty()) return;
495 ok = true;
496 if (time <= keys.front().t || keys.size() == 1) {
497 x = keys.front().x;
498 y = keys.front().y;
499 z = keys.front().z;
500 return;
501 }
502 if (time >= keys.back().t) {
503 x = keys.back().x;
504 y = keys.back().y;
505 z = keys.back().z;
506 return;
507 }
508 const auto upper = std::upper_bound(keys.data(), keys.data() + keys.size(), time,
509 [](float value, const Vec3Key& key) { return value < key.t; });
510 const auto& b = *upper;
511 const auto& a = *(upper - 1);
512 const float den = b.t - a.t;
513 const float t = den > 1e-8f ? (time - a.t) / den : 0.f;
514 x = lerpf(a.x, b.x, t);
515 y = lerpf(a.y, b.y, t);
516 z = lerpf(a.z, b.z, t);
517}
518
519void AnimClip::sampleQuat(const std::vector<QuatKey>& keys, float time, float& x, float& y, float& z, float& w,
520 bool& ok) {
521 ok = false;
522 if (keys.empty()) return;
523 ok = true;
524 if (time <= keys.front().t || keys.size() == 1) {
525 x = keys.front().x;
526 y = keys.front().y;
527 z = keys.front().z;
528 w = keys.front().w;
529 return;
530 }
531 if (time >= keys.back().t) {
532 x = keys.back().x;
533 y = keys.back().y;
534 z = keys.back().z;
535 w = keys.back().w;
536 return;
537 }
538 const auto upper = std::upper_bound(keys.data(), keys.data() + keys.size(), time,
539 [](float value, const QuatKey& key) { return value < key.t; });
540 const auto& b = *upper;
541 const auto& a = *(upper - 1);
542 const float den = b.t - a.t;
543 const float t = den > 1e-8f ? (time - a.t) / den : 0.f;
544 slerpQuat(a.x, a.y, a.z, a.w, b.x, b.y, b.z, b.w, t, x, y, z, w);
545}
546
547TransformTRS AnimClip::sampleBone(int boneIndex, float time, const TransformTRS& fallback) const {
548 TransformTRS out = fallback;
549 if (boneIndex < 0 || boneIndex >= static_cast<int>(tracks_.size())) return out;
550 const BoneTrack& tr = tracks_[static_cast<size_t>(boneIndex)];
551 bool ok = false;
552 float x, y, z, w;
553 sampleVec3(tr.positions, time, x, y, z, ok);
554 if (ok) {
555 out.px = x;
556 out.py = y;
557 out.pz = z;
558 }
559 sampleQuat(tr.rotations, time, x, y, z, w, ok);
560 if (ok) {
561 out.qx = x;
562 out.qy = y;
563 out.qz = z;
564 out.qw = w;
565 }
566 sampleVec3(tr.scales, time, x, y, z, ok);
567 if (ok) {
568 out.sx = x;
569 out.sy = y;
570 out.sz = z;
571 }
572 return out;
573}
574
575void AnimClip::sample(float time, AnimPose* out, const AnimSkeleton* skeleton) const {
576 sampleClamped(wrapTime(time), out, skeleton);
577}
578
579void AnimClip::sampleLod(float time, AnimPose* out, const AnimSkeleton* skeleton, int lodLevel) const {
580 if (!out || !skeleton) throw Exception("AnimClip.sampleLod: pose or skeleton is null");
581 if (lodLevel < 0) throw Exception("AnimClip.sampleLod: lodLevel must be >= 0");
582 const int boneCount = skeleton->getBoneCount();
583 out->resize(boneCount);
584 time = clampf(wrapTime(time), 0.f, duration_);
585 for (int i = 0; i < boneCount; ++i) {
586 const TransformTRS& bind = skeleton->bindLocal(i);
587 out->local(i) = lodLevel <= skeleton->getBoneLodLimit(i) ? sampleBone(i, time, bind) : bind;
588 }
589}
590
591void AnimClip::sampleClamped(float time, AnimPose* out, const AnimSkeleton* skeleton) const {
592 if (!out) throw Exception("AnimClip.sample: pose is null");
593 const int boneCount = skeleton ? skeleton->getBoneCount() : static_cast<int>(tracks_.size());
594 out->resize(boneCount);
595 time = clampf(time, 0.f, duration_);
596 for (int i = 0; i < boneCount; ++i) {
597 const TransformTRS& fb = skeleton ? skeleton->bindLocal(i) : TransformTRS::identity();
598 out->local(i) = sampleBone(i, time, fb);
599 }
600}
601
602int AnimClip::compress(float positionError, float rotationErrorDegrees, float scaleError) {
603 if (positionError < 0.f || rotationErrorDegrees < 0.f || scaleError < 0.f)
604 throw Exception("AnimClip.compress: tolerances must be >= 0");
605 const float rotationError = rotationErrorDegrees * 0.01745329251994329577f;
606 int removed = 0;
607 for (BoneTrack& track : tracks_) {
608 auto reduceVec3 = [&](std::vector<Vec3Key>& keys, float tolerance) {
609 if (keys.size() <= 2) return;
610 std::vector<unsigned char> keep(keys.size(), 0);
611 keep.front() = keep.back() = 1;
612 std::function<void(size_t, size_t)> split = [&](size_t first, size_t last) {
613 if (last <= first + 1) return;
614 const float span = keys[last].t - keys[first].t;
615 float worst = -1.f;
616 size_t worstIndex = first;
617 for (size_t i = first + 1; i < last; ++i) {
618 const float alpha = span > 1e-8f ? (keys[i].t - keys[first].t) / span : 0.f;
619 const float dx = keys[i].x - lerpf(keys[first].x, keys[last].x, alpha);
620 const float dy = keys[i].y - lerpf(keys[first].y, keys[last].y, alpha);
621 const float dz = keys[i].z - lerpf(keys[first].z, keys[last].z, alpha);
622 const float error = std::sqrt(dx * dx + dy * dy + dz * dz);
623 if (error > worst) {
624 worst = error;
625 worstIndex = i;
626 }
627 }
628 if (worst > tolerance) {
629 keep[worstIndex] = 1;
630 split(first, worstIndex);
631 split(worstIndex, last);
632 }
633 };
634 split(0, keys.size() - 1);
635 std::vector<Vec3Key> reduced;
636 reduced.reserve(keys.size());
637 for (size_t i = 0; i < keys.size(); ++i)
638 if (keep[i]) reduced.push_back(keys[i]);
639 removed += static_cast<int>(keys.size() - reduced.size());
640 keys.swap(reduced);
641 };
642 auto reduceQuat = [&](std::vector<QuatKey>& keys) {
643 if (keys.size() <= 2) return;
644 std::vector<unsigned char> keep(keys.size(), 0);
645 keep.front() = keep.back() = 1;
646 std::function<void(size_t, size_t)> split = [&](size_t first, size_t last) {
647 if (last <= first + 1) return;
648 const float span = keys[last].t - keys[first].t;
649 float worst = -1.f;
650 size_t worstIndex = first;
651 for (size_t i = first + 1; i < last; ++i) {
652 const float alpha = span > 1e-8f ? (keys[i].t - keys[first].t) / span : 0.f;
653 float x, y, z, w;
654 slerpQuat(keys[first].x, keys[first].y, keys[first].z, keys[first].w, keys[last].x, keys[last].y,
655 keys[last].z, keys[last].w, alpha, x, y, z, w);
656 const float dot = std::abs(x * keys[i].x + y * keys[i].y + z * keys[i].z + w * keys[i].w);
657 const float error = 2.f * std::acos(clampf(dot, -1.f, 1.f));
658 if (error > worst) {
659 worst = error;
660 worstIndex = i;
661 }
662 }
663 if (worst > rotationError) {
664 keep[worstIndex] = 1;
665 split(first, worstIndex);
666 split(worstIndex, last);
667 }
668 };
669 split(0, keys.size() - 1);
670 std::vector<QuatKey> reduced;
671 reduced.reserve(keys.size());
672 for (size_t i = 0; i < keys.size(); ++i)
673 if (keep[i]) reduced.push_back(keys[i]);
674 removed += static_cast<int>(keys.size() - reduced.size());
675 keys.swap(reduced);
676 };
677 reduceVec3(track.positions, positionError);
678 reduceQuat(track.rotations);
679 reduceVec3(track.scales, scaleError);
680 }
681 return removed;
682}
683
684void AnimRetargetProfile::addBoneMapping(const std::string& sourceBone, const std::string& targetBone) {
685 if (sourceBone.empty() || targetBone.empty())
686 throw Exception("AnimRetargetProfile.addBoneMapping: bone name must not be empty");
687 for (Mapping& mapping : mappings_) {
688 if (mapping.target == targetBone) {
689 mapping.source = sourceBone;
690 return;
691 }
692 }
693 mappings_.push_back({sourceBone, targetBone});
694}
695
696void AnimRetargetProfile::clearBoneMappings() { mappings_.clear(); }
697
698void AnimRetargetProfile::setRootBones(const std::string& sourceBone, const std::string& targetBone) {
699 sourceRoot_ = sourceBone;
700 targetRoot_ = targetBone;
701}
702
703void AnimRetargetProfile::setRootTranslationScale(float horizontal, float vertical) {
704 if (horizontal < 0.f || vertical < 0.f)
705 throw Exception("AnimRetargetProfile.setRootTranslationScale: scales must be >= 0");
706 rootHorizontalScale_ = horizontal;
707 rootVerticalScale_ = vertical;
708}
709
711 if (index < 0 || index >= getUnmatchedBoneCount()) return {};
712 return unmatchedTargetBones_[static_cast<size_t>(index)];
713}
714
716 if (distance < 0.f) throw Exception("AnimRetargetProfile.setInteractionContactThreshold: distance must be >= 0");
717 interactionContactThreshold_ = distance;
718}
719
721 if (weight < 0.f || weight > 1.f)
722 throw Exception("AnimRetargetProfile.setInteractionCorrectionWeight: weight must be in [0,1]");
723 interactionCorrectionWeight_ = weight;
724}
725
726void AnimRetargetProfile::addInteractionIkChain(const std::string& rootBone, const std::string& midBone,
727 const std::string& tipBone) {
728 if (rootBone.empty() || midBone.empty() || tipBone.empty())
729 throw Exception("AnimRetargetProfile.addInteractionIkChain: bone name must not be empty");
730 interactionIkChains_.push_back(AnimSmrIkChain{rootBone, midBone, tipBone});
731}
732
733void AnimRetargetProfile::clearInteractionIkChains() { interactionIkChains_.clear(); }
734
735namespace {
736
737struct RetargetQuat {
738 float x = 0.f, y = 0.f, z = 0.f, w = 1.f;
739};
740
741RetargetQuat retargetMul(const RetargetQuat& a, const RetargetQuat& b) {
742 return {a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
743 a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z};
744}
745
746RetargetQuat retargetInverse(const RetargetQuat& q) { return {-q.x, -q.y, -q.z, q.w}; }
747
748RetargetQuat rotationOf(const TransformTRS& transform) {
749 return {transform.qx, transform.qy, transform.qz, transform.qw};
750}
751
752std::string normalizedBoneName(const std::string& name) {
753 const size_t separator = name.find_last_of(":|/");
754 const size_t begin = separator == std::string::npos ? 0 : separator + 1;
755 std::string out;
756 for (size_t i = begin; i < name.size(); ++i) {
757 const unsigned char c = static_cast<unsigned char>(name[i]);
758 if (std::isalnum(c)) out.push_back(static_cast<char>(std::tolower(c)));
759 }
760 return out;
761}
762
763int findNormalizedBone(const AnimSkeleton* skeleton, const std::string& name) {
764 const std::string normalized = normalizedBoneName(name);
765 int match = -1;
766 for (int bone = 0; bone < skeleton->getBoneCount(); ++bone) {
767 if (normalizedBoneName(skeleton->getBoneName(bone)) != normalized) continue;
768 if (match >= 0) return -1; // Ambiguous normalized names must be mapped explicitly.
769 match = bone;
770 }
771 return match;
772}
773
774float skeletonExtent(const AnimSkeleton* skeleton) {
775 AnimPose bind;
776 skeleton->applyBindPose(&bind);
777 bind.computeWorld(skeleton);
778 if (skeleton->getBoneCount() == 0) return 1.f;
779 const TransformTRS& root = bind.world(0);
780 float extent = 0.f;
781 for (int bone = 1; bone < skeleton->getBoneCount(); ++bone) {
782 const TransformTRS& world = bind.world(bone);
783 const float dx = world.px - root.px;
784 const float dy = world.py - root.py;
785 const float dz = world.pz - root.pz;
786 extent = std::max(extent, std::sqrt(dx * dx + dy * dy + dz * dz));
787 }
788 return std::max(extent, 1e-6f);
789}
790
791float retargetRootMeasure(const AnimSkeleton* skeleton, const AnimPose& bindPose, int retargetRoot) {
792 if (retargetRoot < 0) return skeletonExtent(skeleton);
793 int hierarchyRoot = retargetRoot;
794 while (skeleton->getParent(hierarchyRoot) >= 0) hierarchyRoot = skeleton->getParent(hierarchyRoot);
795 const TransformTRS& root = bindPose.world(hierarchyRoot);
796 const TransformTRS& pelvis = bindPose.world(retargetRoot);
797 const float dx = pelvis.px - root.px;
798 const float dy = pelvis.py - root.py;
799 const float dz = pelvis.pz - root.pz;
800 const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
801 return distance > 1e-6f ? distance : skeletonExtent(skeleton);
802}
803
804} // namespace
805
806AnimClip* AnimClip::retarget(const AnimSkeleton* sourceSkeleton, const AnimSkeleton* targetSkeleton) const {
807 AnimRetargetProfile profile;
808 return retargetWithProfile(sourceSkeleton, targetSkeleton, &profile);
809}
810
811AnimClip* AnimClip::retargetWithProfile(const AnimSkeleton* sourceSkeleton, const AnimSkeleton* targetSkeleton,
812 AnimRetargetProfile* profile) const {
813 if (!sourceSkeleton || !targetSkeleton) throw Exception("AnimClip.retargetWithProfile: skeleton is null");
814 if (!profile) throw Exception("AnimClip.retargetWithProfile: profile is null");
815
816 auto out = std::make_unique<AnimClip>(name_ + "_retargeted");
817 out->duration_ = duration_;
818 out->loop_ = loop_;
819 out->sampleRate_ = sampleRate_;
820 out->events_ = events_;
821 out->syncMarkers_ = syncMarkers_;
822 out->tracks_.resize(static_cast<size_t>(targetSkeleton->getBoneCount()));
823
824 std::vector<int> sourceForTarget(static_cast<size_t>(targetSkeleton->getBoneCount()), -1);
825 profile->matchedBoneCount_ = 0;
826 profile->unmatchedTargetBones_.clear();
827 for (int targetBone = 0; targetBone < targetSkeleton->getBoneCount(); ++targetBone) {
828 const std::string targetName = targetSkeleton->getBoneName(targetBone);
829 int sourceBone = -1;
830 bool explicitlyMapped = false;
831 for (const auto& mapping : profile->mappings_) {
832 if (mapping.target == targetName) {
833 explicitlyMapped = true;
834 sourceBone = sourceSkeleton->findBone(mapping.source);
835 break;
836 }
837 }
838 if (!explicitlyMapped) sourceBone = sourceSkeleton->findBone(targetName);
839 if (!explicitlyMapped && sourceBone < 0 && profile->normalizedNameMatching_)
840 sourceBone = findNormalizedBone(sourceSkeleton, targetName);
841 if (sourceBone < 0) {
842 profile->unmatchedTargetBones_.push_back(targetName);
843 continue;
844 }
845 sourceForTarget[static_cast<size_t>(targetBone)] = sourceBone;
846 ++profile->matchedBoneCount_;
847 }
848
849 int targetRoot = profile->targetRoot_.empty() ? -1 : targetSkeleton->findBone(profile->targetRoot_);
850 int sourceRoot = profile->sourceRoot_.empty() ? -1 : sourceSkeleton->findBone(profile->sourceRoot_);
851 if ((!profile->targetRoot_.empty() && targetRoot < 0) || (!profile->sourceRoot_.empty() && sourceRoot < 0))
852 throw Exception("AnimClip.retargetWithProfile: configured root bone was not found");
853 if (targetRoot < 0 || sourceRoot < 0) {
854 for (int targetBone = 0; targetBone < targetSkeleton->getBoneCount(); ++targetBone) {
855 const int candidate = sourceForTarget[static_cast<size_t>(targetBone)];
856 if (candidate < 0 || candidate >= static_cast<int>(tracks_.size()) ||
857 tracks_[static_cast<size_t>(candidate)].positions.empty())
858 continue;
859 const std::string name = normalizedBoneName(targetSkeleton->getBoneName(targetBone));
860 if (name.find("hips") != std::string::npos || name.find("pelvis") != std::string::npos) {
861 targetRoot = targetBone;
862 sourceRoot = candidate;
863 break;
864 }
865 }
866 }
867 if (targetRoot < 0 || sourceRoot < 0) {
868 for (int targetBone = 0; targetBone < targetSkeleton->getBoneCount(); ++targetBone) {
869 if (targetSkeleton->getParent(targetBone) == -1 && sourceForTarget[static_cast<size_t>(targetBone)] >= 0) {
870 targetRoot = targetBone;
871 sourceRoot = sourceForTarget[static_cast<size_t>(targetBone)];
872 break;
873 }
874 }
875 }
876
877 AnimPose sourceBindPose, targetBindPose, sourcePose;
878 sourceSkeleton->applyBindPose(&sourceBindPose);
879 targetSkeleton->applyBindPose(&targetBindPose);
880 sourceBindPose.computeWorld(sourceSkeleton);
881 targetBindPose.computeWorld(targetSkeleton);
882 const float automaticScale = profile->autoRootScale_
883 ? retargetRootMeasure(targetSkeleton, targetBindPose, targetRoot) /
884 retargetRootMeasure(sourceSkeleton, sourceBindPose, sourceRoot)
885 : 1.f;
886
887 const int frameCount = duration_ <= 0.f ? 1 : std::max(1, static_cast<int>(std::ceil(duration_ * sampleRate_)));
888 std::vector<TransformTRS> targetWorld(static_cast<size_t>(targetSkeleton->getBoneCount()));
889 for (int frame = 0; frame <= frameCount; ++frame) {
890 if (duration_ <= 0.f && frame > 0) break;
891 const float time = duration_ <= 0.f ? 0.f : duration_ * static_cast<float>(frame) / frameCount;
892 sample(time, &sourcePose, sourceSkeleton);
893 sourcePose.computeWorld(sourceSkeleton);
894
895 for (int targetBone = 0; targetBone < targetSkeleton->getBoneCount(); ++targetBone) {
896 const int sourceBone = sourceForTarget[static_cast<size_t>(targetBone)];
897 TransformTRS local = targetSkeleton->bindLocal(targetBone);
898 if (sourceBone >= 0 && sourceBone < static_cast<int>(tracks_.size())) {
899 const BoneTrack& sourceTrack = tracks_[static_cast<size_t>(sourceBone)];
900 const TransformTRS& sourceLocal = sourcePose.local(sourceBone);
901 const TransformTRS& sourceBind = sourceSkeleton->bindLocal(sourceBone);
902 const TransformTRS& targetBind = targetSkeleton->bindLocal(targetBone);
903 const float sourceLength = std::sqrt(sourceBind.px * sourceBind.px + sourceBind.py * sourceBind.py +
904 sourceBind.pz * sourceBind.pz);
905 const float targetLength = std::sqrt(targetBind.px * targetBind.px + targetBind.py * targetBind.py +
906 targetBind.pz * targetBind.pz);
907 const float boneScale =
908 sourceLength > 1e-6f && targetLength > 1e-6f ? targetLength / sourceLength : 1.f;
909 if (!sourceTrack.positions.empty()) {
910 if (targetBone == targetRoot && sourceBone == sourceRoot) {
911 const float horizontal = automaticScale * profile->rootHorizontalScale_;
912 const float vertical = automaticScale * profile->rootVerticalScale_;
913 local.px = targetBind.px + (sourceLocal.px - sourceBind.px) * horizontal;
914 local.py = targetBind.py + (sourceLocal.py - sourceBind.py) * vertical;
915 local.pz = targetBind.pz + (sourceLocal.pz - sourceBind.pz) * horizontal;
916 } else {
917 local.px = targetBind.px + (sourceLocal.px - sourceBind.px) * boneScale;
918 local.py = targetBind.py + (sourceLocal.py - sourceBind.py) * boneScale;
919 local.pz = targetBind.pz + (sourceLocal.pz - sourceBind.pz) * boneScale;
920 }
921 out->tracks_[static_cast<size_t>(targetBone)].positions.push_back(
922 {time, local.px, local.py, local.pz});
923 }
924 if (!sourceTrack.rotations.empty()) {
925 RetargetQuat desired;
926 if (profile->skeletonSpaceRotation_) {
927 desired = retargetMul(rotationOf(targetBindPose.world(targetBone)),
928 retargetMul(retargetInverse(rotationOf(sourceBindPose.world(sourceBone))),
929 rotationOf(sourcePose.world(sourceBone))));
930 const int parent = targetSkeleton->getParent(targetBone);
931 if (parent >= 0)
932 desired = retargetMul(retargetInverse(rotationOf(targetWorld[static_cast<size_t>(parent)])),
933 desired);
934 } else {
935 desired =
936 retargetMul(rotationOf(targetBind),
937 retargetMul(retargetInverse(rotationOf(sourceBind)), rotationOf(sourceLocal)));
938 }
939 local.qx = desired.x;
940 local.qy = desired.y;
941 local.qz = desired.z;
942 local.qw = desired.w;
943 local.normalizeRotation();
944 out->tracks_[static_cast<size_t>(targetBone)].rotations.push_back(
945 {time, local.qx, local.qy, local.qz, local.qw});
946 }
947 if (!sourceTrack.scales.empty()) {
948 local.sx = targetBind.sx *
949 (std::abs(sourceBind.sx) > 1e-6f ? sourceLocal.sx / sourceBind.sx : sourceLocal.sx);
950 local.sy = targetBind.sy *
951 (std::abs(sourceBind.sy) > 1e-6f ? sourceLocal.sy / sourceBind.sy : sourceLocal.sy);
952 local.sz = targetBind.sz *
953 (std::abs(sourceBind.sz) > 1e-6f ? sourceLocal.sz / sourceBind.sz : sourceLocal.sz);
954 out->tracks_[static_cast<size_t>(targetBone)].scales.push_back(
955 {time, local.sx, local.sy, local.sz});
956 }
957 }
958 const int parent = targetSkeleton->getParent(targetBone);
959 if (parent < 0) {
960 targetWorld[static_cast<size_t>(targetBone)] = local;
961 } else {
962 const TransformTRS& parentWorld = targetWorld[static_cast<size_t>(parent)];
963 TransformTRS& world = targetWorld[static_cast<size_t>(targetBone)];
964 const RetargetQuat worldRotation = retargetMul(rotationOf(parentWorld), rotationOf(local));
965 world = local;
966 world.qx = worldRotation.x;
967 world.qy = worldRotation.y;
968 world.qz = worldRotation.z;
969 world.qw = worldRotation.w;
970 }
971 }
972 }
973 if (profile->skinnedInteractionPreserve_) {
974 smrRefineRetargetedClip(*this, *out, sourceSkeleton, targetSkeleton, *profile);
975 } else {
976 profile->interactionCorrectionCount_ = 0;
977 }
978 return out.release();
979}
980
981void AnimClip::adopt(AnimClip& other) {
982 std::swap(name_, other.name_);
983 std::swap(duration_, other.duration_);
984 std::swap(loop_, other.loop_);
985 std::swap(sampleRate_, other.sampleRate_);
986 std::swap(tracks_, other.tracks_);
987 std::swap(events_, other.events_);
988 std::swap(syncMarkers_, other.syncMarkers_);
989}
990
991} // namespace eve::animation
LogicalId target
double value
Value::Object payload
Duration start
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
std::string from
bool split
Definition CaveMesh.cpp:123
std::uint32_t key
std::array< double, 10 > q
std::vector< float > positions
std::int32_t c
std::int32_t first
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string local
std::uint32_t bone
std::int32_t parent
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
float tr
graphics::Canvas * previous
std::vector< float > scales
Definition OnnxLstm.cpp:27
std::string error
Definition Package.cpp:60
World3D * world
float begin
float t
int removed
float dz
float dy
float dx
std::uint32_t count
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
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
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
static void unregister(AnimClip *clip)
Forget a clip everywhere (called from ~AnimClip).
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:145
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
AnimClip * retargetWithProfile(const AnimSkeleton *sourceSkeleton, const AnimSkeleton *targetSkeleton, AnimRetargetProfile *profile) const
Retarget using an Avatar-like mapping/profile and update its diagnostics. Skeleton-space rotation pre...
Definition AnimClip.cpp:811
void addSyncMarker(float time, const std::string &name)
Add a named locomotion sync marker at clip-local time.
Definition AnimClip.cpp:216
float getPositionKeyZ(int boneIndex, int keyIndex) const
Returns the position key z.
Definition AnimClip.cpp:400
bool hasCompatibleSyncMarkers(const AnimClip *target) const
Whether this clip and target share a usable ordered marker interval.
Definition AnimClip.cpp:252
bool setScaleKey(int boneIndex, int keyIndex, float time, float x, float y, float z)
Replace and re-sort one scale key.
Definition AnimClip.cpp:114
float getRotationKeyW(int boneIndex, int keyIndex) const
Returns the rotation key w.
Definition AnimClip.cpp:421
bool setPositionKey(int boneIndex, int keyIndex, float time, float x, float y, float z)
Replace and re-sort one position key.
Definition AnimClip.cpp:86
float getSyncMarkerTime(int index) const
Return a sync marker's time, or 0 for an invalid index.
Definition AnimClip.cpp:242
void addScaleKey(int boneIndex, float time, float x, float y, float z)
Adds scale key.
Definition AnimClip.cpp:76
float getEventTime(int index) const
Return an event marker's time, or 0 for an invalid index.
Definition AnimClip.cpp:181
void sampleLod(float time, AnimPose *out, const AnimSkeleton *skeleton, int lodLevel) const
Sample only bones enabled for lodLevel; culled tracks use target bind pose.
Definition AnimClip.cpp:579
int getEventCount() const
Return the number of event markers.
Definition AnimClip.h:204
float getScaleKeyY(int boneIndex, int keyIndex) const
Returns the scale key y.
Definition AnimClip.cpp:434
bool hasEvent(std::string_view name) const noexcept
Whether this clip contains at least one event with the stable semantic name.
Definition AnimClip.cpp:196
void setDuration(float seconds)
Sets the duration.
Definition AnimClip.cpp:22
float getPositionKeyTime(int boneIndex, int keyIndex) const
Returns the position key time.
Definition AnimClip.cpp:388
bool setRotationKey(int boneIndex, int keyIndex, float time, float x, float y, float z, float w)
Replace and re-sort one rotation key.
Definition AnimClip.cpp:97
void addPositionKey(int boneIndex, float time, float x, float y, float z)
Adds position key.
Definition AnimClip.cpp:45
bool setEvent(int index, float time, const std::string &name, const std::string &payload="")
Replace and re-sort one event marker.
Definition AnimClip.cpp:164
bool removePositionKey(int boneIndex, int keyIndex)
Delete one position key.
Definition AnimClip.cpp:125
float getRotationKeyTime(int boneIndex, int keyIndex) const
Returns the rotation key time.
Definition AnimClip.cpp:405
std::string getEventName(int index) const
Return an event marker's name, or empty for an invalid index.
Definition AnimClip.cpp:186
int getRotationKeyCount(int boneIndex) const
Returns the rotation key count.
Definition AnimClip.cpp:370
float getScaleKeyTime(int boneIndex, int keyIndex) const
Returns the scale key time.
Definition AnimClip.cpp:426
void addEvent(float time, const std::string &name, const std::string &payload="")
Add a named event marker at clip-local time. Events are kept time-sorted.
Definition AnimClip.cpp:155
void applyPlanarRootMotion(int boneIndex, float speedX, float speedZ)
Bake planar root motion onto an existing position track (or create one). For each position key at tim...
Definition AnimClip.cpp:443
float mapSyncTimeTo(float time, const AnimClip *target) const
Map local time into target marker space, falling back to normalized duration.
Definition AnimClip.cpp:280
bool removeSyncMarker(int index)
Delete one sync marker.
Definition AnimClip.cpp:236
std::unique_ptr< AnimClip > clone() const
Return a fully owning deep copy suitable for runtime hot replacement.
Definition AnimClip.cpp:481
bool clearTrack(int boneIndex)
Clear every transform channel for one bone.
Definition AnimClip.cpp:149
float getRotationKeyZ(int boneIndex, int keyIndex) const
Returns the rotation key z.
Definition AnimClip.cpp:417
bool setSyncMarker(int index, float time, const std::string &name)
Replace and re-sort one sync marker.
Definition AnimClip.cpp:225
int getScaleKeyCount(int boneIndex) const
Returns the scale key count.
Definition AnimClip.cpp:375
void setSampleRate(float hz)
Sample rate hint used by MotionDatabase baking (Hz).
Definition AnimClip.cpp:33
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
float getPositionKeyX(int boneIndex, int keyIndex) const
Returns the position key x.
Definition AnimClip.cpp:392
int getPositionKeyCount(int boneIndex) const
Returns the position key count.
Definition AnimClip.cpp:365
void addRotationKey(int boneIndex, float time, float x, float y, float z, float w)
Adds rotation key.
Definition AnimClip.cpp:55
float getRotationKeyX(int boneIndex, int keyIndex) const
Returns the rotation key x.
Definition AnimClip.cpp:409
AnimClip * retarget(const AnimSkeleton *sourceSkeleton, const AnimSkeleton *targetSkeleton) const
Bake this clip onto a target skeleton by matching bone names and preserving bind-pose deltas....
Definition AnimClip.cpp:806
int getSyncMarkerCount() const
Return the number of locomotion sync markers.
Definition AnimClip.h:232
float getRotationKeyY(int boneIndex, int keyIndex) const
Returns the rotation key y.
Definition AnimClip.cpp:413
bool removeRotationKey(int boneIndex, int keyIndex)
Delete one rotation key.
Definition AnimClip.cpp:133
bool removeScaleKey(int boneIndex, int keyIndex)
Delete one scale key.
Definition AnimClip.cpp:141
float getPositionKeyY(int boneIndex, int keyIndex) const
Returns the position key y.
Definition AnimClip.cpp:396
int compress(float positionError=0.001f, float rotationErrorDegrees=0.1f, float scaleError=0.001f)
Remove interpolation-redundant keys while keeping sampled error below the supplied tolerances.
Definition AnimClip.cpp:602
AnimClip(std::string name="")
Anim clip.
Definition AnimClip.cpp:18
int getTrackCount() const
Return the number of allocated bone tracks, including empty tracks.
Definition AnimClip.h:196
std::string getSyncMarkerName(int index) const
Return a sync marker's name, or empty for an invalid index.
Definition AnimClip.cpp:247
eve::Result< void > validateNotifyContract(const std::vector< std::string > &requiredNames) const
Validates that every required semantic notify exists before gameplay registration.
Definition AnimClip.cpp:201
float wrapTime(float time) const
Wrap or clamp time according to loop flag.
Definition AnimClip.cpp:471
float getScaleKeyX(int boneIndex, int keyIndex) const
Returns the scale key x.
Definition AnimClip.cpp:430
float getScaleKeyZ(int boneIndex, int keyIndex) const
Returns the scale key z.
Definition AnimClip.cpp:438
std::string getEventPayload(int index) const
Return an event marker's optional payload.
Definition AnimClip.cpp:191
bool removeEvent(int index)
Delete one event marker.
Definition AnimClip.cpp:175
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
const TransformTRS & world(int boneIndex) const
World.
Definition AnimPose.cpp:367
TransformTRS & local(int boneIndex)
Local.
Definition AnimPose.cpp:126
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
Settings and diagnostics for offline skeletal animation retargeting.
Definition AnimClip.h:27
void setRootTranslationScale(float horizontal, float vertical)
Set additional horizontal and vertical multipliers for root translation.
Definition AnimClip.cpp:703
void setInteractionCorrectionWeight(float weight)
Blend weight [0,1] applied to interaction IK corrections.
Definition AnimClip.cpp:720
int getUnmatchedBoneCount() const
Number of target bones left at bind pose by the most recent operation.
Definition AnimClip.h:109
std::string getUnmatchedTargetBone(int index) const
Name of an unmatched target bone, or empty for an invalid index.
Definition AnimClip.cpp:710
void addInteractionIkChain(const std::string &rootBone, const std::string &midBone, const std::string &tipBone)
Register a two-bone IK chain used to realize tip-sensor corrections.
Definition AnimClip.cpp:726
void clearInteractionIkChains()
Remove all explicitly registered interaction IK chains (auto-detect remains).
Definition AnimClip.cpp:733
void addBoneMapping(const std::string &sourceBone, const std::string &targetBone)
Map one source bone to one target bone; replaces an existing mapping for that target.
Definition AnimClip.cpp:684
void clearBoneMappings()
Remove all explicit bone mappings.
Definition AnimClip.cpp:696
void setInteractionContactThreshold(float distance)
Absolute source-space distance below which a sensor pair counts as interacting. Values <= 0 select 8%...
Definition AnimClip.cpp:715
void setRootBones(const std::string &sourceBone, const std::string &targetBone)
Select the source and target pelvis/root used for proportional translation.
Definition AnimClip.cpp:698
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int getBoneCount() const
Returns the bone count.
int getParent(int boneIndex) const
Returns the parent.
const TransformTRS & bindLocal(int boneIndex) const
Binds local.
int getBoneLodLimit(int boneIndex) const
Highest LOD at which the bone is sampled; defaults to all LODs.
std::string getBoneName(int boneIndex) const
Returns the bone name.
int findBone(const std::string &name) const
Finds bone.
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
void slerpQuat(float ax, float ay, float az, float aw, float bx, float by, float bz, float bw, float t, float &ox, float &oy, float &oz, float &ow)
Slerp quat.
Definition AnimMath.h:43
float lerpf(float a, float b, float t)
Lerpf.
Definition AnimMath.h:40
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
int smrRefineRetargetedClip(const AnimClip &sourceClip, AnimClip &targetClip, const AnimSkeleton *sourceSkeleton, const AnimSkeleton *targetSkeleton, AnimRetargetProfile &profile)
Refine an FK-retargeted clip so source body-part proximity/contact is preserved on the target.
Definition AnimSmr.cpp:344
WidgetDesc separator(std::string id)
Horizontal separator line.
Definition Widget.cpp:345
Two-bone IK chain used to realize an interaction correction.
Definition AnimSmr.h:43
Local TRS used by skeletal animation (quaternion xyzw).
Definition AnimMath.h:9
static TransformTRS identity()
Identity.
Definition AnimMath.h:15
void normalizeRotation()
Normalize rotation.
Definition AnimMath.h:18