#include "animation/AnimationEvaluator.h" #include namespace core { namespace { QVector sortedKeys(const QVector& keys) { QVector out = keys; std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); return out; } bool sampleBool(const QVector& keys, int frame, bool fallback) { auto sorted = sortedKeys(keys); bool out = fallback; for (const auto& k : sorted) { if (k.frame <= frame) out = k.boolValue; else break; } return out; } QString sampleString(const QVector& keys, int frame, const QString& fallback = {}) { auto sorted = sortedKeys(keys); QString out = fallback; for (const auto& k : sorted) { if (k.frame <= frame && !k.stringValue.isEmpty()) out = k.stringValue; else if (k.frame > frame) break; } return out; } QByteArray sampleBytes(const QVector& keys, int frame, const QByteArray& fallback = {}) { auto sorted = sortedKeys(keys); QByteArray out = fallback; for (const auto& k : sorted) { if (k.frame <= frame && !k.bytesValue.isEmpty()) out = k.bytesValue; else if (k.frame > frame) break; } return out; } double sampleDouble(const QVector& keys, int frame, double fallback, Project::AnimationInterpolation interpolation) { auto sorted = sortedKeys(keys); if (sorted.isEmpty()) return fallback; if (interpolation == Project::AnimationInterpolation::Hold) { double out = fallback; for (const auto& k : sorted) { if (k.frame <= frame) out = k.numberValue; else break; } return out; } if (frame <= sorted.front().frame) return sorted.front().numberValue; if (frame >= sorted.back().frame) return sorted.back().numberValue; for (int i = 0; i + 1 < sorted.size(); ++i) { const auto& a = sorted[i]; const auto& b = sorted[i + 1]; if (frame >= a.frame && frame <= b.frame) { if (a.frame == b.frame) return a.numberValue; const double t = double(frame - a.frame) / double(b.frame - a.frame); return a.numberValue + (b.numberValue - a.numberValue) * t; } } return sorted.back().numberValue; } QPointF sampleVec2(const QVector& keys, int frame, const QPointF& fallback, Project::AnimationInterpolation interpolation) { auto sorted = sortedKeys(keys); if (sorted.isEmpty()) return fallback; if (interpolation == Project::AnimationInterpolation::Hold) { QPointF out = fallback; for (const auto& k : sorted) { if (k.frame <= frame) out = k.vec2Value; else break; } return out; } if (frame <= sorted.front().frame) return sorted.front().vec2Value; if (frame >= sorted.back().frame) return sorted.back().vec2Value; for (int i = 0; i + 1 < sorted.size(); ++i) { const auto& a = sorted[i]; const auto& b = sorted[i + 1]; if (frame >= a.frame && frame <= b.frame) { if (a.frame == b.frame) return a.vec2Value; const double t = double(frame - a.frame) / double(b.frame - a.frame); return QPointF(a.vec2Value.x() + (b.vec2Value.x() - a.vec2Value.x()) * t, a.vec2Value.y() + (b.vec2Value.y() - a.vec2Value.y()) * t); } } return sorted.back().vec2Value; } } // namespace AnimationSample evaluateAnimation(const Project::Animation& animation, int frame) { AnimationSample out; const int f = animation.loop && animation.lengthFrames > 0 ? (std::max(0, frame) % animation.lengthFrames) : std::clamp(frame, 0, std::max(0, animation.lengthFrames - 1)); for (const auto& t : animation.tracks) { if (t.targetId.isEmpty() || t.keys.isEmpty()) continue; if (t.targetKind == Project::AnimationTargetKind::Entity) { switch (t.property) { case Project::AnimationProperty::Position: out.entityPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation)); break; case Project::AnimationProperty::UserScale: out.entityUserScale.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation)); break; case Project::AnimationProperty::DepthScale: out.entityDepthScale01.insert(t.targetId, sampleDouble(t.keys, f, 0.5, t.interpolation)); break; case Project::AnimationProperty::Sprite: // 贴图仅支持二进制 PNG bytes(不兼容路径关键帧)。 if (t.valueType == Project::AnimationValueType::PngBytes) { out.entitySpritePng.insert(t.targetId, sampleBytes(t.keys, f)); } break; case Project::AnimationProperty::Visibility: out.entityVisibility.insert(t.targetId, sampleBool(t.keys, f, true)); break; case Project::AnimationProperty::Priority: out.entityPriority.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation)); break; default: break; } } else if (t.targetKind == Project::AnimationTargetKind::Tool) { if (t.property == Project::AnimationProperty::Position) { out.toolPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation)); } else if (t.property == Project::AnimationProperty::Visibility) { out.toolVisibility.insert(t.targetId, sampleBool(t.keys, f, true)); } else if (t.property == Project::AnimationProperty::Priority) { out.toolPriority.insert(t.targetId, sampleDouble(t.keys, f, 10.0, t.interpolation)); } } else if (t.targetKind == Project::AnimationTargetKind::Camera) { if (t.property == Project::AnimationProperty::Position) { out.cameraPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation)); } else if (t.property == Project::AnimationProperty::CameraScale) { out.cameraScale.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation)); } } } return out; } QVector spriteFramesForEntity(const Project::Animation& animation, const QString& entityId) { QVector out; for (const auto& t : animation.tracks) { if (t.targetKind != Project::AnimationTargetKind::Entity || t.targetId != entityId || t.property != Project::AnimationProperty::Sprite) { continue; } out.reserve(out.size() + t.keys.size()); for (const auto& k : t.keys) { if (t.valueType == Project::AnimationValueType::PngBytes) { if (!k.bytesValue.isEmpty()) { out.push_back(Project::Entity::ImageFrame{k.frame, QString()}); } } else if (t.valueType == Project::AnimationValueType::ImagePath) { // 旧格式:不再支持路径型 sprite 关键帧(按“无/回退默认贴图”处理) } } } std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); return out; } } // namespace core