Files
ReSkate/Extension/ReplayKeyframes/replay_settings.cpp
T
samitakkulaandClaude Opus 5.5 51dddf3d91 Keyframe smoothing for the replay editor's free camera
The game plays the replay camera's framing keys as cubic Hermite segments whose speed changes at
every key (the acceleration jumps there, and it brakes where a channel slows or turns back), so a
free-camera shot surges and settles around each key. keysmooth on rewrites each segment as the game
builds it, so that the game's own player plays the natural cubic spline through every key: speed
and acceleration carry on through the keys, and every key is still met exactly, at its time.

- keysmooth on|off (off by default), keysmooth amount 0..1 (1 by default; 0 is the game's curve)
  and keysmooth status; kept in %LOCALAPPDATA%\ReSkate\KeySmooth.settings. Only the free camera
  (Behaviour 1) is smoothed; the other camera modes keep the game's curve.
- A switch in Mod Options > Visuals and in the overlay's Skater > Freecam.
- The curve apart from the game, with tests (DINGOSDK_BUILD_REPLAY_KEYFRAMES_TESTS); the hooked
  functions are fingerprinted, and a build that differs gets no hook.
- contrib/keyframe-smoothing.md: what it does, how, and what was seen in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:33:32 -07:00

47 lines
1.7 KiB
C++

#include "replay_settings.h"
#include <cctype>
#include <charconv>
#include <cmath>
#include <format>
namespace dingosdk::replay_keyframes {
namespace {
std::string_view trimmed(std::string_view text) {
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.front()))) text.remove_prefix(1);
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back()))) text.remove_suffix(1);
return text;
}
template <class T> bool number(std::string_view text, T& out) {
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), out);
return parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size();
}
bool flag(std::string_view text, bool& out) {
if (text != "0" && text != "1") return false;
out = text == "1";
return true;
}
} // namespace
std::string to_text(const Settings& settings) {
return std::format("on={}\namount={:g}\n", settings.on ? 1 : 0, settings.amount);
}
Settings from_text(std::string_view text) {
Settings settings;
while (!text.empty()) {
const auto end = text.find('\n');
const auto line = text.substr(0, end);
text = end == std::string_view::npos ? std::string_view{} : text.substr(end + 1);
const auto eq = line.find('=');
if (eq == std::string_view::npos) continue;
const auto key = trimmed(line.substr(0, eq)), value = trimmed(line.substr(eq + 1));
if (key == "on") (void)flag(value, settings.on);
else if (key == "amount") {
float amount{};
if (number(value, amount) && std::isfinite(amount) && amount >= 0 && amount <= 1) settings.amount = amount;
}
}
return settings;
}
} // namespace dingosdk::replay_keyframes