update
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
#include "eval/ProjectEvaluator.h"
|
||||
|
||||
#include "animation/AnimationEvaluator.h"
|
||||
#include "animation/AnimationSampling.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace core::eval {
|
||||
@@ -15,68 +17,6 @@ struct NodeRef {
|
||||
int index = -1;
|
||||
};
|
||||
|
||||
QPointF sampledOriginForEntity(const core::Project::Entity& e,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->entityLocationKeys.contains(e.id)) {
|
||||
const auto& keys = clipOrNull->entityLocationKeys.value(e.id);
|
||||
return core::sampleLocation(keys, localFrame, e.originWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
return core::sampleLocation(e.locationKeys, localFrame, e.originWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
QPointF sampledRelativeForEntity(const core::Project::Entity& e,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->entityLocationKeys.contains(e.id)) {
|
||||
const auto& keys = clipOrNull->entityLocationKeys.value(e.id);
|
||||
return core::sampleLocation(keys, localFrame, e.parentOffsetWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
return core::sampleLocation(e.locationKeys, localFrame, e.parentOffsetWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
QPointF sampledOriginForTool(const core::Project::Tool& t,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->toolLocationKeys.contains(t.id)) {
|
||||
const auto& keys = clipOrNull->toolLocationKeys.value(t.id);
|
||||
return core::sampleLocation(keys, localFrame, t.originWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
return core::sampleLocation(t.locationKeys, localFrame, t.originWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
QPointF sampledCenterForCamera(const core::Project::Camera& c,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->cameraLocationKeys.contains(c.id)) {
|
||||
const auto& keys = clipOrNull->cameraLocationKeys.value(c.id);
|
||||
return core::sampleLocation(keys, localFrame, c.centerWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
return core::sampleLocation(c.locationKeys, localFrame, c.centerWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
double sampledViewScaleForCamera(const core::Project::Camera& c,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->cameraScaleKeys.contains(c.id)) {
|
||||
const auto& keys = clipOrNull->cameraScaleKeys.value(c.id);
|
||||
if (!keys.isEmpty()) {
|
||||
return core::sampleUserScale(keys, localFrame, c.viewScale, core::KeyInterpolation::Linear);
|
||||
}
|
||||
}
|
||||
return core::sampleUserScale(c.scaleKeys, localFrame, c.viewScale, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
QPointF sampledRelativeForTool(const core::Project::Tool& t,
|
||||
const core::Project::AnimationClip* clipOrNull,
|
||||
int localFrame) {
|
||||
if (clipOrNull && clipOrNull->toolLocationKeys.contains(t.id)) {
|
||||
const auto& keys = clipOrNull->toolLocationKeys.value(t.id);
|
||||
return core::sampleLocation(keys, localFrame, t.parentOffsetWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
return core::sampleLocation(t.locationKeys, localFrame, t.parentOffsetWorld, core::KeyInterpolation::Linear);
|
||||
}
|
||||
|
||||
struct VisKey {
|
||||
int frame = 0;
|
||||
bool value = true;
|
||||
@@ -144,82 +84,10 @@ double opacityFromBoolKeys(const QVector<core::Project::ToolKeyframeBool>& keysR
|
||||
return state ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
struct StripEvalCtx {
|
||||
const core::Project::AnimationScheme* scheme = nullptr;
|
||||
const core::Project::NlaStrip* strip = nullptr;
|
||||
const core::Project::AnimationClip* clip = nullptr;
|
||||
int slot = 0;
|
||||
int localFrame = 0; // 0..kClipFixedFrames-1
|
||||
};
|
||||
|
||||
static const core::Project::NlaStrip* findStripById(const core::Project::AnimationScheme& scheme, const QString& id) {
|
||||
if (id.isEmpty()) return nullptr;
|
||||
for (const auto& tr : scheme.tracks) {
|
||||
for (const auto& st : tr.strips) {
|
||||
if (st.id == id) return &st;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool trackIsEffectivelyMuted(const core::Project::AnimationScheme& scheme, const core::Project::NlaTrack& t) {
|
||||
// 若有任意 solo=true,则只有 solo 的 track 生效(且仍受自身 muted 控制)
|
||||
bool anySolo = false;
|
||||
for (const auto& tr : scheme.tracks) {
|
||||
if (tr.solo) {
|
||||
anySolo = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (anySolo && !t.solo) {
|
||||
return true;
|
||||
}
|
||||
return t.muted;
|
||||
}
|
||||
|
||||
static const core::Project::NlaStrip* pickStripAtSlot(const core::Project::AnimationScheme& scheme, int slot) {
|
||||
const core::Project::NlaStrip* chosen = nullptr;
|
||||
for (const auto& tr : scheme.tracks) {
|
||||
if (trackIsEffectivelyMuted(scheme, tr)) continue;
|
||||
for (const auto& st : tr.strips) {
|
||||
if (!st.enabled || st.muted) continue;
|
||||
const int a = st.startSlot;
|
||||
const int b = st.startSlot + std::max(1, st.slotLen);
|
||||
if (slot >= a && slot < b) {
|
||||
chosen = &st; // 轨道顺序靠后的覆盖靠前的(更接近“上层”)
|
||||
}
|
||||
}
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
static StripEvalCtx resolveStripCtx(const core::Project& project, int globalFrame) {
|
||||
StripEvalCtx ctx;
|
||||
const auto* scheme = project.activeSchemeOrNull();
|
||||
if (!scheme) {
|
||||
ctx.localFrame = std::max(0, globalFrame);
|
||||
return ctx;
|
||||
}
|
||||
ctx.scheme = scheme;
|
||||
const int g = std::max(0, globalFrame);
|
||||
ctx.slot = g / core::Project::kClipFixedFrames;
|
||||
ctx.localFrame = g % core::Project::kClipFixedFrames;
|
||||
|
||||
const core::Project::NlaStrip* st = findStripById(*scheme, project.selectedStripId());
|
||||
// 若选中条带不覆盖当前 slot,则退回自动挑选
|
||||
if (!st || ctx.slot < st->startSlot || ctx.slot >= (st->startSlot + std::max(1, st->slotLen)) || !st->enabled || st->muted) {
|
||||
st = pickStripAtSlot(*scheme, ctx.slot);
|
||||
}
|
||||
ctx.strip = st;
|
||||
if (st) {
|
||||
ctx.clip = project.findClipById(st->clipId);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, int fadeFrames) {
|
||||
ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, int fadeFrames,
|
||||
const QString& animationId) {
|
||||
ResolvedProjectFrame out;
|
||||
const auto& ents = project.entities();
|
||||
const auto& tools = project.tools();
|
||||
@@ -228,9 +96,15 @@ ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, in
|
||||
out.tools.reserve(tools.size());
|
||||
out.cameras.reserve(cams.size());
|
||||
|
||||
const StripEvalCtx ctx = resolveStripCtx(project, frame);
|
||||
const int localFrame = ctx.localFrame;
|
||||
const core::Project::AnimationClip* clip = ctx.clip;
|
||||
const int localFrame = std::max(0, frame);
|
||||
const core::Project::Animation* activeAnimation = nullptr;
|
||||
if (animationId.isEmpty()) {
|
||||
activeAnimation = project.activeAnimationOrNull();
|
||||
} else {
|
||||
activeAnimation = project.findAnimationById(animationId);
|
||||
}
|
||||
const core::AnimationSample animSample =
|
||||
activeAnimation ? core::evaluateAnimation(*activeAnimation, frame) : core::AnimationSample{};
|
||||
|
||||
QHash<QString, NodeRef> index;
|
||||
index.reserve(ents.size() + tools.size());
|
||||
@@ -262,8 +136,8 @@ ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, in
|
||||
// cycle:降级为自身采样 origin
|
||||
const NodeRef r = index.value(id);
|
||||
QPointF o;
|
||||
if (r.kind == NodeRef::Kind::Entity) o = sampledOriginForEntity(ents[r.index], clip, localFrame);
|
||||
else o = sampledOriginForTool(tools[r.index], clip, localFrame);
|
||||
if (r.kind == NodeRef::Kind::Entity) o = ents[r.index].originWorld;
|
||||
else o = tools[r.index].originWorld;
|
||||
resolvedOrigin.insert(id, o);
|
||||
return o;
|
||||
}
|
||||
@@ -275,20 +149,23 @@ ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, in
|
||||
if (r.kind == NodeRef::Kind::Entity) {
|
||||
const auto& e = ents[r.index];
|
||||
parentId = e.parentId;
|
||||
selfSampled = sampledOriginForEntity(e, clip, localFrame);
|
||||
selfSampled = e.parentId.isEmpty() ? e.originWorld : e.parentOffsetWorld;
|
||||
if (animSample.entityPosition.contains(e.id)) {
|
||||
selfSampled = animSample.entityPosition.value(e.id);
|
||||
}
|
||||
} else {
|
||||
const auto& t = tools[r.index];
|
||||
parentId = t.parentId;
|
||||
selfSampled = sampledOriginForTool(t, clip, localFrame);
|
||||
selfSampled = t.parentId.isEmpty() ? t.originWorld : t.parentOffsetWorld;
|
||||
if (animSample.toolPosition.contains(t.id)) {
|
||||
selfSampled = animSample.toolPosition.value(t.id);
|
||||
}
|
||||
}
|
||||
|
||||
QPointF outO = selfSampled;
|
||||
if (!parentId.isEmpty() && index.contains(parentId)) {
|
||||
const QPointF po = resolve(parentId);
|
||||
const QPointF rel = (r.kind == NodeRef::Kind::Entity)
|
||||
? sampledRelativeForEntity(ents[r.index], clip, localFrame)
|
||||
: sampledRelativeForTool(tools[r.index], clip, localFrame);
|
||||
outO = po + rel;
|
||||
outO = po + selfSampled;
|
||||
}
|
||||
|
||||
resolving.insert(id, false);
|
||||
@@ -308,50 +185,69 @@ ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, in
|
||||
for (int i = 0; i < ents.size(); ++i) {
|
||||
core::Project::Entity e = ents[i];
|
||||
const QPointF base = e.originWorld;
|
||||
const QPointF ro = (!e.id.isEmpty()) ? resolve(e.id) : sampledOriginForEntity(e, clip, localFrame);
|
||||
const QPointF ro = (!e.id.isEmpty()) ? resolve(e.id) : e.originWorld;
|
||||
const QPointF delta = ro - base;
|
||||
e.originWorld = ro;
|
||||
e.imageTopLeftWorld += delta;
|
||||
|
||||
// Clip channels: userScale / imagePath(迁移后仍能逐帧显示)
|
||||
if (clip && clip->entityUserScaleKeys.contains(e.id)) {
|
||||
const auto& keys = clip->entityUserScaleKeys.value(e.id);
|
||||
e.userScale = core::sampleUserScale(keys, localFrame, e.userScale, core::KeyInterpolation::Linear);
|
||||
if (animSample.entityUserScale.contains(e.id)) {
|
||||
e.userScale = animSample.entityUserScale.value(e.id);
|
||||
}
|
||||
if (clip && clip->entityImageFrames.contains(e.id)) {
|
||||
const auto& frames = clip->entityImageFrames.value(e.id);
|
||||
e.imagePath = core::sampleImagePath(frames, localFrame, e.imagePath);
|
||||
if (animSample.entityDepthScale01.contains(e.id)) {
|
||||
const double v01 = std::clamp(animSample.entityDepthScale01.value(e.id), 0.0, 1.0);
|
||||
e.depth = int(std::lround(v01 * 255.0));
|
||||
}
|
||||
e.runtimeImagePng.clear();
|
||||
if (animSample.entitySpritePng.contains(e.id)) {
|
||||
const QByteArray png = animSample.entitySpritePng.value(e.id);
|
||||
if (!png.isEmpty()) {
|
||||
e.runtimeImagePng = png;
|
||||
}
|
||||
}
|
||||
if (e.runtimeImagePng.isEmpty() && !e.defaultImagePng.isEmpty()) {
|
||||
e.runtimeImagePng = e.defaultImagePng;
|
||||
}
|
||||
|
||||
QVector<core::Project::ToolKeyframeBool> visKeys = e.visibilityKeys;
|
||||
if (clip && clip->entityVisibilityKeys.contains(e.id)) {
|
||||
visKeys = clip->entityVisibilityKeys.value(e.id);
|
||||
if (animSample.entityPriority.contains(e.id)) {
|
||||
const double p = animSample.entityPriority.value(e.id);
|
||||
e.priority = std::clamp(int(std::lround(p)), -1000000, 1000000);
|
||||
}
|
||||
const double op = opacityWithDefault(visKeys, e.visible);
|
||||
out.entities.push_back(ResolvedEntity{e, op});
|
||||
|
||||
const double animOp = animSample.entityVisibility.contains(e.id)
|
||||
? (animSample.entityVisibility.value(e.id) ? 1.0 : 0.0)
|
||||
: (e.visible ? 1.0 : 0.0);
|
||||
out.entities.push_back(ResolvedEntity{e, animOp});
|
||||
}
|
||||
|
||||
// Tools:resolved origin + opacity(可见性轨道)
|
||||
for (int i = 0; i < tools.size(); ++i) {
|
||||
core::Project::Tool t = tools[i];
|
||||
const QPointF base = t.originWorld;
|
||||
const QPointF ro = (!t.id.isEmpty()) ? resolve(t.id) : sampledOriginForTool(t, clip, localFrame);
|
||||
const QPointF ro = (!t.id.isEmpty()) ? resolve(t.id) : t.originWorld;
|
||||
const QPointF delta = ro - base;
|
||||
t.originWorld = ro;
|
||||
// parentOffsetWorld 已包含相对关系,不在这里改
|
||||
QVector<core::Project::ToolKeyframeBool> visKeys = t.visibilityKeys;
|
||||
if (clip && clip->toolVisibilityKeys.contains(t.id)) {
|
||||
visKeys = clip->toolVisibilityKeys.value(t.id);
|
||||
}
|
||||
const double op = opacityWithDefault(visKeys, t.visible);
|
||||
(void)delta;
|
||||
out.tools.push_back(ResolvedTool{t, op});
|
||||
if (animSample.toolPriority.contains(t.id)) {
|
||||
const double p = animSample.toolPriority.value(t.id);
|
||||
t.priority = std::clamp(int(std::lround(p)), -1000000, 1000000);
|
||||
}
|
||||
const double animOp = animSample.toolVisibility.contains(t.id)
|
||||
? (animSample.toolVisibility.value(t.id) ? 1.0 : 0.0)
|
||||
: (t.visible ? 1.0 : 0.0);
|
||||
out.tools.push_back(ResolvedTool{t, animOp});
|
||||
}
|
||||
|
||||
for (const auto& c : cams) {
|
||||
core::Project::Camera cam = c;
|
||||
cam.centerWorld = sampledCenterForCamera(c, clip, localFrame);
|
||||
cam.viewScale = sampledViewScaleForCamera(c, clip, localFrame);
|
||||
cam.centerWorld = c.centerWorld;
|
||||
cam.viewScale = c.viewScale;
|
||||
if (animSample.cameraPosition.contains(c.id)) {
|
||||
cam.centerWorld = animSample.cameraPosition.value(c.id);
|
||||
}
|
||||
if (animSample.cameraScale.contains(c.id)) {
|
||||
cam.viewScale = animSample.cameraScale.value(c.id);
|
||||
}
|
||||
out.cameras.push_back(ResolvedCamera{std::move(cam)});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ struct ResolvedProjectFrame {
|
||||
QVector<ResolvedCamera> cameras;
|
||||
};
|
||||
|
||||
/// 逐帧求值:处理父子跟随与工具可见性淡入淡出。
|
||||
ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, int fadeFrames = 10);
|
||||
/// 逐帧求值:处理父子跟随与工具可见性淡入淡出。animationId 为空时使用 activeAnimationId(含既有回退)。
|
||||
ResolvedProjectFrame evaluateAtFrame(const core::Project& project, int frame, int fadeFrames = 10,
|
||||
const QString& animationId = QString());
|
||||
|
||||
} // namespace core::eval
|
||||
|
||||
|
||||
Reference in New Issue
Block a user