From d0158300204de18e469f74ea59d0e85765e51751 Mon Sep 17 00:00:00 2001 From: Stepland <10530295-Buggyroom@users.noreply.gitlab.com> Date: Fri, 25 Mar 2022 02:20:22 +0100 Subject: [PATCH] This is taking so long wtf --- src/better_notes.cpp | 4 + src/better_notes.hpp | 2 + src/better_song.hpp | 5 +- src/chart_state.hpp | 9 +- src/editor_state.cpp | 4 +- src/toolbox.hpp | 18 ++- src/widgets/linear_view.cpp | 300 ++++++++++++++++++------------------ src/widgets/linear_view.hpp | 19 +-- 8 files changed, 186 insertions(+), 175 deletions(-) diff --git a/src/better_notes.cpp b/src/better_notes.cpp index 7001fe6..7e188c9 100644 --- a/src/better_notes.cpp +++ b/src/better_notes.cpp @@ -20,4 +20,8 @@ namespace better { }); return conflicting_note; } + + bool Notes::contains(const Note& note) const { + return find(note) != end(); + } } \ No newline at end of file diff --git a/src/better_notes.hpp b/src/better_notes.hpp index 6430e36..2c20408 100644 --- a/src/better_notes.hpp +++ b/src/better_notes.hpp @@ -14,6 +14,8 @@ namespace better { class Notes: public interval_tree { public: std::pair insert(const Note& note); + /* returns at iterator to a note exactly equal, if found */ const_iterator find(const Note& note) const; + bool contains(const Note& note) const; }; } \ No newline at end of file diff --git a/src/better_song.hpp b/src/better_song.hpp index 4eedee5..6414912 100644 --- a/src/better_song.hpp +++ b/src/better_song.hpp @@ -23,8 +23,9 @@ namespace better { /* Returns true if the given note (assumed to already be part of the - chart) is colliding with ANOTHER note in the chart (this method does - NOT take identical notes into account for collision detection) + chart) is colliding with ANOTHER note in the chart. This means notes + exactly equal to the one passed as an argument are NOT taken into + account. */ bool is_colliding(const better::Note& note); diff --git a/src/chart_state.hpp b/src/chart_state.hpp index 3e697c1..59716d0 100644 --- a/src/chart_state.hpp +++ b/src/chart_state.hpp @@ -40,10 +40,11 @@ struct ChartState { }; /* -Construct a note to be displayed to preview the long note currently being -created. It's basically the same at the real long note being created but -its start time is set to exactly the (given) current beat so the long note -drawing routine can be repurposed as-is for the preview +Construct a note to be displayed on the playfield as a preview of the long note +currently being created. It's basically the same at the real long note being +created but its start time is set to exactly the (given) current beat so the +long note drawing routine of the playfield can be repurposed as-is for the +preview */ better::LongNote make_long_note_dummy( Fraction current_beat, diff --git a/src/editor_state.cpp b/src/editor_state.cpp index c133a65..3356fa5 100644 --- a/src/editor_state.cpp +++ b/src/editor_state.cpp @@ -211,7 +211,7 @@ void EditorState::display_playfield(Marker& marker, MarkerEndingState markerEndi // Display selected notes for (auto const& [_, note] : visibleNotes) { - if (chart_state->selected_notes.find(note) != chart_state->selected_notes.end()) { + if (chart_state->selected_notes.contains(note)) { ImGui::SetCursorPos({ note.get_position().get_x() * squareSize, TitlebarHeight + note.get_position().get_y() * squareSize @@ -507,7 +507,7 @@ void EditorState::display_linear_view() { if (ImGui::Begin("Linear View", &showLinearView, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { if (chart_state) { linear_view.update( - chart_state, + *chart_state, playback_position, getCurrentTick(), song.BPM, diff --git a/src/toolbox.hpp b/src/toolbox.hpp index c79096b..f123eab 100644 --- a/src/toolbox.hpp +++ b/src/toolbox.hpp @@ -34,6 +34,18 @@ namespace Toolbox { std::string toOrdinal(int number); void center(sf::Shape& s); bool editFillColor(const char* label, sf::Shape& s); + + template + void set_origin_normalized(T& s, float x, float y) { + auto bounds = s.getGlobalBounds(); + s.setOrigin(bounds.left+x*bounds.width, bounds.top+y*bounds.height); + } + + template + void set_local_origin_normalized(T& s, float x, float y) { + auto bounds = s.getLocalBounds(); + s.setOrigin(bounds.left+x*bounds.width, bounds.top+y*bounds.height); + } } template @@ -52,11 +64,11 @@ public: a = (high_output - low_output) / (high_input - low_input); b = (high_input * low_output - high_output * low_input) / (high_input - low_input); }; - T transform(T val) { return a * val + b; }; - T clampedTransform(T val) { + T transform(T val) const { return a * val + b; }; + T clampedTransform(T val) const { return transform(std::clamp(val, low_input, high_input)); }; - T backwards_transform(T val) { + T backwards_transform(T val) const { // if we're too close to zero if (std::abs(a) < 10e-10) { throw std::runtime_error( diff --git a/src/widgets/linear_view.cpp b/src/widgets/linear_view.cpp index 9a12f3c..3f45cae 100644 --- a/src/widgets/linear_view.cpp +++ b/src/widgets/linear_view.cpp @@ -1,16 +1,21 @@ #include "linear_view.hpp" +#include #include #include +#include + +#include "../special_numeric_types.hpp" +#include "../toolbox.hpp" +#include "../chart_state.hpp" +#include "../variant_visitor.hpp" + const std::string font_file = "fonts/NotoSans-Medium.ttf"; LinearView::LinearView(std::filesystem::path assets) : - SecondsToTicks(-(60.f / last_BPM) / timeFactor(), 0.f, -last_resolution / timeFactor(), 0), - SecondsToTicksProportional(0.f, (60.f / last_BPM), 0.f, last_resolution), - PixelsToSeconds(-25.f, 75.f, -(60.f / last_BPM) / timeFactor(), 0.f), - PixelsToSecondsProprotional(0.f, 100.f, 0.f, (60.f / last_BPM) / timeFactor()), - PixelsToTicks(-25.f, 75.f, -last_resolution / timeFactor(), 0), + beats_to_pixels(0, 1, cursor_y - 100, cursor_y), + beats_to_pixels_proportional(0, 1, 0, 100), font_path(assets / font_file) { if (!beat_number_font.loadFromFile(font_path)) { @@ -26,7 +31,7 @@ LinearView::LinearView(std::filesystem::path assets) : selection.setOutlineColor(sf::Color(153, 255, 153, 189)); selection.setOutlineThickness(1.f); - note_rect.setFillColor(sf::Color(255, 213, 0, 255)); + tap_note_rect.setFillColor(sf::Color(255, 213, 0, 255)); note_selected.setFillColor(sf::Color(255, 255, 255, 200)); note_selected.setOutlineThickness(1.f); @@ -34,7 +39,6 @@ LinearView::LinearView(std::filesystem::path assets) : note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127)); long_note_rect.setFillColor(sf::Color(255, 90, 0, 223)); - long_note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127)); } void LinearView::resize(unsigned int width, unsigned int height) { @@ -46,180 +50,172 @@ void LinearView::resize(unsigned int width, unsigned int height) { } view.setSmooth(true); } - view.clear(sf::Color::Transparent); } void LinearView::update( - const std::optional& chart, - const sf::Time& playbackPosition, - const float& ticksAtPlaybackPosition, - const float& BPM, - const int& resolution, + const ChartState& chart_state, + const sf::Time& playback_position, const ImVec2& size ) { int x = std::max(140, static_cast(size.x)); int y = std::max(140, static_cast(size.y)); resize(static_cast(x), static_cast(y)); - reloadTransforms(playbackPosition, ticksAtPlaybackPosition, BPM, resolution); - if (chart) { - /* - * Draw the beat lines and numbers - */ - int next_beat_tick = - ((1 + (static_cast(PixelsToTicks.transform(0.f)) + resolution) / resolution) * resolution) - - resolution; - int next_beat = std::max(0, next_beat_tick / resolution); - next_beat_tick = next_beat * resolution; - float next_beat_line_y = - PixelsToTicks.backwards_transform(static_cast(next_beat_tick)); + // Just in case, clamp the beat cursor inside the window, with some margin + cursor_y = std::clamp(cursor_y, 25.f, static_cast(y) - 25.f); - sf::RectangleShape beat_line(sf::Vector2f(static_cast(x) - 80.f, 1.f)); + // Here we compute the range of visible beats from the size of the window + // in pixels, we know by definition that the current beat is exactly at + // cursor_y pixels and we use this fact to compute the rest + const auto beats_before_cursor = beats_to_pixels_proportional.backwards_transform(cursor_y); + const auto beats_after_cursor = beats_to_pixels_proportional.backwards_transform(static_cast(y) - cursor_y); + const auto current_beat = chart_state.chart.timing.beats_at(playback_position); + Fraction first_visible_beat = current_beat - beats_before_cursor; + Fraction last_visible_beat = current_beat + beats_after_cursor; + AffineTransform beats_to_pixels_absolute{first_visible_beat, last_visible_beat, 0, y}; - sf::Text beat_number; - beat_number.setFont(beat_number_font); - beat_number.setCharacterSize(15); - beat_number.setFillColor(sf::Color::White); - std::stringstream ss; - - while (next_beat_line_y < static_cast(y)) { - if (next_beat % 4 == 0) { - beat_line.setFillColor(sf::Color::White); - beat_line.setPosition({50.f, next_beat_line_y}); - view.draw(beat_line); - - ss.str(std::string()); - ss << next_beat / 4; - beat_number.setString(ss.str()); - sf::FloatRect textRect = beat_number.getLocalBounds(); - beat_number.setOrigin( - textRect.left + textRect.width, - textRect.top + textRect.height / 2.f); - beat_number.setPosition({40.f, next_beat_line_y}); - view.draw(beat_number); - - } else { - beat_line.setFillColor(sf::Color(255, 255, 255, 127)); - beat_line.setPosition({50.f, next_beat_line_y}); - view.draw(beat_line); - } - - next_beat_tick += resolution; - next_beat += 1; - next_beat_line_y = - PixelsToTicks.backwards_transform(static_cast(next_beat_tick)); + // Draw the beat lines and numbers + auto next_beat = [&](const auto& first_beat) -> Fraction { + if (first_beat % 1 == 0) { + return first_beat; + } else { + return floor_fraction(first_beat) + 1; } + }(first_visible_beat); - /* - * Draw the notes - */ + auto next_beat_line_y = beats_to_pixels_absolute.backwards_transform(next_beat); - // Size & center the shapes - float note_width = (static_cast(x) - 80.f) / 16.f; - note_rect.setSize({note_width, 6.f}); - Toolbox::center(note_rect); + sf::RectangleShape beat_line(sf::Vector2f(static_cast(x) - 80.f, 1.f)); - note_selected.setSize({note_width + 2.f, 8.f}); - Toolbox::center(note_selected); + sf::Text beat_number; + beat_number.setFont(beat_number_font); + beat_number.setCharacterSize(15); + beat_number.setFillColor(sf::Color::White); + std::stringstream ss; - float collision_zone_size = PixelsToSecondsProprotional.backwards_transform(1.f); - note_collision_zone.setSize( - {(static_cast(x) - 80.f) / 16.f - 2.f, collision_zone_size}); - Toolbox::center(note_collision_zone); + while (next_beat_line_y < y) { + if (next_beat % 4 == 0) { + beat_line.setFillColor(sf::Color::White); + beat_line.setPosition({50.f, static_cast(next_beat_line_y)}); + view.draw(beat_line); - long_note_collision_zone.setSize( - {(static_cast(x) - 80.f) / 16.f - 2.f, collision_zone_size}); - Toolbox::center(long_note_collision_zone); + ss.str(std::string()); + ss << static_cast(next_beat / 4); + beat_number.setString(ss.str()); + sf::FloatRect textRect = beat_number.getLocalBounds(); + beat_number.setOrigin( + textRect.left + textRect.width, + textRect.top + textRect.height / 2.f); + beat_number.setPosition({40.f, static_cast(next_beat_line_y)}); + view.draw(beat_number); - // Find the notes that need to be displayed - int lower_bound_ticks = std::max( - 0, - static_cast(SecondsToTicks.transform(PixelsToSeconds.transform(0.f) - 0.5f))); - int upper_bound_ticks = std::max( - 0, - static_cast(SecondsToTicks.transform( - PixelsToSeconds.transform(static_cast(y)) + 0.5f))); - - auto notes = chart->chart.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks); - auto currentLongNote = chart->make_current_long_note(); - if (currentLongNote) { - notes.insert(*currentLongNote); + } else { + beat_line.setFillColor(sf::Color(255, 255, 255, 127)); + beat_line.setPosition({50.f, static_cast(next_beat_line_y)}); + view.draw(beat_line); } + next_beat += 1; + next_beat_line_y = beats_to_pixels_absolute.backwards_transform(next_beat); + } - for (auto& note : notes) { - float note_x = 50.f + note_width * (note.getPos() + 0.5f); - float note_y = - PixelsToTicks.backwards_transform(static_cast(note.getTiming())); - note_rect.setPosition(note_x, note_y); - note_selected.setPosition(note_x, note_y); - note_collision_zone.setPosition(note_x, note_y); + // Draw the notes - if (note.getLength() != 0) { - float tail_size = PixelsToSecondsProprotional.backwards_transform( - SecondsToTicksProportional.backwards_transform(note.getLength())); - float long_note_collision_size = collision_zone_size + tail_size; - long_note_collision_zone.setSize( - {(static_cast(x) - 80.f) / 16.f - 2.f, collision_zone_size}); - Toolbox::center(long_note_collision_zone); - long_note_collision_zone.setSize( - {(static_cast(x) - 80.f) / 16.f - 2.f, long_note_collision_size}); - long_note_collision_zone.setPosition(note_x, note_y); + // Pre-size & center the shapes that can be + float note_width = (static_cast(x) - 80.f) / 16.f; + float collizion_zone_width = note_width - 2.f; + float tail_width = note_width * 0.75f; + tap_note_rect.setSize({note_width, 6.f}); + Toolbox::center(tap_note_rect); - view.draw(long_note_collision_zone); + note_selected.setSize({note_width + 2.f, 8.f}); + Toolbox::center(note_selected); - float tail_width = .75f * (static_cast(x) - 80.f) / 16.f; - long_note_rect.setSize({tail_width, tail_size}); - sf::FloatRect long_note_bounds = long_note_rect.getLocalBounds(); - long_note_rect.setOrigin( - long_note_bounds.left + long_note_bounds.width / 2.f, - long_note_bounds.top); - long_note_rect.setPosition(note_x, note_y); - - view.draw(long_note_rect); - - } else { - view.draw(note_collision_zone); - } - - view.draw(note_rect); - - if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) { + auto draw_note = VariantVisitor { + [&, this](const better::TapNote& tap_note){ + float note_x = 50.f + note_width * (tap_note.get_position().index() + 0.5f); + float note_y = static_cast(beats_to_pixels_absolute.transform(tap_note.get_time())); + const auto note_seconds = chart_state.chart.timing.time_at(tap_note.get_time()); + const auto first_colliding_beat = chart_state.chart.timing.beats_at(note_seconds - sf::milliseconds(500)); + const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat); + const auto last_colliding_beat = chart_state.chart.timing.beats_at(note_seconds + sf::milliseconds(500)); + const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat); + note_collision_zone.setSize({collizion_zone_width, static_cast(collision_zone_height)}); + Toolbox::set_local_origin_normalized(note_collision_zone, 0.5f, 0.f); + note_collision_zone.setPosition(note_x, static_cast(collision_zone_y)); + this->view.draw(note_collision_zone); + tap_note_rect.setPosition(note_x, note_y); + this->view.draw(tap_note_rect); + if (chart_state.selected_notes.contains(tap_note)) { + note_selected.setPosition(note_x, note_y); view.draw(note_selected); } + }, + [&, this](const better::LongNote& long_note){ + float note_x = 50.f + note_width * (long_note.get_position().index() + 0.5f); + float note_y = static_cast(beats_to_pixels_absolute.transform(long_note.get_time())); + const auto note_start_seconds = chart_state.chart.timing.time_at(long_note.get_time()); + const auto first_colliding_beat = chart_state.chart.timing.beats_at(note_start_seconds - sf::milliseconds(500)); + const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat); + const auto note_end_seconds = chart_state.chart.timing.time_at(long_note.get_end()); + const auto last_colliding_beat = chart_state.chart.timing.beats_at(note_end_seconds + sf::milliseconds(500)); + const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat); + note_collision_zone.setSize({collizion_zone_width, static_cast(collision_zone_height)}); + Toolbox::set_local_origin_normalized(note_collision_zone, 0.5f, 0.f); + note_collision_zone.setPosition(note_x, static_cast(collision_zone_y)); + this->view.draw(note_collision_zone); + const auto tail_height = beats_to_pixels_proportional.transform(long_note.get_duration()); + long_note_rect.setSize({tail_width, static_cast(tail_height)}); + Toolbox::set_local_origin_normalized(long_note_rect, 0.5f, 0.f); + long_note_rect.setPosition(note_x, note_y); + this->view.draw(long_note_rect); + tap_note_rect.setPosition(note_x, note_y); + this->view.draw(tap_note_rect); + if (chart_state.selected_notes.contains(long_note)) { + note_selected.setPosition(note_x, note_y); + this->view.draw(note_selected); + } + }, + }; + + chart_state.chart.notes.in( + first_visible_beat, + last_visible_beat, + [&](const better::Notes::iterator& it){ + it->second.visit(draw_note); } + ); - /* - * Draw the cursor - */ - cursor.setSize({static_cast(x) - 76.f, 4.f}); - view.draw(cursor); + if (chart_state.long_note_being_created.has_value()) { + draw_note(make_long_note(*chart_state.long_note_being_created)); + } - /* - * Draw the timeSelection - */ - selection.setSize({static_cast(x) - 80.f, 0.f}); - if (std::holds_alternative(chart->time_selection)) { - unsigned int ticks = std::get(chart->time_selection); - float selection_y = - PixelsToTicks.backwards_transform(static_cast(ticks)); - if (selection_y > 0.f and selection_y < static_cast(y)) { - selection.setPosition(50.f, selection_y); - view.draw(selection); - } - } else if (std::holds_alternative(chart->time_selection)) { - const auto& ts = std::get(chart->time_selection); - float selection_start_y = - PixelsToTicks.backwards_transform(static_cast(ts.start)); - float selection_end_y = PixelsToTicks.backwards_transform( - static_cast(ts.start + ts.duration)); - if ((selection_start_y > 0.f and selection_start_y < static_cast(y)) - or (selection_end_y > 0.f and selection_end_y < static_cast(y))) { - selection.setSize({static_cast(x) - 80.f, selection_end_y - selection_start_y}); - selection.setPosition(50.f, selection_start_y); - view.draw(selection); - } + // Draw the cursor + cursor.setSize({static_cast(x) - 76.f, 4.f}); + view.draw(cursor); + + // Draw the time selection + selection.setSize({static_cast(x) - 80.f, 0.f}); + if (std::holds_alternative(chart_state.time_selection)) { + unsigned int ticks = std::get(chart->time_selection); + float selection_y = + PixelsToTicks.backwards_transform(static_cast(ticks)); + if (selection_y > 0.f and selection_y < static_cast(y)) { + selection.setPosition(50.f, selection_y); + view.draw(selection); + } + } else if (std::holds_alternative(chart->time_selection)) { + const auto& ts = std::get(chart->time_selection); + float selection_start_y = + PixelsToTicks.backwards_transform(static_cast(ts.start)); + float selection_end_y = PixelsToTicks.backwards_transform( + static_cast(ts.start + ts.duration)); + if ((selection_start_y > 0.f and selection_start_y < static_cast(y)) + or (selection_end_y > 0.f and selection_end_y < static_cast(y))) { + selection.setSize({static_cast(x) - 80.f, selection_end_y - selection_start_y}); + selection.setPosition(50.f, selection_start_y); + view.draw(selection); } } } @@ -232,7 +228,7 @@ void LinearView::setZoom(int newZoom) { void LinearView::displaySettings() { if (ImGui::Begin("Linear View Settings", &shouldDisplaySettings)) { Toolbox::editFillColor("Cursor", cursor); - Toolbox::editFillColor("Note", note_rect); + Toolbox::editFillColor("Note", tap_note_rect); if (Toolbox::editFillColor("Note Collision Zone", note_collision_zone)) { long_note_collision_zone.setFillColor(note_collision_zone.getFillColor()); } diff --git a/src/widgets/linear_view.hpp b/src/widgets/linear_view.hpp index 2a87ebd..948d74d 100644 --- a/src/widgets/linear_view.hpp +++ b/src/widgets/linear_view.hpp @@ -15,12 +15,10 @@ public: sf::RenderTexture view; void update( - const std::optional& chart, + const ChartState& chart_state, const sf::Time& playbackPosition, - const float& ticksAtPlaybackPosition, - const float& BPM, - const int& resolution, - const ImVec2& size); + const ImVec2& size + ); void setZoom(int zoom); void zoom_in() { setZoom(zoom + 1); }; @@ -35,9 +33,8 @@ private: sf::Font beat_number_font; sf::RectangleShape cursor; sf::RectangleShape selection; - sf::RectangleShape note_rect; + sf::RectangleShape tap_note_rect; sf::RectangleShape long_note_rect; - sf::RectangleShape long_note_collision_zone; sf::RectangleShape note_selected; sf::RectangleShape note_collision_zone; @@ -45,11 +42,9 @@ private: int last_resolution = 240; bool shouldReloadTransforms; - AffineTransform SecondsToTicks; - AffineTransform SecondsToTicksProportional; - AffineTransform PixelsToSeconds; - AffineTransform PixelsToSecondsProprotional; - AffineTransform PixelsToTicks; + float cursor_y = 75.f; + AffineTransform beats_to_pixels; + AffineTransform beats_to_pixels_proportional; void resize(unsigned int width, unsigned int height);