Add frame-based RVB animation snapshots
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -25,6 +26,12 @@ struct RvbImageDraw {
|
|||||||
// root timeline). Clips not mentioned here remain on their first FRAM.
|
// root timeline). Clips not mentioned here remain on their first FRAM.
|
||||||
struct RvbSnapshotState {
|
struct RvbSnapshotState {
|
||||||
std::unordered_map<std::string, std::string> frameByPath;
|
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;
|
bool includeRootOther = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,6 +53,14 @@ bool BuildRvbSnapshot(const std::vector<uint8_t>& bytes,
|
|||||||
std::vector<RvbImageDraw>* draws,
|
std::vector<RvbImageDraw>* draws,
|
||||||
std::string* error = nullptr);
|
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
|
// Builds an exported/linkage MovieClip without requiring it to be placed on
|
||||||
// the root timeline. game471.exe uses this path for the twelve dynamically
|
// the root timeline. game471.exe uses this path for the twelve dynamically
|
||||||
// instantiated select-music rows (mc_music_link / mc_index_link).
|
// instantiated select-music rows (mc_music_link / mc_index_link).
|
||||||
|
|||||||
+39
-6
@@ -162,6 +162,7 @@ struct Context {
|
|||||||
std::vector<RvbImageDraw>* draws = nullptr;
|
std::vector<RvbImageDraw>* draws = nullptr;
|
||||||
std::string* error = nullptr;
|
std::string* error = nullptr;
|
||||||
const RvbSnapshotState* state = nullptr;
|
const RvbSnapshotState* state = nullptr;
|
||||||
|
uint32_t maxAnimationFrames = 1;
|
||||||
|
|
||||||
bool renderDefinition(const std::string& name, const Matrix& matrix,
|
bool renderDefinition(const std::string& name, const Matrix& matrix,
|
||||||
const ColorTransform& color, uint32_t depth, unsigned recursion,
|
const ColorTransform& color, uint32_t depth, unsigned recursion,
|
||||||
@@ -229,11 +230,11 @@ struct Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!targetFrame) return true;
|
if (!targetFrame) return true;
|
||||||
// A label whose script calls play() is an animation entry point, not
|
// A label whose script calls play() is an animation entry point. Walk
|
||||||
// the visible steady state. The original player advances until the
|
// toward the following stop() and select the requested relative frame.
|
||||||
// following stop(); doing the same resolves authored fades such as
|
// UINT32_MAX retains the previous static behavior by selecting the end.
|
||||||
// lf_title_selectmusic_start and jf_focusds_start.
|
|
||||||
if (frameActionSource(bytes, *targetFrame).find("play();") != std::string::npos) {
|
if (frameActionSource(bytes, *targetFrame).find("play();") != std::string::npos) {
|
||||||
|
std::vector<const RvbNode*> animationFrames{targetFrame};
|
||||||
bool afterTarget = false;
|
bool afterTarget = false;
|
||||||
for (const RvbNode& child : timeline.children) {
|
for (const RvbNode& child : timeline.children) {
|
||||||
if (child.tag != "FRAM") continue;
|
if (child.tag != "FRAM") continue;
|
||||||
@@ -242,9 +243,23 @@ struct Context {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!afterTarget) continue;
|
if (!afterTarget) continue;
|
||||||
targetFrame = &child;
|
animationFrames.push_back(&child);
|
||||||
if (frameActionSource(bytes, child).find("stop();") != std::string::npos) break;
|
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 {
|
struct Placement {
|
||||||
uint32_t depth = 0;
|
uint32_t depth = 0;
|
||||||
@@ -383,6 +398,24 @@ bool BuildRvbSnapshot(const std::vector<uint8_t>& bytes,
|
|||||||
return context.renderTimeline(*rootTimeline, {}, {}, 0, "/");
|
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,
|
bool BuildRvbSymbolSnapshot(const std::vector<uint8_t>& bytes,
|
||||||
const RvbScene& scene,
|
const RvbScene& scene,
|
||||||
const std::string& symbolName,
|
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';
|
std::cerr << path << ": snapshot failed: " << error << '\n';
|
||||||
return false;
|
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
|
std::cout << path << "\n movie=" << scene.sourceWidth << 'x' << scene.sourceHeight
|
||||||
<< " @ " << static_cast<unsigned>(scene.framesPerSecond) << " fps"
|
<< " @ " << static_cast<unsigned>(scene.framesPerSecond) << " fps"
|
||||||
<< " bindings=" << scene.bindings.size()
|
<< " bindings=" << scene.bindings.size()
|
||||||
<< " images=" << scene.images.size()
|
<< " images=" << scene.images.size()
|
||||||
|
<< " animationFrames=" << animationFrames
|
||||||
<< " initialDraws=" << snapshot.size() << "\n chunks:";
|
<< " initialDraws=" << snapshot.size() << "\n chunks:";
|
||||||
for (const gc::RvbChunk& chunk : scene.chunks) {
|
for (const gc::RvbChunk& chunk : scene.chunks) {
|
||||||
std::cout << ' ' << chunk.tag << "@0x" << std::hex << chunk.offset
|
std::cout << ' ' << chunk.tag << "@0x" << std::hex << chunk.offset
|
||||||
|
|||||||
Reference in New Issue
Block a user