载入中...
搜索中...
未找到
AnimationClip.cpp
浏览该文件的文档.
76 return EditorValue::Object{{"id", track.id.value()}, {"bone", track.bone}, {"keys", std::move(keys)}};
114 if (!id || id->empty() || !readNumber(value, "time", time) || !std::isfinite(time) || time < 0.0 ||
130 result.payload = std::move(payload); result.inverse = std::move(inverse); result.hasInverse = true;
144EditorDiagnostic clipDiagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
145 return eve::editing::ruleDiagnostic(eve::DiagnosticCode::PreconditionViolation, RuleId(rule), severity,
173 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.target-mismatch"),
177 const EditorValue* loopEntry = field(op.payload, "loop"); const auto* loop = loopEntry ? loopEntry->getIf<bool>() : nullptr;
178 if (!readNumber(op.payload, "duration", duration) || !readNumber(op.payload, "sampleRate", rate) ||
180 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-settings"),
190 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.key-after-duration"),
200 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.animation.track-not-found"),
208 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.event-after-duration"),
213 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.animation.event-not-found"),
217 if (!bone || bone->empty() || !readNumber(op.payload, "weight", weight) || !std::isfinite(weight) ||
219 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-mask"),
223 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.animation.unsupported-operation"),
235Result<void> AnimationClipDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate) {
238 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-candidate"),
243Result<DomainOperation> AnimationClipDocumentTarget::makeSetSettings(double duration, double sampleRate, bool loop) const {
244 if (!std::isfinite(duration) || !std::isfinite(sampleRate) || duration <= 0.0 || sampleRate <= 0.0)
248 for (const auto& [id, track] : tracks_) { static_cast<void>(id); for (const auto& key : track.keys)
259 return eve::editing::applied<DomainOperation>(operation("animation.clip.settings.v1", "animation.clip.settings.v1", id_,
260 settingsValue(duration, sampleRate, loop), settingsValue(duration_, sampleRate_, loop_), "settings"));
263Result<DomainOperation> AnimationClipDocumentTarget::makeSetTrack(const AnimationBoneTrack& track) const {
266 return eve::editing::failed<DomainOperation>(parsed.code(), RuleId("editor.animation.invalid-track"),
268 const auto old = tracks_.find(track.id); EditorValue inverse = old == tracks_.end() ? trackValue(track) : trackValue(old->second);
276 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.animation.track-not-found"),
281Result<DomainOperation> AnimationClipDocumentTarget::makeSetEvent(const AnimationEventRecord& event) const {
284 return eve::editing::failed<DomainOperation>(parsed.code(), RuleId("editor.animation.invalid-event"),
286 const auto old = events_.find(event.id); EditorValue inverse = old == events_.end() ? eventValue(event) : eventValue(old->second);
294 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.animation.event-not-found"),
299Result<DomainOperation> AnimationClipDocumentTarget::makeSetMask(const AnimationMaskEntry& mask) const {
301 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.animation.invalid-mask"),
303 const auto old = mask_.find(mask.bone); const double oldWeight = old == mask_.end() ? 1.0 : old->second;
304 return eve::editing::applied<DomainOperation>(operation("animation.clip.mask.set.v1", "animation.clip.mask.set.v1", id_,
305 EditorValue::Object{{"bone", mask.bone}, {"weight", mask.weight}}, EditorValue::Object{{"bone", mask.bone}, {"weight", oldWeight}}, mask.bone));
309 std::vector<AnimationBoneTrack> result; for (const auto& [id, track] : tracks_) { static_cast<void>(id); result.push_back(track); } return result;
313 std::vector<AnimationEventRecord> result; for (const auto& [id, event] : events_) { static_cast<void>(id); result.push_back(event); }
317std::vector<EditorDiagnostic> AnimationClipDocumentTarget::validate(const std::vector<std::string>& bones) const {
318 std::vector<EditorDiagnostic> result; const std::set<std::string> known(bones.begin(), bones.end());
320 if (!known.empty() && !known.contains(track.bone)) result.push_back(clipDiagnostic("editor.animation.missing-track-bone", DiagnosticSeverity::Error, "Track references missing bone: " + track.bone));
321 if (track.keys.empty()) result.push_back(clipDiagnostic("editor.animation.empty-track", DiagnosticSeverity::Warning, "Bone track has no keys: " + track.bone));
323 for (const auto& [bone, weight] : mask_) { static_cast<void>(weight); if (!known.empty() && !known.contains(bone))
324 result.push_back(clipDiagnostic("editor.animation.missing-mask-bone", DiagnosticSeverity::Error, "Mask references missing bone: " + bone)); }
328AnimationClipPreview AnimationClipDocumentTarget::preview(double time, const std::vector<std::string>& bones) const {
331 if (!std::isfinite(time)) { result.diagnostics.push_back(clipDiagnostic("editor.animation.invalid-preview-time", DiagnosticSeverity::Error, "Preview time must be finite")); return result; }
332 result.time = loop_ ? std::fmod(std::max(0.0, time), duration_) : std::clamp(time, 0.0, duration_);
334 for (const auto& [id, track] : tracks_) { static_cast<void>(id); if (track.keys.empty()) continue;
335 auto upper = std::upper_bound(track.keys.begin(), track.keys.end(), result.time, [](double t, const auto& key) { return t < key.time; });
340 sample.positionX = mix(a.positionX, b.positionX, amount); sample.positionY = mix(a.positionY, b.positionY, amount); sample.positionZ = mix(a.positionZ, b.positionZ, amount);
341 sample.rotationX = mix(a.rotationX, b.rotationX, amount); sample.rotationY = mix(a.rotationY, b.rotationY, amount); sample.rotationZ = mix(a.rotationZ, b.rotationZ, amount); sample.rotationW = mix(a.rotationW, b.rotationW, amount);
342 const double length = std::sqrt(sample.rotationX * sample.rotationX + sample.rotationY * sample.rotationY + sample.rotationZ * sample.rotationZ + sample.rotationW * sample.rotationW);
343 if (length > 1e-8) { sample.rotationX /= length; sample.rotationY /= length; sample.rotationZ /= length; sample.rotationW /= length; }
344 sample.scaleX = mix(a.scaleX, b.scaleX, amount); sample.scaleY = mix(a.scaleY, b.scaleY, amount); sample.scaleZ = mix(a.scaleZ, b.scaleZ, amount);
345 const auto mask = mask_.find(track.bone); sample.maskWeight = mask == mask_.end() ? 1.0 : mask->second; result.bones.push_back(std::move(sample));
347 result.status = std::any_of(result.diagnostics.begin(), result.diagnostics.end(), [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; }) ? EditorStatus::Failed : EditorStatus::Applied;
351AnimationRetargetPreview AnimationClipDocumentTarget::previewRetarget(const std::vector<std::string>& targetBones) const {
355 for (const auto& [id, track] : tracks_) { static_cast<void>(id); auto exact = std::find(targetBones.begin(), targetBones.end(), track.bone);
356 auto found = exact; if (found == targetBones.end()) found = std::find_if(targetBones.begin(), targetBones.end(), [&](const auto& target) { return normalized(target) == normalized(track.bone); });
357 if (found == targetBones.end() || used.contains(*found)) result.unmatchedSourceBones.push_back(track.bone); else { result.mapping[track.bone] = *found; used.insert(*found); }
359 for (const auto& target : targetBones) if (!used.contains(target)) result.unmatchedTargetBones.push_back(target);
360 if (!result.unmatchedSourceBones.empty()) result.diagnostics.push_back(clipDiagnostic("editor.animation.unmatched-source-bones", DiagnosticSeverity::Warning, "Some source tracks cannot be mapped"));
361 if (!result.unmatchedTargetBones.empty()) result.diagnostics.push_back(clipDiagnostic("editor.animation.unmatched-target-bones", DiagnosticSeverity::Info, "Some target bones remain at bind pose"));
366 EditorValue::Array tracks; for (const auto& [id, track] : tracks_) { static_cast<void>(id); tracks.push_back(trackValue(track)); }
367 EditorValue::Array events; for (const auto& event : this->events()) events.push_back(eventValue(event));
368 EditorValue::Array masks; for (const auto& [bone, weight] : mask_) masks.push_back(EditorValue::Object{{"bone", bone}, {"weight", weight}});
373 const EditorValue* versionEntry = field(snapshot, "schemaVersion"); const auto* version = versionEntry ? versionEntry->getIf<int64_t>() : nullptr;
374 const EditorValue* settings = field(snapshot, "settings"); const EditorValue* tracksEntry = field(snapshot, "tracks"); const EditorValue* eventsEntry = field(snapshot, "events"); const EditorValue* masksEntry = field(snapshot, "masks");
375 const auto* tracks = tracksEntry ? tracksEntry->getIf<EditorValue::Array>() : nullptr; const auto* events = eventsEntry ? eventsEntry->getIf<EditorValue::Array>() : nullptr; const auto* masks = masksEntry ? masksEntry->getIf<EditorValue::Array>() : nullptr;
377 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.animation.invalid-snapshot"),
380 const EditorValue* loopEntry = field(*settings, "loop"); const auto* loop = loopEntry ? loopEntry->getIf<bool>() : nullptr;
381 if (!readNumber(*settings, "duration", duration) || !readNumber(*settings, "sampleRate", rate) || !loop)
382 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-settings"),
384 AnimationClipDocumentTarget candidate(id_); auto settingsOp = candidate.makeSetSettings(duration, rate, *loop);
386 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-settings"),
391 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-track"),
395 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-track"),
401 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-event"),
405 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-event"),
412 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-mask"),
416 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.animation.invalid-mask"),
425Result<AnimationBoneTrack> parseAnimationBoneTrack(const EditorValue& value) { return parseTrack(value); }
426Result<AnimationEventRecord> parseAnimationEventRecord(const EditorValue& value) { return parseEvent(value); }
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:480
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
UI-neutral animation clip document with reversible stable-id timeline edits.
Definition AnimationClip.h:106
std::vector< EditorDiagnostic > validate(const std::vector< std::string > &skeletonBones={}) const
Validate timeline, references and mask against optional target bone names.
Definition AnimationClip.cpp:317
Result< DomainOperation > makeSetSettings(double duration, double sampleRate, bool loop) const override
Make set settings.
Definition AnimationClip.cpp:243
Result< DomainOperation > makeDeleteEvent(const StableId &event) const override
Make delete event.
Definition AnimationClip.cpp:291
Result< DomainOperation > makeSetMask(const AnimationMaskEntry &mask) const override
Make set mask.
Definition AnimationClip.cpp:299
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
Definition AnimationClip.cpp:231
AnimationClipPreview preview(double time, const std::vector< std::string > &skeletonBones={}) const
Sample the authored clip without mutating a runtime asset.
Definition AnimationClip.cpp:328
Result< DomainOperation > makeDeleteTrack(const StableId &track) const override
Make delete track.
Definition AnimationClip.cpp:273
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
Definition AnimationClip.cpp:235
Result< DomainOperation > makeSetEvent(const AnimationEventRecord &event) const override
Make set event.
Definition AnimationClip.cpp:281
EditorValue snapshotValue() const override
Capture deterministic schema-version-one clip data.
Definition AnimationClip.cpp:365
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Definition AnimationClip.cpp:171
AnimationClipDocumentTarget(std::string id)
Animation clip document target.
Definition AnimationClip.cpp:151
std::vector< AnimationEventRecord > events() const
Return event markers ordered by time then stable id.
Definition AnimationClip.cpp:312
std::vector< AnimationBoneTrack > tracks() const
Return tracks ordered by stable id.
Definition AnimationClip.cpp:308
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
Definition AnimationClip.cpp:163
Result< DomainOperation > makeSetTrack(const AnimationBoneTrack &track) const override
Make set track.
Definition AnimationClip.cpp:263
AnimationRetargetPreview previewRetarget(const std::vector< std::string > &targetBones) const
Preview exact then normalized-name retarget mapping without baking a clip.
Definition AnimationClip.cpp:351
Timeline editing capability for skeletal clips.
Definition AnimationClip.h:78
static CapabilityId editingCapabilityId()
Stable capability id for clip timeline operations.
Definition AnimationClip.h:83
void setRevision(Revision revision)
Adopt a revision observed elsewhere, without advancing it.
Definition EditableTarget.h:181
void bumpRevision()
Advance the revision without touching the dirty region.
Definition EditableTarget.h:179
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
Definition EditableTarget.h:169
void widenDirty(int x, int y)
Widen the dirty region without advancing the revision.
Definition EditableTarget.h:173
void widenDirty()
Mark an unknown area dirty without advancing the revision.
Definition EditableTarget.h:177
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
Definition EditableTarget.h:82
Optional capability exposing a deterministic, persistence-safe editing snapshot.
Definition EditableTarget.h:247
static CapabilityId editingCapabilityId()
Stable capability identity shared by every editable domain target.
Definition EditableTarget.h:252
Deterministic owning value tree shared by authoring hosts.
Definition EditingValue.h:19
Definition AnimationClip.cpp:10
Result< AnimationEventRecord > parseAnimationEventRecord(const EditorValue &value)
Parse one authored event marker from an owning Value payload.
Definition AnimationClip.cpp:426
Result< AnimationBoneTrack > parseAnimationBoneTrack(const EditorValue &value)
Parse one authored bone track from an owning Value payload.
Definition AnimationClip.cpp:425
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
Definition EditingResult.h:25
@ PreconditionViolation
Definition Container.h:602
Stable bone track addressed by skeleton bone name.
Definition AnimationClip.h:29
std::vector< AnimationTransformKey > keys
Definition AnimationClip.h:32
Immutable timeline scrub result tied to one document revision.
Definition AnimationClip.h:59
std::vector< AnimationSampledBone > bones
Definition AnimationClip.h:63
EditorStatus status
Definition AnimationClip.h:60
std::vector< EditorDiagnostic > diagnostics
Definition AnimationClip.h:64
Revision documentRevision
Definition AnimationClip.h:61
Stable timeline event marker with optional payload.
Definition AnimationClip.h:36
Per-bone blend mask entry in normalized [0, 1] weight space.
Definition AnimationClip.h:44
Non-destructive source-to-target skeleton mapping preview.
Definition AnimationClip.h:68
std::map< std::string, std::string > mapping
Definition AnimationClip.h:71
std::vector< std::string > unmatchedSourceBones
Definition AnimationClip.h:72
std::vector< std::string > unmatchedTargetBones
Definition AnimationClip.h:73
EditorStatus status
Definition AnimationClip.h:69
std::vector< EditorDiagnostic > diagnostics
Definition AnimationClip.h:74
Revision documentRevision
Definition AnimationClip.h:70
Sampled local transform returned by non-destructive timeline scrubbing.
Definition AnimationClip.h:50
One stable transform key used by the animation timeline.
Definition AnimationClip.h:20
std::vector< ObjectRefValue > affectedObjects
Definition EditingProtocol.h:51
Stable discovery metadata exposed by every editable target.
Definition EditableTarget.h:39
std::vector< CapabilityId > capabilities
Definition EditableTarget.h:44