diff --git a/crogine/src/ecs/components/Drawable2D.cpp b/crogine/src/ecs/components/Drawable2D.cpp index 23f532a63..3ea62c7f2 100644 --- a/crogine/src/ecs/components/Drawable2D.cpp +++ b/crogine/src/ecs/components/Drawable2D.cpp @@ -179,6 +179,14 @@ void Drawable2D::updateLocalBounds() m_localBounds.width = xExtremes.second->position.x - m_localBounds.left; m_localBounds.height = yExtremes.second->position.y - m_localBounds.bottom; + //if one or other is empty (say we're drawing a perfectly + //straight line) add a small amount so we don't get culled + if (m_localBounds.width + m_localBounds.height != 0) + { + m_localBounds.width += 0.001f; + m_localBounds.height += 0.001f; + } + m_updateBufferData = true; //tells the system we have new data to upload } diff --git a/samples/golf/src/golf/StatsState.cpp b/samples/golf/src/golf/StatsState.cpp index 5051d5434..5d5e4a77e 100644 --- a/samples/golf/src/golf/StatsState.cpp +++ b/samples/golf/src/golf/StatsState.cpp @@ -80,6 +80,8 @@ namespace { constexpr float PieRadius = 48.f; constexpr std::int32_t PieBaseColour = CD32::BlueLight; + constexpr glm::vec2 PerformanceBoardArea = glm::vec2(416.f, 88.f); + constexpr float PerformanceVerticalOffset = 2.f; //each graph offset by this to reduce overlap //indices into the colour palette for each hole graph constexpr std::array PerformanceColours = @@ -1082,6 +1084,46 @@ void StatsState::createPerformanceTab(cro::Entity parent, const cro::SpriteSheet entity.addComponent().active = true; entity.getComponent().function = buttonCallback; buttonEnt.getComponent().addChild(entity.getComponent()); + + + //create and attach empty ents to hold the graphs, updated by refreshPerformanceTab() + float yPos = 144.f; + for (auto i = 0u; i < 18; ++i) + { + entity = m_scene.createEntity(); + entity.addComponent().setPosition({ 28.f, yPos, 0.2f }); + entity.addComponent().setPrimitiveType(GL_LINE_STRIP); + entity.getComponent().setVertexData( + { + cro::Vertex2D(glm::vec2(0.f), CD32::Colours[PerformanceColours[i]]), + cro::Vertex2D(glm::vec2(PerformanceBoardArea.x, 0.f), CD32::Colours[PerformanceColours[i]]), + }); + + performanceEnt.getComponent().addChild(entity.getComponent()); + m_graphEntities[i] = entity; + + yPos -= PerformanceVerticalOffset; + + if (i == 8) + { + yPos -= 90.f; //gap in front/back area + } + } + + //and the personal best text in the middle + const auto& infoFont = m_sharedData.sharedResources->fonts.get(FontID::Info); + entity = m_scene.createEntity(); + entity.addComponent().setPosition({ centre, 176.f, 0.2f }); + entity.addComponent(); + entity.addComponent(infoFont).setString("0 Records Found. No Personal Best"); + entity.getComponent().setFillColour(TextNormalColour); + entity.getComponent().setCharacterSize(InfoTextSize); + centreText(entity); + m_tabNodes[TabID::Performance].getComponent().addChild(entity.getComponent()); + + m_personalBestEntity = entity; + + refreshPerformanceTab(true); } void StatsState::createHistoryTab(cro::Entity parent) @@ -1349,7 +1391,72 @@ void StatsState::activateTab(std::int32_t tabID) void StatsState::refreshPerformanceTab(bool newProfile) { - //TODO quit if profiles empty + if (m_profileData.empty()) + { + return; + } + + if (newProfile) + { + if (!m_profileDB.open(m_profileData[m_profileIndex].dbPath)) + { + LogE << "Failed opening " << m_profileData[m_profileIndex].dbPath << std::endl; + return; + } + } + + constexpr std::size_t MaxPoints = 52; //8px apart + + //set a max number of entries in a given period + //then average the number of results if greater + //TODO apply the time range + //TODO apply the CPU filter + auto records = m_profileDB.getCourseRecords(m_courseIndex); + if (records.empty()) + { + //reset the graph + for (auto e : m_graphEntities) + { + e.getComponent().getVertexData().clear(); + } + m_personalBestEntity.getComponent().setString("No Records Found."); + centreText(m_personalBestEntity); + return; + } + const auto stride = std::max(std::size_t(1), records.size() / MaxPoints); + //TODO this only shows up to half the results if, say, the record count is ~100 + //really we want to take a floating point stride then alternate it by average + //eg 1/2 1/2 over the course of the record collection + + + //TODO build the vert arrays async, then check the results + //in the process() loop and apply them when found + + const auto PointCount = std::min(MaxPoints, records.size()); + const float HorizontalPixelStride = PerformanceBoardArea.x / PointCount; + //there's a 12 stroke limit, but each graph is also offset by 2px + const float VerticalPixelStride = (PerformanceBoardArea.y / 12.f) - PerformanceVerticalOffset; + std::vector verts; + for (auto j = 0; j < 18; ++j) + { + for (auto i = 0u; i < PointCount; i += stride) + { + auto score = records[i].holeScores[j]; + verts.emplace_back(glm::vec2(i * HorizontalPixelStride, (score * VerticalPixelStride) + (j * PerformanceVerticalOffset)), CD32::Colours[PerformanceColours[j]]); + } + + + auto score = records.back().holeScores[j]; + verts.emplace_back(glm::vec2(PerformanceBoardArea.x, (score * VerticalPixelStride) + (j * PerformanceVerticalOffset)), CD32::Colours[PerformanceColours[j]]); + + m_graphEntities[j].getComponent().setVertexData(verts); + verts.clear(); + } + + //TODO apply the personal best text to centre of the layout, along with record count + //auto personalBest = m_profileDB.getPersonalBest(m_courseIndex); //hm this is for each hole, not the course + m_personalBestEntity.getComponent().setString(std::to_string(records.size()) + " Records Available"); + centreText(m_personalBestEntity); } void StatsState::quitState() diff --git a/samples/golf/src/golf/StatsState.hpp b/samples/golf/src/golf/StatsState.hpp index 12cd0c01e..46bc6ef99 100644 --- a/samples/golf/src/golf/StatsState.hpp +++ b/samples/golf/src/golf/StatsState.hpp @@ -30,6 +30,7 @@ source distribution. #pragma once #include "../StateIDs.hpp" +#include "../sqlite/ProfileDB.hpp" #include #include @@ -148,6 +149,10 @@ class StatsState final : public cro::State enum {Week, Month, Year, Count}; }; std::int32_t m_dateRange = DateRange::Week; + std::array m_graphEntities; + cro::Entity m_personalBestEntity; + + ProfileDB m_profileDB; void buildScene(); void parseCourseData();