Update every columns's color up to the current one instead of trying to be efficient
This commit is contained in:
parent
1b72bc0a50
commit
114527fb4d
2
TODO.md
2
TODO.md
@ -13,6 +13,8 @@
|
||||
- Density Graph
|
||||
- White line
|
||||
- Cursor
|
||||
- Fix some columns not being hit
|
||||
- mouse clicks highlights don't fade away
|
||||
|
||||
## Results Screen
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -56,8 +56,14 @@ namespace Drawables {
|
||||
115.f
|
||||
};
|
||||
for (auto &¬e : 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(),
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
37
src/Screens/Gameplay/Drawables/Cursor.cpp
Normal file
37
src/Screens/Gameplay/Drawables/Cursor.cpp
Normal 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);
|
||||
}
|
||||
}
|
18
src/Screens/Gameplay/Drawables/Cursor.hpp
Normal file
18
src/Screens/Gameplay/Drawables/Cursor.hpp
Normal 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;
|
||||
};
|
||||
}
|
@ -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};
|
||||
|
@ -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
29
test/imgui-sfml-demo.cpp
Normal 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();
|
||||
}
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user