mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-15 03:27:41 +01:00
Still in the middle of rewriting ...
This commit is contained in:
parent
b6fff2914d
commit
b689c1ede5
@ -86,6 +86,39 @@ namespace better {
|
||||
Position LongNote::get_tail_tip() const {
|
||||
return tail_tip;
|
||||
};
|
||||
|
||||
const auto abs_diff = [](const auto& a, const auto& b){
|
||||
if (a > b) {
|
||||
return a - b;
|
||||
} else {
|
||||
return b - a;
|
||||
}
|
||||
};
|
||||
|
||||
unsigned int LongNote::get_tail_length() const {
|
||||
if (position.get_x() == tail_tip.get_x()) {
|
||||
return abs_diff(position.get_y(), tail_tip.get_y());
|
||||
} else {
|
||||
return abs_diff(position.get_x(), tail_tip.get_x());
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int LongNote::get_tail_angle() const {
|
||||
if (position.get_x() == tail_tip.get_x()) {
|
||||
if (position.get_y() > tail_tip.get_y()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 180;
|
||||
}
|
||||
} else {
|
||||
if (position.get_x() > tail_tip.get_x()) {
|
||||
return 270;
|
||||
} else {
|
||||
return 90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto _time_bounds = VariantVisitor {
|
||||
[](const TapNote& t) -> std::pair<Fraction, Fraction> { return {t.get_time(), t.get_time()}; },
|
||||
|
@ -17,8 +17,8 @@ namespace better {
|
||||
0 1 2 3
|
||||
y 0 □ □ □ □
|
||||
↓ 1 □ □ □ □
|
||||
2 □ □ □ □
|
||||
3 □ □ □ □
|
||||
2 □ □ □ □
|
||||
3 □ □ □ □
|
||||
*/
|
||||
class Position {
|
||||
public:
|
||||
@ -51,12 +51,14 @@ namespace better {
|
||||
class LongNote {
|
||||
public:
|
||||
LongNote(Fraction time, Position position, Fraction duration, Position tail_tip);
|
||||
|
||||
|
||||
Fraction get_time() const;
|
||||
Position get_position() const;
|
||||
Fraction get_end() const;
|
||||
Fraction get_duration() const;
|
||||
Position get_tail_tip() const;
|
||||
unsigned int get_tail_length() const;
|
||||
unsigned int get_tail_angle() const;
|
||||
private:
|
||||
Fraction time;
|
||||
Position position;
|
||||
|
@ -63,5 +63,6 @@ namespace better {
|
||||
decltype(order_by_difficulty_name)
|
||||
> charts{order_by_difficulty_name};
|
||||
Metadata metadata;
|
||||
Timing timing;
|
||||
};
|
||||
}
|
@ -117,19 +117,49 @@ namespace better {
|
||||
change
|
||||
*/
|
||||
Fraction Timing::fractional_seconds_at(Fraction beats) const {
|
||||
auto bpm_change = this->events_by_beats
|
||||
.upper_bound(BPMEvent(beats, 0, 0));
|
||||
auto bpm_change = this->events_by_beats.upper_bound(BPMEvent(beats, 0, 0));
|
||||
if (bpm_change != this->events_by_beats.begin()) {
|
||||
bpm_change = std::prev(bpm_change);
|
||||
}
|
||||
auto beats_since_previous_event = beats - bpm_change->get_beats();
|
||||
auto seconds_since_previous_event =
|
||||
(60 * beats_since_previous_event) / bpm_change->get_bpm();
|
||||
auto seconds_since_previous_event = (
|
||||
Fraction{60}
|
||||
* beats_since_previous_event
|
||||
/ bpm_change->get_bpm()
|
||||
);
|
||||
return bpm_change->get_seconds() + seconds_since_previous_event;
|
||||
};
|
||||
|
||||
Fraction Timing::fractional_seconds_between(
|
||||
Fraction beat_a,
|
||||
Fraction beat_b
|
||||
) const {
|
||||
return (
|
||||
fractional_seconds_at(beat_b)
|
||||
- fractional_seconds_at(beat_a)
|
||||
);
|
||||
};
|
||||
|
||||
sf::Time Timing::time_at(Fraction beats) const {
|
||||
auto microseconds = fractional_seconds_at(beats) * 1000000;
|
||||
return sf::microseconds(microseconds.convert_to<sf::Int64>());
|
||||
}
|
||||
return frac_to_time(fractional_seconds_at(beats));
|
||||
};
|
||||
|
||||
sf::Time Timing::time_between(Fraction beat_a, Fraction beat_b) const {
|
||||
return frac_to_time(fractional_seconds_between(beat_a, beat_b));
|
||||
};
|
||||
|
||||
Fraction Timing::beats_at(sf::Time time) const {
|
||||
Fraction fractional_seconds{time.asMicroseconds(), 1000000};
|
||||
auto bpm_change = this->events_by_seconds.upper_bound(BPMEvent(0, fractional_seconds, 0));
|
||||
if (bpm_change != this->events_by_seconds.begin()) {
|
||||
bpm_change = std::prev(bpm_change);
|
||||
}
|
||||
auto seconds_since_previous_event = fractional_seconds - bpm_change->get_seconds();
|
||||
auto beats_since_previous_event = (
|
||||
bpm_change->get_bpm()
|
||||
* seconds_since_previous_event
|
||||
/ Fraction{60}
|
||||
);
|
||||
return bpm_change->get_beats() + beats_since_previous_event;
|
||||
};
|
||||
}
|
@ -48,10 +48,19 @@ namespace better {
|
||||
Timing(const std::vector<BPMAtBeat>& events, const SecondsAtBeat& offset);
|
||||
|
||||
Fraction fractional_seconds_at(Fraction beats) const;
|
||||
Fraction fractional_seconds_between(Fraction beat_a, Fraction beat_b) const;
|
||||
sf::Time time_at(Fraction beats) const;
|
||||
sf::Time time_between(Fraction beat_a, Fraction beat_b) const;
|
||||
|
||||
Fraction beats_at(sf::Time time) const;
|
||||
|
||||
private:
|
||||
std::set<BPMEvent, decltype(order_by_beats)> events_by_beats{order_by_beats};
|
||||
std::set<BPMEvent, decltype(order_by_seconds)> events_by_seconds{order_by_seconds};
|
||||
};
|
||||
|
||||
const auto frac_to_time = [](const Fraction& f) {
|
||||
auto microseconds = f * 1000000;
|
||||
return sf::microseconds(microseconds.convert_to<sf::Int64>());
|
||||
};
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
#include "chart_state.hpp"
|
||||
#include "src/better_note.hpp"
|
||||
|
||||
ChartState::ChartState(better::Chart& c, std::filesystem::path assets) :
|
||||
chart(c),
|
||||
@ -7,9 +8,12 @@ ChartState::ChartState(better::Chart& c, std::filesystem::path assets) :
|
||||
history.push(std::make_shared<OpenChart>(c));
|
||||
}
|
||||
|
||||
std::optional<Note> ChartState::makeLongNoteDummy(int current_tick) const {
|
||||
std::optional<better::LongNote> ChartState::make_long_note_dummy(Fraction current_beat) const {
|
||||
if (creating_long_note and long_note_being_created) {
|
||||
Note long_note = Note(long_note_being_created->first, long_note_being_created->second);
|
||||
better::LongNote long_note{
|
||||
long_note_being_created->first,
|
||||
long_note_being_created->second
|
||||
};
|
||||
Note dummy_long_note = Note(
|
||||
long_note.getPos(),
|
||||
current_tick,
|
||||
@ -21,7 +25,7 @@ std::optional<Note> ChartState::makeLongNoteDummy(int current_tick) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Note> ChartState::makeCurrentLongNote() const {
|
||||
std::optional<better::LongNote> ChartState::make_current_long_note() const {
|
||||
if (creating_long_note and long_note_being_created) {
|
||||
return Note(long_note_being_created->first, long_note_being_created->second);
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "better_note.hpp"
|
||||
#include "better_song.hpp"
|
||||
#include "history.hpp"
|
||||
#include "history_actions.hpp"
|
||||
@ -15,11 +16,11 @@ struct ChartState {
|
||||
std::set<Note> selected_notes;
|
||||
NotesClipboard notes_clipboard;
|
||||
SelectionState time_selection;
|
||||
std::optional<std::pair<Note, Note>> long_note_being_created;
|
||||
std::optional<better::LongNote> long_note_being_created;
|
||||
bool creating_long_note;
|
||||
History<std::shared_ptr<ActionWithMessage>> history;
|
||||
DensityGraph density_graph;
|
||||
|
||||
std::optional<Note> makeLongNoteDummy(int current_tick) const;
|
||||
std::optional<Note> makeCurrentLongNote() const;
|
||||
std::optional<better::LongNote> make_long_note_dummy(Fraction current_beat) const;
|
||||
std::optional<better::LongNote> make_current_long_note() const;
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "editor_state.hpp"
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
@ -8,6 +9,27 @@
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <tinyfiledialogs.h>
|
||||
#include "src/time_interval.hpp"
|
||||
|
||||
EditorState::EditorState(
|
||||
const better::Song& song_,
|
||||
const std::filesystem::path& assets_,
|
||||
const std::filesystem::path& song_path = {}
|
||||
) :
|
||||
song(song_),
|
||||
song_path(song_path),
|
||||
playfield(assets_),
|
||||
linear_view(assets_),
|
||||
music_path_in_gui(song.metadata.audio.value_or("")),
|
||||
applicable_timing(song.timing),
|
||||
assets(assets_)
|
||||
{
|
||||
if (not song.charts.empty()) {
|
||||
open_chart(this->song.charts.begin()->second);
|
||||
}
|
||||
reload_music();
|
||||
reload_album_cover();
|
||||
};
|
||||
|
||||
/*
|
||||
* Reloads music from what's indicated in the "music path" field of the song
|
||||
@ -15,47 +37,47 @@
|
||||
* Updates playbackPosition and preview_end as well
|
||||
*/
|
||||
void EditorState::reload_music() {
|
||||
const auto absolute_music_path = song_path.parent_path() / edited_music_path;
|
||||
if (not song_path.has_value()) {
|
||||
music_state.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto absolute_music_path = song_path->parent_path() / music_path_in_gui;
|
||||
try {
|
||||
music_state.emplace(absolute_music_path);
|
||||
} catch (const std::exception& e) {
|
||||
music_state.reset();
|
||||
}
|
||||
|
||||
reload_preview_end();
|
||||
|
||||
auto preview_start = sf::Time::Zero;
|
||||
if (chart_state) {
|
||||
preview_start = std::min(preview_start, chart_state->chart.timing.time_at(0));
|
||||
}
|
||||
playback_position = std::clamp(playback_position, preview_start, preview_end);
|
||||
previous_pos = playback_position;
|
||||
reload_editable_range();
|
||||
playback_position = std::clamp(
|
||||
playback_position,
|
||||
editable_range.start,
|
||||
editable_range.end
|
||||
);
|
||||
previous_playback_position = playback_position;
|
||||
}
|
||||
|
||||
void EditorState::reload_preview_end() {
|
||||
auto old_preview_end = this->preview_end;
|
||||
sf::Time music_duration = sf::Time::Zero;
|
||||
void EditorState::reload_editable_range() {
|
||||
auto old_range = this->editable_range;
|
||||
TimeInterval new_range;
|
||||
if (music_state) {
|
||||
music_duration = music_state->getDuration();
|
||||
new_range += music_state->music.getDuration();
|
||||
}
|
||||
if (chart_state) {
|
||||
new_range += chart_state->chart.time_of_last_event().value_or(sf::Time::Zero);
|
||||
}
|
||||
|
||||
float chart_end = 0;
|
||||
if (chart) {
|
||||
chart_end = chart->ref
|
||||
.time_of_last_event()
|
||||
.value_or(sf::Time::Zero)
|
||||
.asSeconds();
|
||||
new_range.end += sf::seconds(10);
|
||||
|
||||
// If there is no music, make sure we can edit at least the first whole minute
|
||||
if (not music_state) {
|
||||
new_range += sf::seconds(60);
|
||||
}
|
||||
|
||||
float preview_end_seconds = std::max(music_duration, chart_end);
|
||||
|
||||
// Add some extra time at the end to allow for more notes to be placed
|
||||
// after the end of the chart
|
||||
// TODO: is this really the way to do it ?
|
||||
preview_end_seconds += 2.f;
|
||||
this->preview_end = sf::seconds(preview_end_seconds);
|
||||
if (old_preview_end != this->preview_end and this->chart.has_value()) {
|
||||
chart->density_graph.should_recompute = true;
|
||||
this->editable_range = new_range;
|
||||
if (old_range != new_range and this->chart_state.has_value()) {
|
||||
chart_state->density_graph.should_recompute = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,38 +86,56 @@ void EditorState::reload_preview_end() {
|
||||
* of the song Resets the album cover state if anything fails
|
||||
*/
|
||||
void EditorState::reload_album_cover() {
|
||||
album_cover.emplace();
|
||||
if (not song_path.has_value() or not song.metadata.jacket.has_value()) {
|
||||
jacket.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path album_cover_path =
|
||||
std::filesystem::path(song.path).parent_path() / song.albumCoverPath;
|
||||
jacket.emplace();
|
||||
auto jacket_path = song_path->parent_path() / *song.metadata.jacket;
|
||||
|
||||
if (song.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
|
||||
or not album_cover->loadFromFile(album_cover_path.string())) {
|
||||
album_cover.reset();
|
||||
if (
|
||||
not std::filesystem::exists(jacket_path)
|
||||
or not jacket->loadFromFile(jacket_path.string())
|
||||
) {
|
||||
jacket.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::set_playback_and_music_position(sf::Time newPosition) {
|
||||
reload_preview_end();
|
||||
|
||||
newPosition = sf::seconds(
|
||||
std::clamp(
|
||||
newPosition.asSeconds(),
|
||||
-song.offset,
|
||||
this->preview_end.asSeconds()
|
||||
)
|
||||
);
|
||||
previous_pos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
|
||||
void EditorState::set_playback_position(sf::Time newPosition) {
|
||||
newPosition = std::clamp(newPosition, editable_range.start, editable_range.end);
|
||||
previous_playback_position = newPosition - (sf::seconds(1) / 60.f);
|
||||
playback_position = newPosition;
|
||||
if (music) {
|
||||
if (playback_position.asSeconds() >= 0 and playback_position < music->getDuration()) {
|
||||
music->setPlayingOffset(playback_position);
|
||||
if (music_state) {
|
||||
if (
|
||||
playback_position >= sf::Time::Zero
|
||||
and playback_position < music_state->music.getDuration()
|
||||
) {
|
||||
music_state->music.setPlayingOffset(playback_position);
|
||||
} else {
|
||||
music->stop();
|
||||
music_state->music.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float EditorState::current_beats() {
|
||||
return beats_at(playback_position);
|
||||
};
|
||||
|
||||
float EditorState::beats_at(sf::Time time) {
|
||||
auto frac_beats = applicable_timing.beats_at(time);
|
||||
return static_cast<float>(frac_beats);
|
||||
};
|
||||
|
||||
float EditorState::seconds_at(Fraction beat) {
|
||||
auto frac_seconds = applicable_timing.fractional_seconds_at(beat);
|
||||
return static_cast<float>(frac_seconds);
|
||||
};
|
||||
|
||||
Fraction EditorState::get_snap_step() {
|
||||
return Fraction{1, snap};
|
||||
};
|
||||
|
||||
void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndingState) {
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSizeConstraints(
|
||||
@ -104,32 +144,34 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
Toolbox::CustomConstraints::ContentSquare);
|
||||
|
||||
if (ImGui::Begin("Playfield", &showPlayfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
if (not ImGui::IsWindowHovered() and chart and chart->creating_long_note) {
|
||||
if (
|
||||
not ImGui::IsWindowHovered()
|
||||
and chart_state
|
||||
and chart_state->creating_long_note
|
||||
) {
|
||||
// cancel long note creation if the mouse is or goes out of the playfield
|
||||
chart->long_note_being_created.reset();
|
||||
chart->creating_long_note = false;
|
||||
chart_state->long_note_being_created.reset();
|
||||
chart_state->creating_long_note = false;
|
||||
}
|
||||
float squareSize = ImGui::GetWindowSize().x / 4.f;
|
||||
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
|
||||
int ImGuiIndex = 0;
|
||||
|
||||
if (chart) {
|
||||
if (chart_state) {
|
||||
playfield.resize(static_cast<unsigned int>(ImGui::GetWindowSize().x));
|
||||
|
||||
auto longNoteDummy =
|
||||
chart->makeLongNoteDummy(static_cast<int>(roundf(getCurrentTick())));
|
||||
if (longNoteDummy) {
|
||||
if (chart_state->long_note_being_created) {
|
||||
playfield.drawLongNote(
|
||||
*longNoteDummy,
|
||||
*chart_state->long_note_being_created,
|
||||
playback_position,
|
||||
getCurrentTick(),
|
||||
song.BPM,
|
||||
getResolution());
|
||||
getResolution()
|
||||
);
|
||||
}
|
||||
|
||||
for (auto const& note : visibleNotes) {
|
||||
float note_offset =
|
||||
(playback_position.asSeconds() - getSecondsAt(note.getTiming()));
|
||||
(playback_position.asSeconds() - seconds_at(note.getTiming()));
|
||||
// auto frame = static_cast<long long
|
||||
// int>(std::floor(note_offset * 30.f));
|
||||
int x = note.getPos() % 4;
|
||||
@ -152,7 +194,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
playfield.drawLongNote(
|
||||
note,
|
||||
playback_position,
|
||||
getCurrentTick(),
|
||||
current_tick(),
|
||||
song.BPM,
|
||||
getResolution(),
|
||||
marker,
|
||||
@ -161,9 +203,9 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
}
|
||||
|
||||
ImGui::SetCursorPos({0, TitlebarHeight});
|
||||
ImGui::Image(playfield.longNoteLayer);
|
||||
ImGui::Image(playfield.long_note_layer);
|
||||
ImGui::SetCursorPos({0, TitlebarHeight});
|
||||
ImGui::Image(playfield.markerLayer);
|
||||
ImGui::Image(playfield.marker_layer);
|
||||
}
|
||||
|
||||
// Display button grid
|
||||
@ -181,11 +223,11 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) {
|
||||
toggleNoteAtCurrentTime(x + 4 * y);
|
||||
}
|
||||
if (ImGui::IsItemHovered() and chart and chart->creating_long_note) {
|
||||
if (ImGui::IsItemHovered() and chart_state and chart_state->creating_long_note) {
|
||||
// Deal with long note creation stuff
|
||||
if (not chart->long_note_being_created) {
|
||||
Note current_note =
|
||||
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
|
||||
Note(x + 4 * y, static_cast<int>(roundf(current_tick())));
|
||||
chart->long_note_being_created =
|
||||
std::make_pair(current_note, current_note);
|
||||
} else {
|
||||
@ -248,8 +290,8 @@ void EditorState::displayProperties() {
|
||||
{
|
||||
ImGui::Columns(2, nullptr, false);
|
||||
|
||||
if (album_cover) {
|
||||
ImGui::Image(*album_cover, sf::Vector2f(200, 200));
|
||||
if (jacket) {
|
||||
ImGui::Image(*jacket, sf::Vector2f(200, 200));
|
||||
} else {
|
||||
ImGui::BeginChild("Album Cover", ImVec2(200, 200), true);
|
||||
ImGui::EndChild();
|
||||
@ -266,7 +308,7 @@ void EditorState::displayProperties() {
|
||||
reload_music();
|
||||
}
|
||||
if (Toolbox::InputTextColored(
|
||||
album_cover.has_value(),
|
||||
jacket.has_value(),
|
||||
"Invalid Album Cover Path",
|
||||
"Album Cover",
|
||||
&song.albumCoverPath)) {
|
||||
@ -302,7 +344,7 @@ void EditorState::displayStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
if (not album_cover) {
|
||||
if (not jacket) {
|
||||
if (not song.albumCoverPath.empty()) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.42, 0.41, 1),
|
||||
@ -347,7 +389,7 @@ void EditorState::displayPlaybackStatus() {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Beats :");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%02.2f", this->getBeats());
|
||||
ImGui::Text("%02.2f", this->get_current_beats());
|
||||
ImGui::SameLine();
|
||||
if (music) {
|
||||
ImGui::TextColored(
|
||||
@ -590,9 +632,14 @@ void EditorState::toggleNoteAtCurrentTime(int pos) {
|
||||
}
|
||||
}
|
||||
|
||||
const sf::Time& EditorState::get_preview_end() {
|
||||
reload_preview_end();
|
||||
return preview_end;
|
||||
const TimeInterval& EditorState::get_editable_range() {
|
||||
reload_editable_range();
|
||||
return editable_range;
|
||||
}
|
||||
|
||||
void EditorState::open_chart(better::Chart& chart) {
|
||||
chart_state.emplace(chart, assets);
|
||||
|
||||
}
|
||||
|
||||
void ESHelper::save(EditorState& ed) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "music_state.hpp"
|
||||
#include "notes_clipboard.hpp"
|
||||
#include "precise_music.hpp"
|
||||
#include "time_interval.hpp"
|
||||
#include "time_selection.hpp"
|
||||
#include "widgets/linear_view.hpp"
|
||||
#include "widgets/playfield.hpp"
|
||||
@ -28,29 +29,19 @@ enum saveChangesResponses {
|
||||
|
||||
/*
|
||||
* The god class, holds everything there is to know about the currently open
|
||||
* .memon file
|
||||
* file
|
||||
*/
|
||||
class EditorState {
|
||||
public:
|
||||
EditorState(
|
||||
const better::Song& song,
|
||||
const std::filesystem::path& song_path,
|
||||
const std::filesystem::path& assets
|
||||
) :
|
||||
song(song),
|
||||
playfield(assets),
|
||||
linear_view(assets),
|
||||
edited_music_path(song.metadata.audio.value_or("")),
|
||||
song_path(song_path)
|
||||
{
|
||||
if (not this->song.charts.empty()) {
|
||||
this->chart_state.emplace(this->song.charts.begin()->second, assets);
|
||||
}
|
||||
reload_music();
|
||||
reload_album_cover();
|
||||
};
|
||||
const std::filesystem::path& assets,
|
||||
const std::filesystem::path& save_path
|
||||
);
|
||||
|
||||
better::Song song;
|
||||
std::optional<std::filesystem::path> song_path;
|
||||
|
||||
std::optional<ChartState> chart_state;
|
||||
|
||||
std::optional<MusicState> music_state;
|
||||
@ -58,38 +49,23 @@ public:
|
||||
Playfield playfield;
|
||||
LinearView linear_view;
|
||||
|
||||
// the snap but divided by 4 because you can't set a snap to anything lower
|
||||
// than 4ths
|
||||
int snap = 1;
|
||||
|
||||
std::optional<sf::Texture> album_cover;
|
||||
std::optional<sf::Texture> jacket;
|
||||
|
||||
bool playing;
|
||||
|
||||
sf::Time previous_pos;
|
||||
sf::Time previous_playback_position;
|
||||
sf::Time playback_position;
|
||||
|
||||
const sf::Time& get_preview_end();
|
||||
const TimeInterval& get_editable_range();
|
||||
|
||||
void set_playback_and_music_position(sf::Time new_position);
|
||||
void set_playback_position(sf::Time new_position);
|
||||
|
||||
float getBeats() { return getBeatsAt(playback_position.asSeconds()); };
|
||||
float getBeatsAt(float seconds) {
|
||||
return ((seconds + song.offset) / 60.f) * song.BPM;
|
||||
};
|
||||
float getCurrentTick() { return getTicksAt(playback_position.asSeconds()); };
|
||||
float getTicksAt(float seconds) {
|
||||
return getBeatsAt(seconds) * get_resolution();
|
||||
}
|
||||
float getSecondsAt(int tick) {
|
||||
return (60.f * tick) / (song.BPM * get_resolution()) - song.offset;
|
||||
};
|
||||
int get_resolution() { return chart_state ? chart_state->chart.getResolution() : 240; };
|
||||
int get_snap_step() { return get_resolution() / snap; };
|
||||
|
||||
|
||||
void reload_album_cover();
|
||||
void reload_preview_end();
|
||||
float current_beats();
|
||||
float beats_at(sf::Time time);
|
||||
float seconds_at(Fraction beat);
|
||||
Fraction get_snap_step();
|
||||
|
||||
bool showPlayfield = true;
|
||||
bool showProperties;
|
||||
@ -120,13 +96,25 @@ public:
|
||||
void toggleNoteAtCurrentTime(int pos);
|
||||
|
||||
private:
|
||||
sf::Time preview_end; // sf::Time (in the audio file "coordinates") at which the chart preview stops, can be
|
||||
// after the end of the actual audio file
|
||||
/*
|
||||
sf::Time bounds (in the audio file "coordinates") which are accessible
|
||||
(and maybe editable) from the editor, can extend before and after
|
||||
the actual audio file
|
||||
*/
|
||||
TimeInterval editable_range;
|
||||
|
||||
void reload_album_cover();
|
||||
void reload_editable_range();
|
||||
|
||||
std::string edited_music_path;
|
||||
std::string music_path_in_gui;
|
||||
void reload_music();
|
||||
|
||||
std::filesystem::path song_path;
|
||||
better::Timing& applicable_timing;
|
||||
void reload_applicable_timing();
|
||||
|
||||
void open_chart(better::Chart& chart);
|
||||
|
||||
std::filesystem::path assets;
|
||||
};
|
||||
|
||||
namespace ESHelper {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
void Move::backwardsInTime(std::optional<EditorState>& ed) {
|
||||
if (ed and ed->chart) {
|
||||
float floatTicks = ed->getCurrentTick();
|
||||
float floatTicks = ed->current_tick();
|
||||
auto prevTick = static_cast<int>(floorf(floatTicks));
|
||||
int step = ed->get_snap_step();
|
||||
int prevTickInSnap = prevTick;
|
||||
@ -11,17 +11,17 @@ void Move::backwardsInTime(std::optional<EditorState>& ed) {
|
||||
} else {
|
||||
prevTickInSnap -= prevTick % step;
|
||||
}
|
||||
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(prevTickInSnap)));
|
||||
ed->setPlaybackAndMusicPosition(sf::seconds(ed->seconds_at(prevTickInSnap)));
|
||||
}
|
||||
}
|
||||
|
||||
void Move::forwardsInTime(std::optional<EditorState>& ed) {
|
||||
if (ed and ed->chart) {
|
||||
float floatTicks = ed->getCurrentTick();
|
||||
float floatTicks = ed->current_tick();
|
||||
auto nextTick = static_cast<int>(ceilf(floatTicks));
|
||||
int step = ed->get_snap_step();
|
||||
int nextTickInSnap = nextTick + (step - nextTick % step);
|
||||
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(nextTickInSnap)));
|
||||
ed->setPlaybackAndMusicPosition(sf::seconds(ed->seconds_at(nextTickInSnap)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ void Edit::copy(std::optional<EditorState>& ed, NotificationsQueue& nq) {
|
||||
|
||||
void Edit::paste(std::optional<EditorState>& ed, NotificationsQueue& nq) {
|
||||
if (ed and ed->chart and (not ed->chart->notesClipboard.empty())) {
|
||||
auto tick_offset = static_cast<int>(ed->getCurrentTick());
|
||||
auto tick_offset = static_cast<int>(ed->current_tick());
|
||||
std::set<Note> pasted_notes = ed->chart->notesClipboard.paste(tick_offset);
|
||||
|
||||
std::stringstream ss;
|
||||
|
@ -40,7 +40,7 @@ ToggledNotes::ToggledNotes(std::set<Note> n, bool have_been_added) :
|
||||
}
|
||||
|
||||
void ToggledNotes::doAction(EditorState& ed) const {
|
||||
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
|
||||
ed.setPlaybackAndMusicPosition(sf::seconds(ed.seconds_at(notes.begin()->getTiming())));
|
||||
if (have_been_added) {
|
||||
for (auto note : notes) {
|
||||
if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) {
|
||||
@ -57,7 +57,7 @@ void ToggledNotes::doAction(EditorState& ed) const {
|
||||
}
|
||||
|
||||
void ToggledNotes::undoAction(EditorState& ed) const {
|
||||
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
|
||||
ed.setPlaybackAndMusicPosition(sf::seconds(ed.seconds_at(notes.begin()->getTiming())));
|
||||
if (not have_been_added) {
|
||||
for (auto note : notes) {
|
||||
if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) {
|
||||
|
@ -2,74 +2,60 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
LNMarker::LNMarker(std::filesystem::path folder) {
|
||||
triangle_appearance = load_tex_with_prefix<16, 400>(folder, "LN0001_M");
|
||||
triangle_begin_cycle = load_tex_with_prefix<8, 600>(folder, "LN0001_M");
|
||||
triangle_cycle = load_tex_with_prefix<16, 500>(folder, "LN0001_M");
|
||||
|
||||
square_highlight = load_tex_with_prefix<16, 300>(folder, "LN0001_M");
|
||||
square_outline = load_tex_with_prefix<16, 100>(folder, "LN0001_M");
|
||||
square_background = load_tex_with_prefix<16, 200>(folder, "LN0001_M");
|
||||
|
||||
tail_cycle = load_tex_with_prefix<16, 0>(folder, "LN0001_M");
|
||||
LNMarker::LNMarker(std::filesystem::path folder) :
|
||||
triangle_appearance(load_tex_with_prefix<16, 400>(folder, "LN0001_M")),
|
||||
triangle_begin_cycle(load_tex_with_prefix<8, 600>(folder, "LN0001_M")),
|
||||
triangle_cycle(load_tex_with_prefix<16, 500>(folder, "LN0001_M")),
|
||||
square_highlight(load_tex_with_prefix<16, 300>(folder, "LN0001_M")),
|
||||
square_outline(load_tex_with_prefix<16, 100>(folder, "LN0001_M")),
|
||||
square_background(load_tex_with_prefix<16, 200>(folder, "LN0001_M")),
|
||||
tail_cycle(load_tex_with_prefix<16, 0>(folder, "LN0001_M"))
|
||||
{
|
||||
setRepeated<16>(tail_cycle, true);
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
LNMarker::getTriangleTexture(float seconds) {
|
||||
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
|
||||
|
||||
opt_tex_ref LNMarker::triangle_at(int frame) {
|
||||
if (frame >= -16 and frame <= -1) {
|
||||
// approach phase
|
||||
return triangle_appearance.at(static_cast<unsigned long long int>(16 + frame));
|
||||
return triangle_appearance.at(16 + frame);
|
||||
} else if (frame >= 0) {
|
||||
if (frame <= 7) {
|
||||
return triangle_begin_cycle.at(static_cast<unsigned long long int>(frame));
|
||||
return triangle_begin_cycle.at(frame);
|
||||
} else {
|
||||
return triangle_cycle.at(static_cast<unsigned long long int>((frame - 8) % 16));
|
||||
return triangle_cycle.at((frame - 8) % 16);
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getTailTexture(float seconds) {
|
||||
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
|
||||
opt_tex_ref LNMarker::tail_at(int frame) {
|
||||
if (frame >= -16) {
|
||||
return tail_cycle.at(static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
|
||||
return tail_cycle.at((16 + (frame % 16)) % 16);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
LNMarker::getSquareHighlightTexture(float seconds) {
|
||||
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
|
||||
opt_tex_ref LNMarker::highlight_at(int frame) {
|
||||
if (frame >= 0) {
|
||||
return square_highlight.at(
|
||||
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
|
||||
return square_highlight.at((16 + (frame % 16)) % 16);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
LNMarker::getSquareOutlineTexture(float seconds) {
|
||||
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
|
||||
opt_tex_ref LNMarker::outline_at(int frame) {
|
||||
if (frame >= -16) {
|
||||
return square_outline.at(
|
||||
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
|
||||
return square_outline.at((16 + (frame % 16)) % 16);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
LNMarker::getSquareBackgroundTexture(float seconds) {
|
||||
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
|
||||
opt_tex_ref LNMarker::background_at(int frame) {
|
||||
if (frame >= -16) {
|
||||
return square_background.at(
|
||||
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
|
||||
return square_background.at((16 + (frame % 16)) % 16);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
using opt_tex_ref = std::optional<std::reference_wrapper<sf::Texture>>;
|
||||
|
||||
/*
|
||||
* Stores every rotated variant of the long note marker
|
||||
* This approach is absolutely terrible, I should just dig a little bit into the
|
||||
@ -18,15 +20,11 @@ class LNMarker {
|
||||
public:
|
||||
explicit LNMarker(std::filesystem::path folder);
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>> getTriangleTexture(float seconds);
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
getSquareHighlightTexture(float seconds);
|
||||
std::optional<std::reference_wrapper<sf::Texture>> getSquareOutlineTexture(float seconds);
|
||||
std::optional<std::reference_wrapper<sf::Texture>>
|
||||
getSquareBackgroundTexture(float seconds);
|
||||
|
||||
std::optional<std::reference_wrapper<sf::Texture>> getTailTexture(float seconds);
|
||||
opt_tex_ref triangle_at(int frame);
|
||||
opt_tex_ref highlight_at(int frame);
|
||||
opt_tex_ref outline_at(int frame);
|
||||
opt_tex_ref background_at(int frame);
|
||||
opt_tex_ref tail_at(int frame);
|
||||
|
||||
private:
|
||||
std::array<sf::Texture, 16> triangle_appearance;
|
||||
@ -45,7 +43,8 @@ private:
|
||||
const std::filesystem::path& folder,
|
||||
const std::string& prefix,
|
||||
int left_padding = 3,
|
||||
const std::string& extension = ".png") {
|
||||
const std::string& extension = ".png"
|
||||
) {
|
||||
std::array<sf::Texture, number> res;
|
||||
for (int frame = first; frame <= first + number - 1; frame++) {
|
||||
std::stringstream filename;
|
||||
|
10
src/main.cpp
10
src/main.cpp
@ -177,14 +177,14 @@ int main(int argc, char** argv) {
|
||||
// current time
|
||||
editor_state->chart->timeSelection =
|
||||
static_cast<unsigned int>(
|
||||
editor_state->getCurrentTick());
|
||||
editor_state->current_tick());
|
||||
|
||||
// if the start of the timeSelection is
|
||||
// already set
|
||||
} else if (std::holds_alternative<unsigned int>(
|
||||
editor_state->chart->timeSelection)) {
|
||||
auto current_tick =
|
||||
static_cast<int>(editor_state->getCurrentTick());
|
||||
static_cast<int>(editor_state->current_tick());
|
||||
auto selection_start =
|
||||
static_cast<int>(std::get<unsigned int>(
|
||||
editor_state->chart->timeSelection));
|
||||
@ -219,7 +219,7 @@ int main(int argc, char** argv) {
|
||||
// current time
|
||||
editor_state->chart->timeSelection =
|
||||
static_cast<unsigned int>(
|
||||
editor_state->getCurrentTick());
|
||||
editor_state->current_tick());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -443,9 +443,9 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
if (beatTick.shouldPlay) {
|
||||
auto previous_tick = static_cast<int>(editor_state->getTicksAt(
|
||||
auto previous_tick = static_cast<int>(editor_state->ticks_at(
|
||||
editor_state->previousPos.asSeconds()));
|
||||
auto current_tick = static_cast<int>(editor_state->getTicksAt(
|
||||
auto current_tick = static_cast<int>(editor_state->ticks_at(
|
||||
editor_state->playbackPosition.asSeconds()));
|
||||
if (previous_tick / editor_state->getResolution()
|
||||
!= current_tick / editor_state->getResolution()) {
|
||||
|
19
src/time_interval.cpp
Normal file
19
src/time_interval.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "time_interval.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
TimeInterval::TimeInterval(const sf::Time& start, const sf::Time& end) :
|
||||
start(std::min(start, end)),
|
||||
end(std::max(start, end))
|
||||
{};
|
||||
|
||||
// interval union
|
||||
TimeInterval& TimeInterval::operator+=(const TimeInterval& rhs) {
|
||||
start = std::min(start, rhs.start);
|
||||
end = std::max(end, rhs.end);
|
||||
return *this;
|
||||
};
|
||||
|
||||
TimeInterval& TimeInterval::operator+=(const sf::Time& rhs) {
|
||||
return this->operator+=(TimeInterval{rhs, rhs});
|
||||
};
|
26
src/time_interval.hpp
Normal file
26
src/time_interval.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
class TimeInterval {
|
||||
public:
|
||||
TimeInterval() = default;
|
||||
TimeInterval(const sf::Time& start, const sf::Time& end);
|
||||
|
||||
bool operator==(const TimeInterval&) const = default;
|
||||
|
||||
TimeInterval& operator+=(const TimeInterval& rhs);
|
||||
TimeInterval& operator+=(const sf::Time& rhs);
|
||||
|
||||
// passing lhs by value helps optimize chained a+b+c (says cppreference)
|
||||
template<typename T>
|
||||
friend TimeInterval operator+(TimeInterval lhs, const T& rhs) {
|
||||
lhs += rhs;
|
||||
return lhs;
|
||||
};
|
||||
|
||||
sf::Time start;
|
||||
sf::Time end;
|
||||
};
|
@ -143,7 +143,7 @@ void LinearView::update(
|
||||
PixelsToSeconds.transform(static_cast<float>(y)) + 0.5f)));
|
||||
|
||||
auto notes = chart->chart.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks);
|
||||
auto currentLongNote = chart->makeCurrentLongNote();
|
||||
auto currentLongNote = chart->make_current_long_note();
|
||||
if (currentLongNote) {
|
||||
notes.insert(*currentLongNote);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
const std::string texture_file = "textures/edit_textures/game_front_edit_tex_1.tex.png";
|
||||
|
||||
Playfield::Playfield(std::filesystem::path assets_folder) :
|
||||
longNoteMarker(assets_folder / "textures" / "long"),
|
||||
long_note(assets_folder / "textures" / "long"),
|
||||
texture_path(assets_folder / texture_file)
|
||||
{
|
||||
if (!base_texture.loadFromFile(texture_path)) {
|
||||
@ -26,161 +26,156 @@ Playfield::Playfield(std::filesystem::path assets_folder) :
|
||||
note_collision.setTexture(base_texture);
|
||||
note_collision.setTextureRect({576, 0, 192, 192});
|
||||
|
||||
if (!markerLayer.create(400, 400)) {
|
||||
if (!marker_layer.create(400, 400)) {
|
||||
std::cerr << "Unable to create Playfield's markerLayer";
|
||||
throw std::runtime_error("Unable to create Playfield's markerLayer");
|
||||
}
|
||||
markerLayer.setSmooth(true);
|
||||
marker_layer.setSmooth(true);
|
||||
|
||||
if (!longNoteLayer.create(400, 400)) {
|
||||
if (!long_note.layer.create(400, 400)) {
|
||||
std::cerr << "Unable to create Playfield's longNoteLayer";
|
||||
throw std::runtime_error("Unable to create Playfield's longNoteLayer");
|
||||
}
|
||||
longNoteLayer.setSmooth(true);
|
||||
long_note.layer.setSmooth(true);
|
||||
|
||||
LNSquareBackgroud.setTexture(*longNoteMarker.getSquareBackgroundTexture(0));
|
||||
LNSquareOutline.setTexture(*longNoteMarker.getSquareOutlineTexture(0));
|
||||
LNSquareHighlight.setTexture(*longNoteMarker.getSquareHighlightTexture(0));
|
||||
LNTail.setTexture(*longNoteMarker.getTailTexture(0));
|
||||
LNTriangle.setTexture(*longNoteMarker.getTriangleTexture(0));
|
||||
long_note.backgroud.setTexture(*long_note.marker.background_at(0));
|
||||
long_note.outline.setTexture(*long_note.marker.outline_at(0));
|
||||
long_note.highlight.setTexture(*long_note.marker.highlight_at(0));
|
||||
long_note.tail.setTexture(*long_note.marker.tail_at(0));
|
||||
long_note.triangle.setTexture(*long_note.marker.triangle_at(0));
|
||||
}
|
||||
|
||||
void Playfield::resize(unsigned int width) {
|
||||
if (longNoteLayer.getSize() != sf::Vector2u(width, width)) {
|
||||
if (!longNoteLayer.create(width, width)) {
|
||||
if (long_note.layer.getSize() != sf::Vector2u(width, width)) {
|
||||
if (!long_note.layer.create(width, width)) {
|
||||
std::cerr << "Unable to resize Playfield's longNoteLayer";
|
||||
throw std::runtime_error(
|
||||
"Unable to resize Playfield's longNoteLayer");
|
||||
}
|
||||
longNoteLayer.setSmooth(true);
|
||||
long_note.layer.setSmooth(true);
|
||||
}
|
||||
|
||||
longNoteLayer.clear(sf::Color::Transparent);
|
||||
long_note.layer.clear(sf::Color::Transparent);
|
||||
|
||||
if (markerLayer.getSize() != sf::Vector2u(width, width)) {
|
||||
if (!markerLayer.create(width, width)) {
|
||||
if (marker_layer.getSize() != sf::Vector2u(width, width)) {
|
||||
if (!marker_layer.create(width, width)) {
|
||||
std::cerr << "Unable to resize Playfield's markerLayer";
|
||||
throw std::runtime_error(
|
||||
"Unable to resize Playfield's markerLayer");
|
||||
}
|
||||
markerLayer.setSmooth(true);
|
||||
marker_layer.setSmooth(true);
|
||||
}
|
||||
|
||||
markerLayer.clear(sf::Color::Transparent);
|
||||
marker_layer.clear(sf::Color::Transparent);
|
||||
}
|
||||
|
||||
void Playfield::drawLongNote(
|
||||
const Note& note,
|
||||
void Playfield::draw_long_note(
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const float& ticksAtPlaybackPosition,
|
||||
const float& BPM,
|
||||
const int& resolution) {
|
||||
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
|
||||
const better::Timing& timing
|
||||
) {
|
||||
float squareSize = static_cast<float>(long_note.layer.getSize().x) / 4;
|
||||
auto note_time = timing.time_at(note.get_time());
|
||||
auto note_offset = playbackPosition - note_time;
|
||||
auto frame = static_cast<int>(std::floor(note_offset.asSeconds() * 30.f));
|
||||
const auto x = note.get_position().get_x();
|
||||
const auto y = note.get_position().get_y();
|
||||
|
||||
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
|
||||
AffineTransform<float> SecondsToTicks(
|
||||
playbackPosition.asSeconds() - (60.f / BPM),
|
||||
playbackPosition.asSeconds(),
|
||||
ticksAtPlaybackPosition - resolution,
|
||||
ticksAtPlaybackPosition);
|
||||
auto tail_end = timing.time_at(note.get_end());
|
||||
|
||||
float note_offset = SecondsToTicksProportional.backwards_transform(
|
||||
ticksAtPlaybackPosition - note.getTiming());
|
||||
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
|
||||
int x = note.getPos() % 4;
|
||||
int y = note.getPos() / 4;
|
||||
|
||||
float tail_end_in_seconds =
|
||||
SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
|
||||
// float tail_end_offset = playbackPosition.asSeconds() -
|
||||
// tail_end_in_seconds;
|
||||
|
||||
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
|
||||
if (playbackPosition < tail_end) {
|
||||
// Before or During the long note
|
||||
auto tail_tex = longNoteMarker.getTailTexture(note_offset);
|
||||
if (tail_tex) {
|
||||
auto triangle_distance = static_cast<float>((note.getTail_pos() / 4) + 1);
|
||||
|
||||
if (auto tail_tex = long_note.marker.tail_at(frame)) {
|
||||
AffineTransform<float> OffsetToTriangleDistance(
|
||||
0.f,
|
||||
SecondsToTicksProportional.backwards_transform(note.getLength()),
|
||||
triangle_distance,
|
||||
0.f);
|
||||
(tail_end - note_time).asSeconds(),
|
||||
static_cast<float>(note.get_tail_length()),
|
||||
0.f
|
||||
);
|
||||
|
||||
LNTail.setTexture(*tail_tex, true);
|
||||
auto LNTriangle_tex = longNoteMarker.getTriangleTexture(note_offset);
|
||||
if (LNTriangle_tex) {
|
||||
LNTriangle.setTexture(*LNTriangle_tex, true);
|
||||
|
||||
long_note.tail.setTexture(*tail_tex, true);
|
||||
if (auto tex = long_note.marker.triangle_at(frame)) {
|
||||
long_note.triangle.setTexture(*tex, true);
|
||||
}
|
||||
auto LNSquareBackgroud_tex =
|
||||
longNoteMarker.getSquareBackgroundTexture(note_offset);
|
||||
if (LNSquareBackgroud_tex) {
|
||||
LNSquareBackgroud.setTexture(*LNSquareBackgroud_tex, true);
|
||||
if (auto tex = long_note.marker.background_at(frame)) {
|
||||
long_note.backgroud.setTexture(*tex, true);
|
||||
}
|
||||
auto LNSquareOutline_tex = longNoteMarker.getSquareOutlineTexture(note_offset);
|
||||
if (LNSquareOutline_tex) {
|
||||
LNSquareOutline.setTexture(*LNSquareOutline_tex, true);
|
||||
if (auto tex = long_note.marker.outline_at(frame)) {
|
||||
long_note.outline.setTexture(*tex, true);
|
||||
}
|
||||
auto LNSquareHighlight_tex =
|
||||
longNoteMarker.getSquareHighlightTexture(note_offset);
|
||||
if (LNSquareHighlight_tex) {
|
||||
LNSquareHighlight.setTexture(*LNSquareHighlight_tex, true);
|
||||
if (auto tex = long_note.marker.highlight_at(frame)) {
|
||||
long_note.highlight.setTexture(*tex, true);
|
||||
}
|
||||
|
||||
auto rect = LNTail.getTextureRect();
|
||||
auto rect = long_note.tail.getTextureRect();
|
||||
float tail_length_factor;
|
||||
|
||||
if (frame < 8) {
|
||||
// Before the note : tail goes from triangle tip to note edge
|
||||
tail_length_factor =
|
||||
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 1.f);
|
||||
tail_length_factor = std::max(
|
||||
0.f,
|
||||
OffsetToTriangleDistance.clampedTransform(
|
||||
note_offset.asSeconds()
|
||||
) - 1.f
|
||||
);
|
||||
} else {
|
||||
// During the note : tail goes from half of the triangle base to
|
||||
// note edge
|
||||
tail_length_factor =
|
||||
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 0.5f);
|
||||
tail_length_factor = std::max(
|
||||
0.f,
|
||||
OffsetToTriangleDistance.clampedTransform(
|
||||
note_offset.asSeconds()
|
||||
) - 0.5f
|
||||
);
|
||||
}
|
||||
|
||||
rect.height = static_cast<int>(rect.height * tail_length_factor);
|
||||
LNTail.setTextureRect(rect);
|
||||
LNTail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
|
||||
LNTail.setRotation(90.f * ((note.getTail_pos() + 2) % 4));
|
||||
long_note.tail.setTextureRect(rect);
|
||||
long_note.tail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
|
||||
long_note.tail.setRotation(note.get_tail_angle() + 180);
|
||||
|
||||
rect = LNTriangle.getTextureRect();
|
||||
LNTriangle.setOrigin(
|
||||
rect = long_note.triangle.getTextureRect();
|
||||
long_note.triangle.setOrigin(
|
||||
rect.width / 2.f,
|
||||
rect.width * (0.5f + OffsetToTriangleDistance.clampedTransform(note_offset)));
|
||||
LNTriangle.setRotation(90.f * (note.getTail_pos() % 4));
|
||||
rect.width * (
|
||||
0.5f
|
||||
+ OffsetToTriangleDistance.clampedTransform(
|
||||
note_offset.asSeconds()
|
||||
)
|
||||
)
|
||||
);
|
||||
long_note.triangle.setRotation(note.get_tail_angle());
|
||||
|
||||
rect = LNSquareBackgroud.getTextureRect();
|
||||
LNSquareBackgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
LNSquareBackgroud.setRotation(90.f * (note.getTail_pos() % 4));
|
||||
rect = long_note.backgroud.getTextureRect();
|
||||
long_note.backgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
long_note.backgroud.setRotation(note.get_tail_angle());
|
||||
|
||||
rect = LNSquareOutline.getTextureRect();
|
||||
LNSquareOutline.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
LNSquareOutline.setRotation(90.f * (note.getTail_pos() % 4));
|
||||
rect = long_note.outline.getTextureRect();
|
||||
long_note.outline.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
long_note.outline.setRotation(note.get_tail_angle());
|
||||
|
||||
rect = LNSquareHighlight.getTextureRect();
|
||||
LNSquareHighlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
rect = long_note.highlight.getTextureRect();
|
||||
long_note.highlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
|
||||
|
||||
float scale = squareSize / rect.width;
|
||||
LNTail.setScale(scale, scale);
|
||||
LNTriangle.setScale(scale, scale);
|
||||
LNSquareBackgroud.setScale(scale, scale);
|
||||
LNSquareOutline.setScale(scale, scale);
|
||||
LNSquareHighlight.setScale(scale, scale);
|
||||
long_note.tail.setScale(scale, scale);
|
||||
long_note.triangle.setScale(scale, scale);
|
||||
long_note.backgroud.setScale(scale, scale);
|
||||
long_note.outline.setScale(scale, scale);
|
||||
long_note.highlight.setScale(scale, scale);
|
||||
|
||||
LNTail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
LNTriangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
LNSquareBackgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
LNSquareOutline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
LNSquareHighlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
long_note.tail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
long_note.triangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
long_note.backgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
long_note.outline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
long_note.highlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
|
||||
|
||||
longNoteLayer.draw(LNTail);
|
||||
longNoteLayer.draw(LNSquareBackgroud);
|
||||
longNoteLayer.draw(LNSquareOutline);
|
||||
longNoteLayer.draw(LNTriangle);
|
||||
longNoteLayer.draw(LNSquareHighlight);
|
||||
long_note.layer.draw(long_note.tail);
|
||||
long_note.layer.draw(long_note.backgroud);
|
||||
long_note.layer.draw(long_note.outline);
|
||||
long_note.layer.draw(long_note.triangle);
|
||||
long_note.layer.draw(long_note.highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,9 +188,9 @@ void Playfield::drawLongNote(
|
||||
const int& resolution,
|
||||
Marker& marker,
|
||||
MarkerEndingState& markerEndingState) {
|
||||
drawLongNote(note, playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
|
||||
draw_long_note(note, playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
|
||||
|
||||
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
|
||||
float squareSize = static_cast<float>(long_note.layer.getSize().x) / 4;
|
||||
|
||||
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
|
||||
AffineTransform<float> SecondsToTicks(
|
||||
@ -219,10 +214,10 @@ void Playfield::drawLongNote(
|
||||
auto t = marker.getSprite(markerEndingState, note_offset);
|
||||
if (t) {
|
||||
float scale = squareSize / t->get().getSize().x;
|
||||
markerSprite.setTexture(*t, true);
|
||||
markerSprite.setScale(scale, scale);
|
||||
markerSprite.setPosition(x * squareSize, y * squareSize);
|
||||
markerLayer.draw(markerSprite);
|
||||
marker_sprite.setTexture(*t, true);
|
||||
marker_sprite.setScale(scale, scale);
|
||||
marker_sprite.setPosition(x * squareSize, y * squareSize);
|
||||
marker_layer.draw(markerSprite);
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -231,10 +226,10 @@ void Playfield::drawLongNote(
|
||||
auto t = marker.getSprite(markerEndingState, tail_end_offset);
|
||||
if (t) {
|
||||
float scale = squareSize / t->get().getSize().x;
|
||||
markerSprite.setTexture(*t, true);
|
||||
markerSprite.setScale(scale, scale);
|
||||
markerSprite.setPosition(x * squareSize, y * squareSize);
|
||||
markerLayer.draw(markerSprite);
|
||||
marker_sprite.setTexture(*t, true);
|
||||
marker_sprite.setScale(scale, scale);
|
||||
marker_sprite.setPosition(x * squareSize, y * squareSize);
|
||||
marker_layer.draw(markerSprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Graphics/RenderTexture.hpp>
|
||||
#include <imgui-SFML.h>
|
||||
#include <string>
|
||||
|
||||
#include "../better_note.hpp"
|
||||
#include "../better_timing.hpp"
|
||||
#include "../ln_marker.hpp"
|
||||
#include "../marker.hpp"
|
||||
#include "../note.hpp"
|
||||
@ -17,34 +20,39 @@ public:
|
||||
sf::Sprite note_selected;
|
||||
sf::Sprite note_collision;
|
||||
|
||||
sf::RenderTexture markerLayer;
|
||||
sf::Sprite markerSprite;
|
||||
sf::RenderTexture marker_layer;
|
||||
sf::Sprite marker_sprite;
|
||||
|
||||
LNMarker longNoteMarker;
|
||||
sf::RenderTexture longNoteLayer;
|
||||
sf::Sprite LNSquareBackgroud;
|
||||
sf::Sprite LNSquareOutline;
|
||||
sf::Sprite LNSquareHighlight;
|
||||
sf::Sprite LNTail;
|
||||
sf::Sprite LNTriangle;
|
||||
struct LongNote {
|
||||
template<class... Args>
|
||||
LongNote(Args&& ...args) : marker(std::forward<Args>(args)...) {};
|
||||
|
||||
LNMarker marker;
|
||||
sf::RenderTexture layer;
|
||||
sf::Sprite backgroud;
|
||||
sf::Sprite outline;
|
||||
sf::Sprite highlight;
|
||||
sf::Sprite tail;
|
||||
sf::Sprite triangle;
|
||||
};
|
||||
|
||||
LongNote long_note;
|
||||
|
||||
void resize(unsigned int width);
|
||||
|
||||
void drawLongNote(
|
||||
const Note& note,
|
||||
void draw_long_note(
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const float& ticksAtPlaybackPosition,
|
||||
const float& BPM,
|
||||
const int& resolution);
|
||||
const better::Timing& timing
|
||||
);
|
||||
|
||||
void drawLongNote(
|
||||
const Note& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const float& ticksAtPlaybackPosition,
|
||||
const float& BPM,
|
||||
const int& resolution,
|
||||
const better::Timing& timing,
|
||||
Marker& marker,
|
||||
MarkerEndingState& markerEndingState);
|
||||
MarkerEndingState& markerEndingState
|
||||
);
|
||||
|
||||
private:
|
||||
const std::filesystem::path texture_path;
|
||||
|
Loading…
Reference in New Issue
Block a user