This is taking so long wtf

This commit is contained in:
Stepland 2022-03-25 02:20:22 +01:00
parent 8ce71368bd
commit d015830020
8 changed files with 186 additions and 175 deletions

View File

@ -20,4 +20,8 @@ namespace better {
});
return conflicting_note;
}
bool Notes::contains(const Note& note) const {
return find(note) != end();
}
}

View File

@ -14,6 +14,8 @@ namespace better {
class Notes: public interval_tree<Fraction, Note> {
public:
std::pair<iterator, bool> 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;
};
}

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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<class T>
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<class T>
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<typename T>
@ -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(

View File

@ -1,16 +1,21 @@
#include "linear_view.hpp"
#include <functional>
#include <iostream>
#include <variant>
#include <SFML/System/Time.hpp>
#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<ChartState>& 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<int>(size.x));
int y = std::max(140, static_cast<int>(size.y));
resize(static_cast<unsigned int>(x), static_cast<unsigned int>(y));
reloadTransforms(playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
if (chart) {
/*
* Draw the beat lines and numbers
*/
int next_beat_tick =
((1 + (static_cast<int>(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<float>(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<float>(y) - 25.f);
sf::RectangleShape beat_line(sf::Vector2f(static_cast<float>(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<float>(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<Fraction> 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<float>(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<float>(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<float>(x) - 80.f) / 16.f;
note_rect.setSize({note_width, 6.f});
Toolbox::center(note_rect);
sf::RectangleShape beat_line(sf::Vector2f(static_cast<float>(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<float>(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<float>(next_beat_line_y)});
view.draw(beat_line);
long_note_collision_zone.setSize(
{(static_cast<float>(x) - 80.f) / 16.f - 2.f, collision_zone_size});
Toolbox::center(long_note_collision_zone);
ss.str(std::string());
ss << static_cast<int>(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<float>(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<int>(SecondsToTicks.transform(PixelsToSeconds.transform(0.f) - 0.5f)));
int upper_bound_ticks = std::max(
0,
static_cast<int>(SecondsToTicks.transform(
PixelsToSeconds.transform(static_cast<float>(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<float>(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<float>(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<float>(x) - 80.f) / 16.f - 2.f, collision_zone_size});
Toolbox::center(long_note_collision_zone);
long_note_collision_zone.setSize(
{(static_cast<float>(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<float>(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<float>(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<float>(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<float>(collision_zone_height)});
Toolbox::set_local_origin_normalized(note_collision_zone, 0.5f, 0.f);
note_collision_zone.setPosition(note_x, static_cast<float>(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<float>(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<float>(collision_zone_height)});
Toolbox::set_local_origin_normalized(note_collision_zone, 0.5f, 0.f);
note_collision_zone.setPosition(note_x, static_cast<float>(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<float>(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<float>(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<float>(x) - 80.f, 0.f});
if (std::holds_alternative<unsigned int>(chart->time_selection)) {
unsigned int ticks = std::get<unsigned int>(chart->time_selection);
float selection_y =
PixelsToTicks.backwards_transform(static_cast<float>(ticks));
if (selection_y > 0.f and selection_y < static_cast<float>(y)) {
selection.setPosition(50.f, selection_y);
view.draw(selection);
}
} else if (std::holds_alternative<TimeSelection>(chart->time_selection)) {
const auto& ts = std::get<TimeSelection>(chart->time_selection);
float selection_start_y =
PixelsToTicks.backwards_transform(static_cast<float>(ts.start));
float selection_end_y = PixelsToTicks.backwards_transform(
static_cast<float>(ts.start + ts.duration));
if ((selection_start_y > 0.f and selection_start_y < static_cast<float>(y))
or (selection_end_y > 0.f and selection_end_y < static_cast<float>(y))) {
selection.setSize({static_cast<float>(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<float>(x) - 76.f, 4.f});
view.draw(cursor);
// Draw the time selection
selection.setSize({static_cast<float>(x) - 80.f, 0.f});
if (std::holds_alternative<unsigned int>(chart_state.time_selection)) {
unsigned int ticks = std::get<unsigned int>(chart->time_selection);
float selection_y =
PixelsToTicks.backwards_transform(static_cast<float>(ticks));
if (selection_y > 0.f and selection_y < static_cast<float>(y)) {
selection.setPosition(50.f, selection_y);
view.draw(selection);
}
} else if (std::holds_alternative<TimeSelection>(chart->time_selection)) {
const auto& ts = std::get<TimeSelection>(chart->time_selection);
float selection_start_y =
PixelsToTicks.backwards_transform(static_cast<float>(ts.start));
float selection_end_y = PixelsToTicks.backwards_transform(
static_cast<float>(ts.start + ts.duration));
if ((selection_start_y > 0.f and selection_start_y < static_cast<float>(y))
or (selection_end_y > 0.f and selection_end_y < static_cast<float>(y))) {
selection.setSize({static_cast<float>(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());
}

View File

@ -15,12 +15,10 @@ public:
sf::RenderTexture view;
void update(
const std::optional<ChartState>& 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<float> SecondsToTicks;
AffineTransform<float> SecondsToTicksProportional;
AffineTransform<float> PixelsToSeconds;
AffineTransform<float> PixelsToSecondsProprotional;
AffineTransform<float> PixelsToTicks;
float cursor_y = 75.f;
AffineTransform<Fraction> beats_to_pixels;
AffineTransform<Fraction> beats_to_pixels_proportional;
void resize(unsigned int width, unsigned int height);