raycaster/animate2.cpp

224 lines
6.6 KiB
C++

#include "animate2.hpp"
#include <memory>
#include <chrono>
#include "dbc.hpp"
#include "rand.hpp"
#include <iostream>
#include <fstream>
#include "sound.hpp"
constexpr float SUB_FRAME_SENSITIVITY = 0.999f;
namespace animate2 {
std::vector<sf::IntRect> Animate2::calc_frames() {
dbc::check(sequence.frames.size() == sequence.durations.size(), "sequence.frames.size() != sequence.durations.size()");
std::vector<sf::IntRect> frames;
for(int frame_i : sequence.frames) {
dbc::check(frame_i < sheet.frames, "frame index greater than sheet frames");
frames.emplace_back(
sf::Vector2i{sheet.frame_width * frame_i, 0}, // NOTE: one row only for now
sf::Vector2i{sheet.frame_width,
sheet.frame_height});
}
return frames;
}
void Animate2::play() {
dbc::check(!playing, "can't call play while playing?");
sequence.current = 0;
sequence.subframe = 0.0f;
sequence.loop_count = 0;
playing = true;
sequence.timer.start();
sequence.frame_count = sequence.frames.size();
}
void Animate2::stop() {
playing = false;
sequence.timer.reset();
}
// need one for each kind of thing to animate
// NOTE: possibly find a way to only run apply on frame change?
void Animate2::apply(sf::Sprite& sprite) {
dbc::check(sequence.current < $frame_rects.size(), "current frame past $frame_rects");
// NOTE: pos is not updated yet
auto& rect = $frame_rects.at(sequence.current);
sprite.setTextureRect(rect);
}
void Animate2::apply_effect(std::shared_ptr<sf::Shader> effect) {
dbc::check(effect != nullptr, "can't apply null effect");
effect->setUniform("u_time", sequence.timer.getElapsedTime().asSeconds());
sf::Vector2f u_resolution{float(sheet.frame_width), float(sheet.frame_height)};
effect->setUniform("u_resolution", u_resolution);
}
void Animate2::play_sound() {
// BUG: this can be optimized way better
if(sounds.contains(form_name)) {
for(auto& [at_frame, sound_name] : sounds.at(form_name)) {
if(sequence.current == at_frame) {
sound::play(sound_name);
}
}
} else {
fmt::println("Animation has not sound {}", form_name);
}
}
void Animate2::update() {
dbc::check(playing, "attempt to update animation that's not playing");
dbc::check(sequence.frame_count == sequence.frames.size(), "frame_count doesn't match frame.size()");
auto duration = sequence.durations.at(sequence.current);
auto elapsed = sequence.timer.getElapsedTime().toDuration();
// std::cout << "subframe: " << sequence.subframe << " elapsed: " << elapsed << " duration: " << duration << std::endl;
bool frame_change = false;
if(elapsed < duration) {
// BUG: subframe will just run crazy because I don't actually do delta time difference here
sequence.subframe = std::lerp(sequence.subframe, 1.0, sequence.timer.DELTA * transform.ease_rate);
} else {
sequence.timer.restart();
sequence.current++;
if(sequence.subframe > SUB_FRAME_SENSITIVITY) {
sequence.subframe = 0.0f;
}
frame_change = true;
}
if(sequence.current >= sequence.frame_count) {
sequence.loop_count++;
playing = onLoop(sequence, transform);
}
if(frame_change) play_sound();
if(frame_change && onFrame != nullptr) onFrame();
dbc::check(sequence.current < sequence.frame_count, "onLoop fail: current frame out of frames.size()");
}
void Animate2::motion(sf::Sprite& sprite, sf::Vector2f pos, sf::Vector2f scale) {
transform.lerp(sequence, pos, scale);
if(transform.flipped) {
scale.x *= -1;
}
sprite.setPosition(pos);
if(transform.scaled) {
sprite.setScale(scale);
}
}
std::pair<int, double> Animate2::commit() {
return sequence.timer.commit();
}
void Timer::start() {
clock.start();
prev_time = clock.getElapsedTime().asSeconds();
}
void Timer::reset() {
clock.reset();
}
void Timer::restart() {
clock.restart();
prev_time = clock.getElapsedTime().asSeconds();
}
sf::Time Timer::getElapsedTime() {
return clock.getElapsedTime();
}
std::pair<int, double> Timer::commit() {
// determine frame duration based on previous time
current_time = clock.getElapsedTime().asSeconds();
frame_duration = current_time - prev_time;
// update prev_time for the next call
prev_time = current_time;
// update accumulator, retaining previous errors
accumulator += frame_duration;
// find the tick count based on DELTA
double tick_count = floor(accumulator / DELTA);
// reduce accumulator by the number of DELTAS
accumulator -= tick_count * DELTA;
// that leaves the remaining errors for next loop
// alpha is then what we lerp...but WHY?!
alpha = accumulator / DELTA;
// return the number of even DELTA ticks and the alpha
return {int(tick_count), alpha};
}
void Transform::lerp(Sequence& seq, sf::Vector2f& pos_out, sf::Vector2f& scale_out) {
// float dt = 1 - std::powf(ease_rate, seq.subframe + 0.0001);
float tick = easing_func(seq.subframe);
motion_func(*this, pos_out, scale_out, tick);
// fmt::println("sub: {}, tick: {}, tr: {},{}; pos: {},{}; scale: {},{}",
// seq.subframe, tick, min_y, max_y, pos_out.x, pos_out.y,
// scale_out.x, scale_out.y);
}
bool Animate2::has_form(const std::string& as_form) {
return forms.contains(as_form);
}
void Animate2::set_form(const std::string& as_form) {
dbc::check(forms.contains(as_form),
fmt::format("form {} does not exist in animation", as_form));
stop();
const auto& [seq_name, tr_name] = forms.at(as_form);
dbc::check(sequences.contains(seq_name),
fmt::format("sequences do NOT have \"{}\" name", seq_name));
dbc::check(transforms.contains(tr_name),
fmt::format("transforms do NOT have \"{}\" name", tr_name));
// everything good, do the update
form_name = as_form;
sequence_name = seq_name;
transform_name = tr_name;
sequence = sequences.at(seq_name);
transform = transforms.at(tr_name);
$frame_rects = calc_frames();
transform.easing_func = ease2::get_easing(transform.easing);
transform.motion_func = ease2::get_motion(transform.motion);
}
Animate2 load(const std::string &file, const std::string &anim_name) {
using nlohmann::json;
std::ifstream infile(file);
auto data = json::parse(infile);
Animate2 anim;
animate2::from_json(data[anim_name], anim);
dbc::check(anim.forms.contains("idle"),
fmt::format("animation {} must have 'idle' form", anim_name));
anim.set_form("idle");
return anim;
}
}