1
0
mirror of synced 2024-11-12 01:40:47 +01:00

Update every columns's color up to the current one instead of trying to be efficient

This commit is contained in:
Stepland 2020-05-16 15:13:28 +02:00
parent 1b72bc0a50
commit 114527fb4d
17 changed files with 188 additions and 92 deletions

View File

@ -13,6 +13,8 @@
- Density Graph
- White line
- Cursor
- Fix some columns not being hit
- mouse clicks highlights don't fade away
## Results Screen

View File

@ -76,6 +76,8 @@ sources = [
'src/Screens/MusicSelect/Resources.cpp',
'src/Screens/MusicSelect/SongInfo.hpp',
'src/Screens/MusicSelect/SongInfo.cpp',
'src/Screens/Gameplay/Drawables/Cursor.hpp',
'src/Screens/Gameplay/Drawables/Cursor.cpp',
'src/Screens/Gameplay/Gameplay.hpp',
'src/Screens/Gameplay/Gameplay.cpp',
'src/Screens/Gameplay/PreciseMusic.hpp',

View File

@ -76,18 +76,7 @@ namespace Data {
}
sf::Time Chart::get_last_event_timing() const {
if (notes.empty()) {
return sf::Time::Zero;
} else {
const auto& last_note = *std::max_element(
notes.begin(),
notes.end(),
[](const Data::Note& a, const Data::Note& b) -> bool {
return a.timing+a.duration < b.timing+b.duration;
}
);
return last_note.timing + last_note.duration;
}
return get_time_bounds_from_notes().end;
}
TimeBounds Data::Chart::get_time_bounds_from_notes() const {

View File

@ -17,7 +17,7 @@ namespace Data {
std::set<Note> notes;
std::size_t resolution;
// get the time at which the very last scorable event happens
// (i.e. including long note releases)
// (i.e. note tap or long note release)
sf::Time get_last_event_timing() const;
// get the time interval covered by the notes, including offset zero
TimeBounds get_time_bounds_from_notes() const;

View File

@ -41,8 +41,10 @@ namespace Data {
return Judgement::Great;
} else if (delta_abs < sf::milliseconds(162)) {
return Judgement::Good;
} else {
} else if (delta_abs < sf::milliseconds(533)) {
return Judgement::Poor;
} else {
return Judgement::Miss;
}
}
}

View File

@ -108,6 +108,7 @@ namespace Data {
if (m.openFromFile(*song.full_audio_path())) {
time_bounds += {sf::Time::Zero, m.getDuration()};
}
time_bounds.end += sf::seconds(1);
return time_bounds;
}

View File

@ -55,11 +55,11 @@ namespace Data {
const Data::Song& song;
const std::string& difficulty;
// get the total play time for this chart :
// if there's no audio and no notes, one second starting at sf::Time::Zero
// else the start bounds is either zero or the first note if for some reason it happens before 0s
// Get the total play interval for this chart :
// if there's no audio and no notes, returns a one second inteval starting at sf::Time::Zero
// otherwise the start bound is either zero or the first note if for some reason it happens before 0s
// and the end bound is either the end of the song of the last timing event if a note or the
// release of a long note happen after the audio ends
// release of a long note happens after the audio ends
TimeBounds get_time_bounds() const;
bool operator==(const SongDifficulty &other) const {

View File

@ -36,7 +36,7 @@ namespace Drawables {
);
m_vertex_array.insert(m_vertex_array.end(), bar.begin(), bar.end());
}
target.draw(m_vertex_array.data(), m_vertex_array.size(), sf::Quads);
target.draw(m_vertex_array.data(), m_vertex_array.size(), sf::Quads, states);
}
std::array<sf::Vertex, 4> make_solid_quad(const sf::FloatRect& rect, const sf::Color& color) {

View File

@ -56,8 +56,14 @@ namespace Drawables {
115.f
};
for (auto &&note : chart.notes) {
auto index = static_cast<unsigned int>(ticks_to_column.transform(note.timing.asSeconds()));
auto index = static_cast<std::size_t>(ticks_to_column.transform(note.timing.asSeconds()));
d.at(index) += 1;
if (note.duration > sf::Time::Zero) {
auto long_note_end_index = static_cast<std::size_t>(
ticks_to_column.transform((note.timing+note.duration).asSeconds())
);
d.at(long_note_end_index) += 1;
}
}
std::replace_if(
d.begin(),

View File

@ -2,10 +2,11 @@
#include <algorithm>
#include <imgui/imgui.h>
#include <imgui-sfml/imgui-SFML.h>
#include <SFML/System/Time.hpp>
namespace Drawables {
DensityLineGrade judgement_to_density_line_grade(Data::Judgement judge) {
switch (judge) {
case Data::Judgement::Perfect:
@ -52,56 +53,11 @@ namespace Drawables {
}
}
Toolkit::AffineTransform<float> get_seconds_to_column_transform_from_notes(const Data::Chart& chart) {
auto time_bounds = chart.get_time_bounds_from_notes();
// only one timing point
if (time_bounds.start == time_bounds.end) {
// give one extra second
return Toolkit::AffineTransform<float>(
time_bounds.start.asSeconds(),
(time_bounds.start + sf::seconds(1)).asSeconds(),
0.f,
115.f
);
} else {
// at least two timing points
return Toolkit::AffineTransform<float>(
time_bounds.start.asSeconds(),
time_bounds.end.asSeconds(),
0.f,
115.f
);
}
}
Toolkit::AffineTransform<float> get_seconds_to_column_transform(const Data::SongDifficulty& sd) {
auto chart = sd.song.get_chart(sd.difficulty);
if (not chart) {
throw std::invalid_argument("Song "+sd.song.title+" has no '"+sd.difficulty+"' chart");
}
// no notes, no need to return something useful
if (chart->notes.empty()) {
return Toolkit::AffineTransform<float>(0.f, 1.f, 0.f, 115.f);
}
// no audio
if (not sd.song.audio) {
return get_seconds_to_column_transform_from_notes(*chart);
}
sf::Music m;
// can't open audio
if (not m.openFromFile(*sd.song.full_audio_path())) {
return get_seconds_to_column_transform_from_notes(*chart);
}
// both notes and audio exist
auto time_bounds = chart->get_time_bounds_from_notes();
auto bounds = sd.get_time_bounds();
return Toolkit::AffineTransform<float>(
time_bounds.start.asSeconds(),
sf::microseconds(
std::max(
time_bounds.end.asMicroseconds(),
m.getDuration().asMicroseconds()
)
).asSeconds(),
bounds.start.asSeconds(),
bounds.end.asSeconds(),
0.f,
115.f
);
@ -148,19 +104,21 @@ namespace Drawables {
for (size_t i = 0; i < index_of_first_vertex_of_each_column.size(); i++) {
m_densities.at(i).first_vertex = std::next(it, index_of_first_vertex_of_each_column.at(i));
}
first_non_played_density = m_densities.begin();
}
void GradedDensityGraph::draw(sf::RenderTarget& target, sf::RenderStates states) const {
states.transform *= getTransform();
target.draw(&m_vertex_array[0], m_vertex_array.size(), sf::Quads, states);
if (debug) {
draw_debug();
}
}
void GradedDensityGraph::update(const sf::Time& music_time) {
const auto float_column = m_seconds_to_column.transform(music_time.asSeconds());
const auto current_column = static_cast<std::size_t>(float_column);
const auto current_column_it = std::next(m_densities.begin(), current_column);
for (auto it = first_non_played_density; it != current_column_it; ++it) {
for (auto it = m_densities.begin(); it != current_column_it; ++it) {
auto color = grade_to_color(it->grade);
auto next = std::next(it);
std::vector<sf::Vertex>::iterator next_vertex_it;
@ -173,7 +131,6 @@ namespace Drawables {
vertex_it->color = color;
}
}
first_non_played_density = current_column_it;
}
void GradedDensityGraph::update_grades(const Data::Judgement& judge, const sf::Time& timing) {
@ -183,11 +140,31 @@ namespace Drawables {
current_grade = merge_grades(current_grade, judgement_to_density_line_grade(judge));
}
}
sf::FloatRect GradedDensityGraph::getLocalBounds() const {
return sf::FloatRect({0, 0}, {(115*5)-1, 39});
}
namespace Toolkit {
template<>
void set_origin_normalized(Drawables::GradedDensityGraph& s, float x, float y) {
s.setOrigin(x*574.f, y*39.f);
sf::FloatRect GradedDensityGraph::getGlobalBounds() const {
return getTransform().transformRect(getLocalBounds());
}
void Drawables::GradedDensityGraph::draw_debug() const {
if (ImGui::Begin("GradedDensityGraph")) {
auto orig_x = ImGui::GetCursorPosX();
auto pos = ImGui::GetCursorPos();
for (size_t line = 0; line < m_densities.size(); line++) {
const auto& graded_density = m_densities.at(line);
auto color = grade_to_color(graded_density.grade);
for (size_t column = 0; column < graded_density.density; column++) {
ImGui::SetCursorPos(pos);
ImGui::DrawRectFilled({{0,0}, {4,4}}, color);
pos.x += 5;
}
pos.x = orig_x;
pos.y += 5;
}
ImGui::Dummy({8*5, 115*5});
}
ImGui::End();
}
}

View File

@ -9,7 +9,7 @@
#include "../Data/GradedNote.hpp"
#include "../Data/Song.hpp"
#include "../Toolkit/AffineTransform.hpp"
#include "../Toolkit/Cache.hpp"
#include "../Toolkit/Debuggable.hpp"
#include "../Toolkit/SFMLHelpers.hpp"
#include "DensityGraph.hpp"
@ -35,27 +35,24 @@ namespace Drawables {
std::vector<sf::Vertex>::iterator first_vertex;
};
// helper functions for GradedDensityGraph
Toolkit::AffineTransform<float> get_seconds_to_column_transform_from_notes(const Data::Chart& chart);
// helper function for GradedDensityGraph
Toolkit::AffineTransform<float> get_seconds_to_column_transform(const Data::SongDifficulty& sd);
class GradedDensityGraph : public sf::Drawable, public sf::Transformable {
class GradedDensityGraph : public sf::Drawable, public sf::Transformable, public Toolkit::Debuggable {
public:
explicit GradedDensityGraph(const DensityGraph& density_graph, const Data::SongDifficulty& sd);
// Set verticies colors for density columns that have already been played
void update(const sf::Time& music_time);
// Update stored grades according to the recieved judgement
void update_grades(const Data::Judgement& judge, const sf::Time& timing);
sf::FloatRect getLocalBounds() const;
sf::FloatRect getGlobalBounds() const;
void draw_debug() const;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::array<GradedDensity, 115> m_densities;
std::vector<sf::Vertex> m_vertex_array;
std::array<GradedDensity, 115>::iterator first_non_played_density;
Toolkit::AffineTransform<float> m_seconds_to_column;
};
}
namespace Toolkit {
template<>
void set_origin_normalized(Drawables::GradedDensityGraph& s, float x, float y);
}

View File

@ -0,0 +1,37 @@
#include "Cursor.hpp"
namespace Drawables {
Cursor::Cursor() {
m_vertex_array = {
sf::Vertex{
sf::Vector2f(1.f, 0.f),
sf::Color::White
},
sf::Vertex{
sf::Vector2f(1.f, 1.f),
sf::Color::White
},
sf::Vertex{
sf::Vector2f(0.f, 1.f),
sf::Color::Transparent
},
sf::Vertex{
sf::Vector2f(0.f, 0.f),
sf::Color::Transparent
}
};
}
sf::FloatRect Cursor::getLocalBounds() const {
return sf::FloatRect{{0.f,0.f}, {1.f,1.f}};
}
sf::FloatRect Cursor::getGlobalBounds() const {
return getTransform().transformRect(getLocalBounds());
}
void Cursor::draw(sf::RenderTarget& target, sf::RenderStates states) const {
states.transform *= getTransform();
target.draw(&m_vertex_array[0], m_vertex_array.size(), sf::Quads, states);
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <array>
#include <SFML/Graphics.hpp>
namespace Drawables {
// Small thingy that scrolls atop the graded density graph to indicate how far we are in the song
class Cursor : public sf::Drawable, public sf::Transformable {
public:
Cursor();
sf::FloatRect getLocalBounds() const;
sf::FloatRect getGlobalBounds() const;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::array<sf::Vertex, 4> m_vertex_array;
};
}

View File

@ -154,13 +154,20 @@ namespace Gameplay {
window.draw(line);
// Density Graph
Toolkit::set_origin_normalized(graded_density_graph, 0.5f, 1.f);
Toolkit::set_local_origin_normalized(graded_density_graph, 0.5f, 1.f);
graded_density_graph.setScale(get_screen_width()/768.f, get_screen_width()/768.f);
graded_density_graph.setPosition(get_screen_width()*0.5f,423.f/768.f*get_screen_width());
window.draw(graded_density_graph);
// Cursor on the density graph
Toolkit::set_local_origin_normalized(cursor, 1.f, 0.f);
auto bounds = graded_density_graph.getGlobalBounds();
cursor.setScale(bounds.height, bounds.height);
cursor.setPosition(
bounds.left+music_time_to_progression.transform(music_time.asSeconds())*bounds.width,
bounds.top
);
window.draw(cursor);
// Draw Combo
@ -448,6 +455,7 @@ namespace Gameplay {
switch (*key) {
case sf::Keyboard::F12:
debug = not debug;
graded_density_graph.debug = not graded_density_graph.debug;
break;
default:
break;
@ -487,6 +495,12 @@ namespace Gameplay {
score.update(judgement);
graded_density_graph.update_grades(judgement, note.timing);
if (Data::judgement_breaks_combo(judgement)) {
// If we've broken combo at the begining of a long we also missed the end
if (note.duration > sf::Time::Zero) {
note.long_release = Data::TimedJudgement{sf::Time::Zero, Data::Judgement::Miss};
score.update(Data::Judgement::Miss);
graded_density_graph.update_grades(Data::Judgement::Miss, note.timing+note.duration);
}
combo = 0;
} else {
combo++;
@ -538,6 +552,11 @@ namespace Gameplay {
note.tap_judgement = timed_judgement;
score.update(Data::Judgement::Miss);
graded_density_graph.update_grades(Data::Judgement::Miss, note.timing);
if (note.duration > sf::Time::Zero) {
note.long_release = timed_judgement;
score.update(Data::Judgement::Miss);
graded_density_graph.update_grades(Data::Judgement::Miss, note.timing+note.duration);
}
combo = 0;
}
}
@ -550,7 +569,7 @@ namespace Gameplay {
(note.duration > sf::Time::Zero)
and (note.timing + note.duration < music_time)
and note.tap_judgement.has_value()
and note.tap_judgement->judgement != Data::Judgement::Miss
and not Data::judgement_breaks_combo(note.tap_judgement->judgement)
and (not note.long_release.has_value())
) {
auto timed_judgement = Data::TimedJudgement{sf::Time::Zero, Data::Judgement::Perfect};

View File

@ -22,6 +22,7 @@
#include "AbstractMusic.hpp"
#include "Resources.hpp"
#include "TimedEventsQueue.hpp"
#include "Drawables/Cursor.hpp"
namespace Gameplay {
class Screen : public Toolkit::Debuggable, public HoldsResources {
@ -49,7 +50,7 @@ namespace Gameplay {
const Resources::LNMarker& ln_marker;
std::unique_ptr<AbstractMusic> music;
Drawables::GradedDensityGraph graded_density_graph;
std::array<sf::Vertex, 4> cursor;
Drawables::Cursor cursor;
// maps music time to [0, 1]
Toolkit::AffineTransform<float> music_time_to_progression;

29
test/imgui-sfml-demo.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <imgui/imgui.h>
#include <imgui-sfml/imgui-SFML.h>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>
int main() {
sf::RenderWindow window(sf::VideoMode(640, 480), "imgui-sfml-demo");
window.setVerticalSyncEnabled(true);
ImGui::SFML::Init(window);
window.resetGLStates(); // call it if you only draw ImGui. Otherwise not needed.
sf::Clock deltaClock;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
ImGui::SFML::ProcessEvent(event);
if (event.type == sf::Event::Closed) {
window.close();
}
}
ImGui::SFML::Update(window, deltaClock.restart());
window.clear(sf::Color::Transparent); // fill background with color
ImGui::ShowDemoWindow();
ImGui::SFML::Render(window);
window.display();
}
ImGui::SFML::Shutdown();
}

View File

@ -1,5 +1,21 @@
test_files = []
imgui_demo = executable(
'imgui_demo.out',
[
'imgui-sfml-demo.cpp',
'../include/imgui/imgui.cpp',
'../include/imgui/imgui_demo.cpp',
'../include/imgui/imgui_draw.cpp',
'../include/imgui/imgui_widgets.cpp',
'../include/imgui-sfml/imgui-SFML.cpp'
],
dependencies : dependencies,
include_directories: inc
)
test('Able to build imgui demo', imgui_demo)
foreach test_file : test_files
test_executable = executable(
test_file+'.out',