Add frame-based RVB animation snapshots
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -25,6 +26,12 @@ struct RvbImageDraw {
|
||||
// root timeline). Clips not mentioned here remain on their first FRAM.
|
||||
struct RvbSnapshotState {
|
||||
std::unordered_map<std::string, std::string> frameByPath;
|
||||
// Frame offset after a labeled play() entry point. The default resolves
|
||||
// to the following stop(), preserving the static snapshot behavior.
|
||||
uint32_t animationFrame = std::numeric_limits<uint32_t>::max();
|
||||
// Optional per-clip overrides allow a transition to advance while child
|
||||
// loops and unrelated state clips remain at their authored stop frames.
|
||||
std::unordered_map<std::string, uint32_t> animationFrameByPath;
|
||||
bool includeRootOther = false;
|
||||
};
|
||||
|
||||
@@ -46,6 +53,14 @@ bool BuildRvbSnapshot(const std::vector<uint8_t>& bytes,
|
||||
std::vector<RvbImageDraw>* draws,
|
||||
std::string* error = nullptr);
|
||||
|
||||
// Returns the longest play()..stop() span selected by this state. The result
|
||||
// includes the labeled entry frame and can be converted to seconds with the
|
||||
// scene frame rate.
|
||||
uint32_t MeasureRvbSnapshotAnimationFrames(const std::vector<uint8_t>& bytes,
|
||||
const RvbScene& scene,
|
||||
const RvbSnapshotState& state,
|
||||
std::string* error = nullptr);
|
||||
|
||||
// Builds an exported/linkage MovieClip without requiring it to be placed on
|
||||
// the root timeline. game471.exe uses this path for the twelve dynamically
|
||||
// instantiated select-music rows (mc_music_link / mc_index_link).
|
||||
|
||||
+39
-6
@@ -162,6 +162,7 @@ struct Context {
|
||||
std::vector<RvbImageDraw>* draws = nullptr;
|
||||
std::string* error = nullptr;
|
||||
const RvbSnapshotState* state = nullptr;
|
||||
uint32_t maxAnimationFrames = 1;
|
||||
|
||||
bool renderDefinition(const std::string& name, const Matrix& matrix,
|
||||
const ColorTransform& color, uint32_t depth, unsigned recursion,
|
||||
@@ -229,11 +230,11 @@ struct Context {
|
||||
}
|
||||
}
|
||||
if (!targetFrame) return true;
|
||||
// A label whose script calls play() is an animation entry point, not
|
||||
// the visible steady state. The original player advances until the
|
||||
// following stop(); doing the same resolves authored fades such as
|
||||
// lf_title_selectmusic_start and jf_focusds_start.
|
||||
// A label whose script calls play() is an animation entry point. Walk
|
||||
// toward the following stop() and select the requested relative frame.
|
||||
// UINT32_MAX retains the previous static behavior by selecting the end.
|
||||
if (frameActionSource(bytes, *targetFrame).find("play();") != std::string::npos) {
|
||||
std::vector<const RvbNode*> animationFrames{targetFrame};
|
||||
bool afterTarget = false;
|
||||
for (const RvbNode& child : timeline.children) {
|
||||
if (child.tag != "FRAM") continue;
|
||||
@@ -242,10 +243,24 @@ struct Context {
|
||||
continue;
|
||||
}
|
||||
if (!afterTarget) continue;
|
||||
targetFrame = &child;
|
||||
if (frameActionSource(bytes, child).find("stop();") != std::string::npos) break;
|
||||
animationFrames.push_back(&child);
|
||||
if (frameActionSource(bytes, child).find("stop();") != std::string::npos) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
maxAnimationFrames = std::max(
|
||||
maxAnimationFrames, static_cast<uint32_t>(animationFrames.size()));
|
||||
uint32_t requested = state ? state->animationFrame
|
||||
: std::numeric_limits<uint32_t>::max();
|
||||
if (state) {
|
||||
const auto pathFrame = state->animationFrameByPath.find(path);
|
||||
if (pathFrame != state->animationFrameByPath.end()) {
|
||||
requested = pathFrame->second;
|
||||
}
|
||||
}
|
||||
const size_t frameIndex = std::min<size_t>(requested, animationFrames.size() - 1);
|
||||
targetFrame = animationFrames[frameIndex];
|
||||
}
|
||||
struct Placement {
|
||||
uint32_t depth = 0;
|
||||
std::string definition;
|
||||
@@ -383,6 +398,24 @@ bool BuildRvbSnapshot(const std::vector<uint8_t>& bytes,
|
||||
return context.renderTimeline(*rootTimeline, {}, {}, 0, "/");
|
||||
}
|
||||
|
||||
uint32_t MeasureRvbSnapshotAnimationFrames(const std::vector<uint8_t>& bytes,
|
||||
const RvbScene& scene,
|
||||
const RvbSnapshotState& state,
|
||||
std::string* error) {
|
||||
std::vector<RvbImageDraw> draws;
|
||||
RvbSnapshotState terminalState = state;
|
||||
terminalState.animationFrame = std::numeric_limits<uint32_t>::max();
|
||||
Context context{bytes, {}, &draws, error, &terminalState};
|
||||
const RvbNode* rootTimeline = nullptr;
|
||||
collectDefinitions(bytes, scene, &context, &rootTimeline);
|
||||
if (!rootTimeline) {
|
||||
if (error) *error = "RVB has no root TIME timeline";
|
||||
return 0;
|
||||
}
|
||||
if (!context.renderTimeline(*rootTimeline, {}, {}, 0, "/")) return 0;
|
||||
return context.maxAnimationFrames;
|
||||
}
|
||||
|
||||
bool BuildRvbSymbolSnapshot(const std::vector<uint8_t>& bytes,
|
||||
const RvbScene& scene,
|
||||
const std::string& symbolName,
|
||||
|
||||
@@ -42,11 +42,15 @@ bool probe(const std::string& path, bool tree, const std::string& symbol,
|
||||
std::cerr << path << ": snapshot failed: " << error << '\n';
|
||||
return false;
|
||||
}
|
||||
const uint32_t animationFrames = symbol.empty()
|
||||
? gc::MeasureRvbSnapshotAnimationFrames(bytes, scene, state, &error)
|
||||
: 1;
|
||||
|
||||
std::cout << path << "\n movie=" << scene.sourceWidth << 'x' << scene.sourceHeight
|
||||
<< " @ " << static_cast<unsigned>(scene.framesPerSecond) << " fps"
|
||||
<< " bindings=" << scene.bindings.size()
|
||||
<< " images=" << scene.images.size()
|
||||
<< " animationFrames=" << animationFrames
|
||||
<< " initialDraws=" << snapshot.size() << "\n chunks:";
|
||||
for (const gc::RvbChunk& chunk : scene.chunks) {
|
||||
std::cout << ' ' << chunk.tag << "@0x" << std::hex << chunk.offset
|
||||
|
||||
Reference in New Issue
Block a user