From 4a7842928755dfddd6d279a2bab36911517fcc58 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 10:08:21 +0100 Subject: [PATCH 001/233] bump verison number --- libsocial/include/Social.hpp | 4 ++-- samples/golf/golf.aps | Bin 294872 -> 295048 bytes samples/golf/golf.rc | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libsocial/include/Social.hpp b/libsocial/include/Social.hpp index abf97f359..1d83683d5 100644 --- a/libsocial/include/Social.hpp +++ b/libsocial/include/Social.hpp @@ -45,8 +45,8 @@ source distribution. //(terrain vertex data and materials changed 1100 -> 1110) //(player avatar data format changed 1110 -> 1120) //(ball started sending wind effect 1120 -> 1124) -static constexpr std::uint16_t CURRENT_VER = 1124; -static const std::string StringVer("1.12.5"); +static constexpr std::uint16_t CURRENT_VER = 1130; +static const std::string StringVer("1.13.0"); class Social final diff --git a/samples/golf/golf.aps b/samples/golf/golf.aps index 3c2d267c89f168d835a1dd4f80942eef5801075d..13c08d959e3202fa6761c51bfb4a37d2124f6f27 100644 GIT binary patch delta 475 zcmb76O)mps5T1E=^dc%8L=&n-f{3b*Nc1SZmL^h(WJQ{`hcWrndf7knfITLgPqQk2xd;B12eTcK{BCd%tFP~Q(4Q> zlSa;7FO)4)Un-h*b}h1K+4-UonT$`&>gkG6D&(xwh3K`hjCk)o;@S8=m%UXrqCo<^ zLp8jT0TT*Pk*-4uvS7*AAqfWLz=n01mBsO=OHdTFC9?(*SQOWWym*GFNr=M)%t|UP zw+txBUJfkpR=w@I+P(r^?;s(;lRBtcPsB%XhKer=D8{yNQ;{%zNk|_zVnmhagdiC>gAwAU6^n3 zIiFwod?2g!ZBR@* KuM)p(QRo*SCWVIp delta 320 zcmXYoze@sf7{=fCd+!;T5IF=<-DwJmgn~mu^y7xcpvH)3liIXH0lIy3!rNwBkiWR|NHw~>DI z4-kFt2k&US=!tE}LKsFFf%h(QQzn$53Kj1}8MX|%F$iUZjM6uV+UGL~KmZUhArBiA zswF5ussD08dpPb~wCD`;99Xo19XHU`Rr%%fmqmKam-Q39U({kFS-XPQY+4pQg%2e$ z$CgbQQByhY&>|0PniIW=d`m&#z@{bdA(m862_Vax4(XyB=VOO7QCnbJ5;yohny*_t gh;*0VBJHI3FS>d)S!+t}*LkZ+1?8ho>@;Zp9}hELfB*mh diff --git a/samples/golf/golf.rc b/samples/golf/golf.rc index 47493abf7..879fba15b 100644 --- a/samples/golf/golf.rc +++ b/samples/golf/golf.rc @@ -61,8 +61,8 @@ IDI_ICON1 ICON "icon.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,12,2,0 - PRODUCTVERSION 1,12,2,0 + FILEVERSION 1,13,0,0 + PRODUCTVERSION 1,13,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -79,12 +79,12 @@ BEGIN BEGIN VALUE "CompanyName", "Trederia" VALUE "FileDescription", "Super Video Golf" - VALUE "FileVersion", "1.12.2.0" + VALUE "FileVersion", "1.13.0.0" VALUE "InternalName", "golf.exe" VALUE "LegalCopyright", "Copyright (C) 2023 Trederia Games" VALUE "OriginalFilename", "golf.exe" VALUE "ProductName", "Super Video Golf" - VALUE "ProductVersion", "1.12.2.0" + VALUE "ProductVersion", "1.13.0.0" END END BLOCK "VarFileInfo" From bd262f8749dc779c24eae34de4efa88dfe9a9f21 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 12:21:16 +0100 Subject: [PATCH 002/233] begin refactoring of golf rule handling --- samples/golf/golf.vcxproj | 4 + samples/golf/golf.vcxproj.filters | 12 ++ samples/golf/src/golf/CMakeLists.txt | 2 + .../src/golf/server/GolfDefaultDirector.cpp | 99 ++++++++++ .../src/golf/server/GolfDefaultDirector.hpp | 55 ++++++ samples/golf/src/golf/server/GolfDirector.hpp | 42 +++++ .../golf/src/golf/server/ServerGolfRules.cpp | 136 ++++++++++++++ .../golf/src/golf/server/ServerGolfState.cpp | 173 +++++++----------- .../golf/src/golf/server/ServerGolfState.hpp | 3 + 9 files changed, 419 insertions(+), 107 deletions(-) create mode 100644 samples/golf/src/golf/server/GolfDefaultDirector.cpp create mode 100644 samples/golf/src/golf/server/GolfDefaultDirector.hpp create mode 100644 samples/golf/src/golf/server/GolfDirector.hpp create mode 100644 samples/golf/src/golf/server/ServerGolfRules.cpp diff --git a/samples/golf/golf.vcxproj b/samples/golf/golf.vcxproj index 21d612878..33aa82f2e 100644 --- a/samples/golf/golf.vcxproj +++ b/samples/golf/golf.vcxproj @@ -505,9 +505,11 @@ + + @@ -615,6 +617,8 @@ + + diff --git a/samples/golf/golf.vcxproj.filters b/samples/golf/golf.vcxproj.filters index a6e630112..5c3ff46d8 100644 --- a/samples/golf/golf.vcxproj.filters +++ b/samples/golf/golf.vcxproj.filters @@ -357,6 +357,12 @@ Source Files\sqlite + + Source Files\golf\server\directors + + + Source Files\golf\server + @@ -719,6 +725,12 @@ Header Files\sqlite + + Header Files\golf\server\directors + + + Header Files\golf\server\directors + diff --git a/samples/golf/src/golf/CMakeLists.txt b/samples/golf/src/golf/CMakeLists.txt index 7ba6e128c..32e842c0f 100644 --- a/samples/golf/src/golf/CMakeLists.txt +++ b/samples/golf/src/golf/CMakeLists.txt @@ -79,10 +79,12 @@ set(GOLF_SRC ${PROJECT_DIR}/golf/VatFile.cpp ${PROJECT_DIR}/golf/Weather.cpp + #${PROJECT_DIR}/golf/server/GolfDefaultDirector.cpp ${PROJECT_DIR}/golf/server/EightballDirector.cpp ${PROJECT_DIR}/golf/server/NineballDirector.cpp ${PROJECT_DIR}/golf/server/Server.cpp ${PROJECT_DIR}/golf/server/ServerBilliardsState.cpp + ${PROJECT_DIR}/golf/server/ServerGolfRules.cpp ${PROJECT_DIR}/golf/server/ServerGolfState.cpp ${PROJECT_DIR}/golf/server/ServerLobbyState.cpp ${PROJECT_DIR}/golf/server/SnookerDirector.cpp) diff --git a/samples/golf/src/golf/server/GolfDefaultDirector.cpp b/samples/golf/src/golf/server/GolfDefaultDirector.cpp new file mode 100644 index 000000000..ed4c810f2 --- /dev/null +++ b/samples/golf/src/golf/server/GolfDefaultDirector.cpp @@ -0,0 +1,99 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#include "GolfDefaultDirector.hpp" + +GolfDefaultDirector::GolfDefaultDirector(sv::SharedData& sd, std::vector& pi) + : m_sharedData (sd), + m_playerInfo (pi) +{ + +} + +//public +void GolfDefaultDirector::handleMessage(const cro::Message& msg) +{ + +} + +void GolfDefaultDirector::summariseHole() +{ + //if (m_playerInfo.size() > 1) + //{ + // auto sortData = m_playerInfo; + // std::sort(sortData.begin(), sortData.end(), + // [&](const PlayerStatus& a, const PlayerStatus& b) + // { + // return a.holeScore[m_currentHole] < b.holeScore[m_currentHole]; + // }); + + // //only score if no player tied + // if (sortData[0].holeScore[m_currentHole] != sortData[1].holeScore[m_currentHole]) + // { + // auto player = std::find_if(m_playerInfo.begin(), m_playerInfo.end(), [&sortData](const PlayerStatus& p) + // { + // return p.client == sortData[0].client && p.player == sortData[0].player; + // }); + + // player->matchWins++; + // player->skins += m_skinsPot; + // m_skinsPot = 1; + + // //check the match score and end the game if this is the mode we're in + // if (m_sharedData.scoreType == ScoreType::Match) + // { + // sortData[0].matchWins++; + // std::sort(sortData.begin(), sortData.end(), + // [&](const PlayerStatus& a, const PlayerStatus& b) + // { + // return a.matchWins > b.matchWins; + // }); + + + // auto remainingHoles = static_cast(m_holeData.size()) - (m_currentHole + 1); + // //if second place can't beat first even if they win all the holes it's game over + // if (sortData[1].matchWins + remainingHoles < sortData[0].matchWins) + // { + // gameFinished = true; + // } + // } + + // //send notification packet to clients that player won the hole + // std::uint16_t data = (player->client << 8) | player->player; + // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + // } + // else + // { + // m_skinsPot++; + + // std::uint16_t data = 0xff00 | m_skinsPot; + // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + // } + //} +} \ No newline at end of file diff --git a/samples/golf/src/golf/server/GolfDefaultDirector.hpp b/samples/golf/src/golf/server/GolfDefaultDirector.hpp new file mode 100644 index 000000000..257e615b6 --- /dev/null +++ b/samples/golf/src/golf/server/GolfDefaultDirector.hpp @@ -0,0 +1,55 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#pragma once + +#include "GolfDirector.hpp" + +#include + +namespace sv +{ + struct SharedData; +} +struct PlayerStatus; + +//default game rules of stroke/match/skins +class GolfDefaultDirector final : public GolfDirector +{ +public: + GolfDefaultDirector(sv::SharedData&, std::vector&); + + void handleMessage(const cro::Message&) override; + void summariseHole() override; + +private: + sv::SharedData& m_sharedData; + std::vector& m_playerInfo; +}; + diff --git a/samples/golf/src/golf/server/GolfDirector.hpp b/samples/golf/src/golf/server/GolfDirector.hpp new file mode 100644 index 000000000..d66cded2f --- /dev/null +++ b/samples/golf/src/golf/server/GolfDirector.hpp @@ -0,0 +1,42 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#pragma once + +#include + +class GolfDirector : public cro::Director +{ +public: + virtual void handleMessage(const cro::Message&) = 0; + virtual void summariseHole() = 0; + +private: + +}; \ No newline at end of file diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp new file mode 100644 index 000000000..f84736bae --- /dev/null +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -0,0 +1,136 @@ +#include "../PacketIDs.hpp" + +#include "ServerMessages.hpp" +#include "ServerGolfState.hpp" + +using namespace sv; +void GolfState::handleDefaultRules(const GolfBallEvent& data) +{ + if (data.type == GolfBallEvent::TurnEnded) + { + //if match/skins play check if our score is even with anyone holed already and forfeit + if (m_sharedData.scoreType != ScoreType::Stroke) + { + if (m_playerInfo[0].holeScore[m_currentHole] >= m_currentBest) + { + m_playerInfo[0].distanceToHole = 0; + m_playerInfo[0].holeScore[m_currentHole]++; + } + } + } + else if (data.type == GolfBallEvent::Holed) + { + //if we're playing match play or skins then + //anyone who has a worse score has already lost + //so set them to finished. + if (m_sharedData.scoreType != ScoreType::Stroke) + { + //eliminate anyone who can't beat this score + for (auto i = 1u; i < m_playerInfo.size(); ++i) + { + if ((m_playerInfo[i].holeScore[m_currentHole]) >= + m_playerInfo[0].holeScore[m_currentHole]) + { + if (m_playerInfo[i].distanceToHole > 0) //not already holed + { + m_playerInfo[i].distanceToHole = 0.f; + m_playerInfo[i].holeScore[m_currentHole]++; //therefore they lose a stroke and don't draw + } + } + } + + //if this is the second hole and it has the same as the current best + //force a draw by eliminating anyone who can't beat it + if (m_playerInfo[0].holeScore[m_currentHole] == m_currentBest) + { + for (auto i = 1u; i < m_playerInfo.size(); ++i) + { + if ((m_playerInfo[i].holeScore[m_currentHole] + 1) >= + m_currentBest) + { + if (m_playerInfo[i].distanceToHole > 0) + { + m_playerInfo[i].distanceToHole = 0.f; + m_playerInfo[i].holeScore[m_currentHole] = std::min(m_currentBest, std::uint8_t(m_playerInfo[i].holeScore[m_currentHole] + 1)); + } + } + } + } + } + } + else if (data.type == GolfBallEvent::Gimme) + { + //if match/skins play check if our score is even with anyone holed already and forfeit + if (m_sharedData.scoreType != ScoreType::Stroke) + { + for (auto i = 1u; i < m_playerInfo.size(); ++i) + { + if (m_playerInfo[i].distanceToHole == 0 + && m_playerInfo[i].holeScore[m_currentHole] < m_playerInfo[0].holeScore[m_currentHole]) + { + m_playerInfo[0].distanceToHole = 0; + } + } + } + } +} + +bool GolfState::summariseDefaultRules() +{ + bool gameFinished = false; + + if (m_playerInfo.size() > 1) + { + auto sortData = m_playerInfo; + std::sort(sortData.begin(), sortData.end(), + [&](const PlayerStatus& a, const PlayerStatus& b) + { + return a.holeScore[m_currentHole] < b.holeScore[m_currentHole]; + }); + + //only score if no player tied + if (sortData[0].holeScore[m_currentHole] != sortData[1].holeScore[m_currentHole]) + { + auto player = std::find_if(m_playerInfo.begin(), m_playerInfo.end(), [&sortData](const PlayerStatus& p) + { + return p.client == sortData[0].client && p.player == sortData[0].player; + }); + + player->matchWins++; + player->skins += m_skinsPot; + m_skinsPot = 1; + + //check the match score and end the game if this is the mode we're in + if (m_sharedData.scoreType == ScoreType::Match) + { + sortData[0].matchWins++; + std::sort(sortData.begin(), sortData.end(), + [&](const PlayerStatus& a, const PlayerStatus& b) + { + return a.matchWins > b.matchWins; + }); + + + auto remainingHoles = static_cast(m_holeData.size()) - (m_currentHole + 1); + //if second place can't beat first even if they win all the holes it's game over + if (sortData[1].matchWins + remainingHoles < sortData[0].matchWins) + { + gameFinished = true; + } + } + + //send notification packet to clients that player won the hole + std::uint16_t data = (player->client << 8) | player->player; + m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + } + else + { + m_skinsPot++; + + std::uint16_t data = 0xff00 | m_skinsPot; + m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + } + } + + return gameFinished; +} \ No newline at end of file diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 934aa22c2..2f62c3064 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -159,14 +159,11 @@ void GolfState::handleMessage(const cro::Message& msg) } m_playerInfo[0].terrain = data.terrain; - //if match/skins play check if our score is even with anyone holed already and forfeit - if (m_sharedData.scoreType != ScoreType::Stroke) + switch (m_sharedData.scoreType) { - if (m_playerInfo[0].holeScore[m_currentHole] >= m_currentBest) - { - m_playerInfo[0].distanceToHole = 0; - m_playerInfo[0].holeScore[m_currentHole]++; - } + default: + handleDefaultRules(data); + break; } setNextPlayer(); @@ -179,44 +176,12 @@ void GolfState::handleMessage(const cro::Message& msg) m_playerInfo[0].distanceToHole = 0.f; m_playerInfo[0].terrain = data.terrain; - - //if we're playing match play or skins then - //anyone who has a worse score has already lost - //so set them to finished. - if (m_sharedData.scoreType != ScoreType::Stroke) + switch (m_sharedData.scoreType) { - //eliminate anyone who can't beat this score - for (auto i = 1u; i < m_playerInfo.size(); ++i) - { - if ((m_playerInfo[i].holeScore[m_currentHole]) >= - m_playerInfo[0].holeScore[m_currentHole]) - { - if (m_playerInfo[i].distanceToHole > 0) //not already holed - { - m_playerInfo[i].distanceToHole = 0.f; - m_playerInfo[i].holeScore[m_currentHole]++; //therefore they lose a stroke and don't draw - } - } - } - - //if this is the second hole and it has the same as the current best - //force a draw by eliminating anyone who can't beat it - if (m_playerInfo[0].holeScore[m_currentHole] == m_currentBest) - { - for (auto i = 1u; i < m_playerInfo.size(); ++i) - { - if ((m_playerInfo[i].holeScore[m_currentHole]+1) >= - m_currentBest) - { - if (m_playerInfo[i].distanceToHole > 0) - { - m_playerInfo[i].distanceToHole = 0.f; - m_playerInfo[i].holeScore[m_currentHole] = std::min(m_currentBest, std::uint8_t(m_playerInfo[i].holeScore[m_currentHole] + 1)); - } - } - } - } - } + default: + handleDefaultRules(data); + break; + } if (m_playerInfo[0].holeScore[m_currentHole] < m_currentBest) { @@ -258,17 +223,11 @@ void GolfState::handleMessage(const cro::Message& msg) std::uint16_t inf = (m_playerInfo[0].client << 8) | m_playerInfo[0].player; m_sharedData.host.broadcastPacket(PacketID::Gimme, inf, net::NetFlag::Reliable); - //if match/skins play check if our score is even with anyone holed already and forfeit - if (m_sharedData.scoreType != ScoreType::Stroke) + switch (m_sharedData.scoreType) { - for (auto i = 1u; i < m_playerInfo.size(); ++i) - { - if (m_playerInfo[i].distanceToHole == 0 - && m_playerInfo[i].holeScore[m_currentHole] < m_playerInfo[0].holeScore[m_currentHole]) - { - m_playerInfo[0].distanceToHole = 0; - } - } + default: + handleDefaultRules(data); + break; } } } @@ -686,61 +645,61 @@ void GolfState::setNextHole() m_currentBest = MaxStrokes; m_scene.getSystem()->forceWindChange(); - bool gameFinished = false; //update player skins/match scores - if (m_playerInfo.size() > 1) - { - auto sortData = m_playerInfo; - std::sort(sortData.begin(), sortData.end(), - [&](const PlayerStatus& a, const PlayerStatus& b) - { - return a.holeScore[m_currentHole] < b.holeScore[m_currentHole]; - }); - - //only score if no player tied - if (sortData[0].holeScore[m_currentHole] != sortData[1].holeScore[m_currentHole]) - { - auto player = std::find_if(m_playerInfo.begin(), m_playerInfo.end(), [&sortData](const PlayerStatus& p) - { - return p.client == sortData[0].client && p.player == sortData[0].player; - }); - - player->matchWins++; - player->skins += m_skinsPot; - m_skinsPot = 1; - - //check the match score and end the game if this is the mode we're in - if (m_sharedData.scoreType == ScoreType::Match) - { - sortData[0].matchWins++; - std::sort(sortData.begin(), sortData.end(), - [&](const PlayerStatus& a, const PlayerStatus& b) - { - return a.matchWins > b.matchWins; - }); - - - auto remainingHoles = static_cast(m_holeData.size()) - (m_currentHole + 1); - //if second place can't beat first even if they win all the holes it's game over - if(sortData[1].matchWins + remainingHoles < sortData[0].matchWins) - { - gameFinished = true; - } - } - - //send notification packet to clients that player won the hole - std::uint16_t data = (player->client << 8) | player->player; - m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - } - else - { - m_skinsPot++; - - std::uint16_t data = 0xff00 | m_skinsPot; - m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - } - } + auto gameFinished = summariseDefaultRules(); + //if (m_playerInfo.size() > 1) + //{ + // auto sortData = m_playerInfo; + // std::sort(sortData.begin(), sortData.end(), + // [&](const PlayerStatus& a, const PlayerStatus& b) + // { + // return a.holeScore[m_currentHole] < b.holeScore[m_currentHole]; + // }); + + // //only score if no player tied + // if (sortData[0].holeScore[m_currentHole] != sortData[1].holeScore[m_currentHole]) + // { + // auto player = std::find_if(m_playerInfo.begin(), m_playerInfo.end(), [&sortData](const PlayerStatus& p) + // { + // return p.client == sortData[0].client && p.player == sortData[0].player; + // }); + + // player->matchWins++; + // player->skins += m_skinsPot; + // m_skinsPot = 1; + + // //check the match score and end the game if this is the mode we're in + // if (m_sharedData.scoreType == ScoreType::Match) + // { + // sortData[0].matchWins++; + // std::sort(sortData.begin(), sortData.end(), + // [&](const PlayerStatus& a, const PlayerStatus& b) + // { + // return a.matchWins > b.matchWins; + // }); + + + // auto remainingHoles = static_cast(m_holeData.size()) - (m_currentHole + 1); + // //if second place can't beat first even if they win all the holes it's game over + // if(sortData[1].matchWins + remainingHoles < sortData[0].matchWins) + // { + // gameFinished = true; + // } + // } + + // //send notification packet to clients that player won the hole + // std::uint16_t data = (player->client << 8) | player->player; + // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + // } + // else + // { + // m_skinsPot++; + + // std::uint16_t data = 0xff00 | m_skinsPot; + // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + // } + //} //broadcast all scores to make sure everyone is up to date for (auto& player : m_playerInfo) diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 578a4782d..375723d09 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -85,6 +85,9 @@ namespace sv void initScene(); void buildWorld(); + void handleDefaultRules(const struct GolfBallEvent&); + bool summariseDefaultRules(); + void doServerCommand(const net::NetEvent&); }; } \ No newline at end of file From 82075a81446e40779ee3d4d1ebe682fc3527764b Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 14:10:04 +0100 Subject: [PATCH 003/233] begin new lobby layout --- samples/golf/golf.vcxproj | 3 - samples/golf/golf.vcxproj.filters | 9 -- samples/golf/src/golf/MenuCreation.cpp | 150 ++++-------------- samples/golf/src/golf/MenuState.hpp | 2 + .../golf/src/golf/server/ServerGolfState.cpp | 53 +------ 5 files changed, 37 insertions(+), 180 deletions(-) diff --git a/samples/golf/golf.vcxproj b/samples/golf/golf.vcxproj index 33aa82f2e..58ac97d4e 100644 --- a/samples/golf/golf.vcxproj +++ b/samples/golf/golf.vcxproj @@ -505,7 +505,6 @@ - @@ -617,8 +616,6 @@ - - diff --git a/samples/golf/golf.vcxproj.filters b/samples/golf/golf.vcxproj.filters index 5c3ff46d8..5d5a6eaa8 100644 --- a/samples/golf/golf.vcxproj.filters +++ b/samples/golf/golf.vcxproj.filters @@ -357,9 +357,6 @@ Source Files\sqlite - - Source Files\golf\server\directors - Source Files\golf\server @@ -725,12 +722,6 @@ Header Files\sqlite - - Header Files\golf\server\directors - - - Header Files\golf\server\directors - diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index b17aa3f51..f715e1606 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1420,6 +1420,9 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st cro::SpriteSheet spriteSheet; spriteSheet.loadFromFile("assets/golf/sprites/lobby_menu.spt", m_resources.textures); + cro::SpriteSheet spriteSheetV2; + spriteSheetV2.loadFromFile("assets/golf/sprites/lobby_menu_v2.spt", m_resources.textures); + //title auto entity = m_uiScene.createEntity(); entity.addComponent(); @@ -1438,79 +1441,16 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity = m_uiScene.createEntity(); entity.addComponent(); entity.addComponent(); - auto bgSprite = spriteSheet.getSprite("background"); + entity.addComponent() = spriteSheetV2.getSprite("background"); entity.addComponent().ID = CommandID::Menu::UIElement; - entity.addComponent().relativePosition = LobbyBackgroundPosition; + entity.addComponent().relativePosition = { 0.5f, 0.5f }; + entity.getComponent().absolutePosition = { 0.f, 10.f }; entity.getComponent().depth = -0.2f; - entity.getComponent().resizeCallback = - [&, bgSprite](cro::Entity e) - { - auto baseSize = glm::vec2(cro::App::getWindow().getSize()) / m_viewScale; - constexpr float EdgeOffset = 50.f; //this much from outside before splitting - - e.getComponent().setTexture(bgSprite.getTexture()); - auto bounds = bgSprite.getTextureBounds(); - auto rect = bgSprite.getTextureRectNormalised(); - - //how much bigger to get either side in wider views - float expansion = std::min(100.f, std::floor((baseSize.x - bounds.width) / 2.f)); - //only needs > 0 really but this gives a little leeway - expansion = (baseSize.x - bounds.width > 10) ? expansion : 0.f; - float edgeOffsetNorm = (EdgeOffset / bgSprite.getTexture()->getSize().x); - - bounds.width += expansion * 2.f; - - e.getComponent().setVertexData( - { - cro::Vertex2D(glm::vec2(0.f, bounds.height), glm::vec2(rect.left, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(0.f), glm::vec2(rect.left, rect.bottom)), - - cro::Vertex2D(glm::vec2(EdgeOffset, bounds.height), glm::vec2(rect.left + edgeOffsetNorm, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(EdgeOffset, 0.f), glm::vec2(rect.left + edgeOffsetNorm, rect.bottom)), - cro::Vertex2D(glm::vec2(EdgeOffset + expansion, bounds.height), glm::vec2(rect.left + edgeOffsetNorm, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(EdgeOffset + expansion, 0.f), glm::vec2(rect.left + edgeOffsetNorm, rect.bottom)), - - - cro::Vertex2D(glm::vec2(bounds.width - EdgeOffset - expansion, bounds.height), glm::vec2((rect.left + rect.width) - edgeOffsetNorm, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(bounds.width - EdgeOffset - expansion, 0.f), glm::vec2((rect.left + rect.width) - edgeOffsetNorm, rect.bottom)), - cro::Vertex2D(glm::vec2(bounds.width - EdgeOffset, bounds.height), glm::vec2((rect.left + rect.width) - edgeOffsetNorm, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(bounds.width - EdgeOffset, 0.f), glm::vec2((rect.left + rect.width) - edgeOffsetNorm, rect.bottom)), - - - cro::Vertex2D(glm::vec2(bounds.width, bounds.height), glm::vec2(rect.left + rect.width, rect.bottom + rect.height)), - cro::Vertex2D(glm::vec2(bounds.width, 0.f), glm::vec2(rect.left + rect.width, rect.bottom)) - }); - - e.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_lobbyExpansion = expansion; - - cro::Command cmd; - cmd.targetFlags = CommandID::Menu::LobbyText; - cmd.action = [expansion](cro::Entity e, float) - { - //set cropping area - if (e.hasComponent()) - { - auto bounds = cro::Text::getLocalBounds(e); - bounds.width = std::min(bounds.width, MinLobbyCropWidth + expansion); - e.getComponent().setCroppingArea(bounds); - } - - - //set offset if on right - if (auto pos = e.getComponent().getPosition(); pos.x > LobbyTextSpacing) - { - pos.x = LobbyTextSpacing + expansion + 1.f; - e.getComponent().setPosition(pos); - } - }; - m_uiScene.getSystem()->sendCommand(cmd); - }; - entity.getComponent().resizeCallback(entity); - entity.getComponent().updateLocalBounds(); - bounds = entity.getComponent().getLocalBounds(); + bounds = entity.getComponent().getTextureBounds(); + entity.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); menuTransform.addChild(entity.getComponent()); + m_menuEntities[MenuID::LobbyBackground] = entity; auto bgEnt = entity; #ifdef USE_GNS @@ -1519,7 +1459,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ 100.f, 0.f, 0.2f }); entity.addComponent(); - entity.addComponent(labelFont);// .setString(scoreStr); + entity.addComponent(labelFont); entity.getComponent().setCharacterSize(LabelTextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setShadowColour(LeaderboardTextDark); @@ -1535,20 +1475,19 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st auto pos = e.getComponent().getPosition(); pos.x -= 20.f * dt; - pos.y = 17.f; + pos.y = 23.f; pos.z = 0.3f; - static constexpr float Offset = 150.f; + static constexpr float Offset = 232.f; const auto bgWidth = bounds.width; if (pos.x < -scrollBounds.width + Offset) { pos.x = bgWidth; - pos.x -= Offset; } e.getComponent().setPosition(pos); - cro::FloatRect cropping = { -pos.x + Offset, -16.f, (bgWidth) - (Offset * 2.f), 18.f }; + cro::FloatRect cropping = { -pos.x + Offset, -16.f, (bgWidth) - (Offset + 10.f), 18.f }; e.getComponent().setCroppingArea(cropping); } }; @@ -1556,19 +1495,18 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st m_lobbyWindowEntities[LobbyEntityID::CourseTicker] = entity; #endif - auto textResizeCallback = + /*auto textResizeCallback = [&,bgEnt](cro::Entity e) { e.getComponent().setPosition({ bgEnt.getComponent().getOrigin().x , e.getComponent().absolutePosition.y, e.getComponent().depth }); - }; + };*/ //display the score type entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 159.f, 0.1f }); + entity.addComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { bounds.width / 2.f, 159.f }; + entity.addComponent().absolutePosition = { 366.f, 210.f }; entity.getComponent().depth = 0.1f; - entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent(font).setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString(ScoreTypes[m_sharedData.scoreType]); @@ -1579,11 +1517,10 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //and score description entity = m_uiScene.createEntity(); entity.addComponent().setOrigin({ 74.f, 0.f }); - entity.getComponent().setPosition({(bounds.width / 2.f), 140.f, 0.1f}); + entity.getComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { bounds.width / 2.f, 140.f }; + entity.addComponent().absolutePosition = { 366.f, 190.f }; entity.getComponent().depth = 0.1f; - entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString(RuleDescriptions[m_sharedData.scoreType]); @@ -1593,38 +1530,23 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //gimme radius entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 85.f, 0.1f }); + entity.addComponent(); entity.addComponent(); entity.addComponent(font).setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setString("Gimme Radius"); - entity.addComponent().absolutePosition = { bounds.width / 2.f, 85.f }; - entity.getComponent().depth = 0.1f; - entity.getComponent().resizeCallback = textResizeCallback; - entity.addComponent().ID = CommandID::Menu::UIElement; - centreText(entity); - bgEnt.getComponent().addChild(entity.getComponent()); - - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ (bounds.width / 2.f), 66.f, 0.1f }); - entity.addComponent(); - entity.addComponent(smallFont).setCharacterSize(InfoTextSize); - entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString(GimmeString[m_sharedData.gimmeRadius]); - entity.addComponent().absolutePosition = { bounds.width / 2.f, 66.f }; + entity.addComponent().absolutePosition = {366.f, 100.f }; entity.getComponent().depth = 0.1f; - entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent().ID = CommandID::Menu::GimmeDesc | CommandID::Menu::UIElement; centreText(entity); bgEnt.getComponent().addChild(entity.getComponent()); entity = m_uiScene.createEntity(); entity.addComponent().setOrigin({ 74.f, 0.f }); - entity.getComponent().setPosition({ (bounds.width / 2.f), 50.f, 0.1f }); + entity.getComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { bounds.width / 2.f, 50.f }; + entity.addComponent().absolutePosition = { 366.f, 80.f }; entity.getComponent().depth = 0.1f; - entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString("The ball is considered holed\nwhen it's within this radius"); @@ -1644,12 +1566,12 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //displays the thumbnails for the selected course entity = m_uiScene.createEntity(); - entity.addComponent(); + entity.addComponent().setScale({ 0.f, 0.f }); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("thumbnail_window"); entity.addComponent().absolutePosition = { bounds.width / 2.f, bounds.height / 2.f }; entity.getComponent().depth = 1.2f; - entity.getComponent().resizeCallback = textResizeCallback; + //entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent().ID = CommandID::Menu::UIElement; bounds = entity.getComponent().getTextureBounds(); entity.getComponent().setOrigin({ std::floor(bounds.width / 2.f), std::floor(bounds.height / 2.f) }); @@ -1702,7 +1624,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //background for course info entity = m_uiScene.createEntity(); - entity.addComponent(); + entity.addComponent().setScale({ 0.f, 0.f }); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("course_desc"); entity.addComponent().ID = CommandID::Menu::UIElement; @@ -2471,8 +2393,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { -62.f, 59.f }; - buttonEnt.getComponent().relativePosition = LobbyBackgroundPosition; + buttonEnt.addComponent().absolutePosition = { 366.f-85.f, 206.f }; buttonEnt.getComponent().depth = 0.01f; auto bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2489,7 +2410,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); buttonEnt = m_uiScene.createEntity(); @@ -2499,8 +2420,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 63.f, 59.f }; - buttonEnt.getComponent().relativePosition = LobbyBackgroundPosition; + buttonEnt.addComponent().absolutePosition = { 366.f+82.f, 206.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2517,7 +2437,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); //choose gimme radius @@ -2528,8 +2448,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { -62.f, -15.f }; - buttonEnt.getComponent().relativePosition = LobbyBackgroundPosition; + buttonEnt.addComponent().absolutePosition = { 366.f - 85.f, 95.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2546,7 +2465,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); buttonEnt = m_uiScene.createEntity(); @@ -2556,8 +2475,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 63.f, -15.f }; - buttonEnt.getComponent().relativePosition = LobbyBackgroundPosition; + buttonEnt.addComponent().absolutePosition = { 366.f + 82.f, 95.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2574,7 +2492,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index 3ed18fca1..3021c37cc 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -95,6 +95,8 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co Dummy, Main, Avatar, Join, Lobby, ProfileFlyout, ConfirmQuit, + + LobbyBackground, Count }; diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 2f62c3064..1039da33b 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -648,58 +648,7 @@ void GolfState::setNextHole() //update player skins/match scores auto gameFinished = summariseDefaultRules(); - //if (m_playerInfo.size() > 1) - //{ - // auto sortData = m_playerInfo; - // std::sort(sortData.begin(), sortData.end(), - // [&](const PlayerStatus& a, const PlayerStatus& b) - // { - // return a.holeScore[m_currentHole] < b.holeScore[m_currentHole]; - // }); - - // //only score if no player tied - // if (sortData[0].holeScore[m_currentHole] != sortData[1].holeScore[m_currentHole]) - // { - // auto player = std::find_if(m_playerInfo.begin(), m_playerInfo.end(), [&sortData](const PlayerStatus& p) - // { - // return p.client == sortData[0].client && p.player == sortData[0].player; - // }); - - // player->matchWins++; - // player->skins += m_skinsPot; - // m_skinsPot = 1; - - // //check the match score and end the game if this is the mode we're in - // if (m_sharedData.scoreType == ScoreType::Match) - // { - // sortData[0].matchWins++; - // std::sort(sortData.begin(), sortData.end(), - // [&](const PlayerStatus& a, const PlayerStatus& b) - // { - // return a.matchWins > b.matchWins; - // }); - - - // auto remainingHoles = static_cast(m_holeData.size()) - (m_currentHole + 1); - // //if second place can't beat first even if they win all the holes it's game over - // if(sortData[1].matchWins + remainingHoles < sortData[0].matchWins) - // { - // gameFinished = true; - // } - // } - - // //send notification packet to clients that player won the hole - // std::uint16_t data = (player->client << 8) | player->player; - // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - // } - // else - // { - // m_skinsPot++; - - // std::uint16_t data = 0xff00 | m_skinsPot; - // m_sharedData.host.broadcastPacket(PacketID::HoleWon, data, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - // } - //} + //broadcast all scores to make sure everyone is up to date for (auto& player : m_playerInfo) From 4d1353d45e39e7e65ea0a4911e21f25f1d286558 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 14:59:10 +0100 Subject: [PATCH 004/233] update lobby name layout --- samples/golf/src/golf/MenuAvatars.cpp | 408 +++++++++++-------------- samples/golf/src/golf/MenuCreation.cpp | 10 +- 2 files changed, 186 insertions(+), 232 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 39b7129cb..9e68745af 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1775,15 +1775,7 @@ void MenuState::updateLobbyAvatars() auto& largeFont = m_sharedData.sharedResources->fonts.get(FontID::UI); auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); - std::array stringCols = {}; - - const std::array IconPositions = - { - glm::vec3(1.f, -119.f, 0.1f), - glm::vec3(1.f, -132.f, 0.1f), - glm::vec3(1.f + LobbyTextSpacing, -119.f, 0.1f), - glm::vec3(1.f + LobbyTextSpacing, -132.f, 0.1f) - }; + cro::String nameString; for (const auto& c : m_sharedData.connectionData) { @@ -1848,9 +1840,7 @@ void MenuState::updateLobbyAvatars() } glm::vec2 iconPos(1.f, 0.f); - const std::int32_t col = (playerCount / ConstVal::MaxPlayers); - const std::int32_t row = (playerCount % ConstVal::MaxPlayers); - iconPos.x += (LobbyTextSpacing + m_lobbyExpansion) * col; + const std::int32_t row = playerCount; iconPos.y = row * -14.f; //TODO constify row spacing //add list of names on the connected client @@ -1860,7 +1850,7 @@ void MenuState::updateLobbyAvatars() auto avatarIndex = indexFromAvatarID(c.playerData[i].skinID); applyTexture(avatarIndex, m_sharedData.avatarTextures[c.connectionID][i], c.playerData[i].avatarFlags); - stringCols[playerCount / ConstVal::MaxPlayers] += c.playerData[i].name.substr(0, ConstVal::MaxStringChars) + "\n"; + nameString += c.playerData[i].name.substr(0, ConstVal::MaxStringChars) + "\n"; playerCount++; } @@ -1868,262 +1858,226 @@ void MenuState::updateLobbyAvatars() if (c.playerCount != 0) { clientCount++; - } - - //client icons are attached to this - auto entity = m_uiScene.createEntity(); - entity.addComponent().setPosition(IconPositions[h]); - - //used to update spacing by resize callback from lobby background ent. - entity.addComponent().ID = CommandID::Menu::LobbyText; - e.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - auto iconEnt = entity; - - //add a ready status for that client - entity = m_uiScene.createEntity(); - entity.addComponent(); - entity.addComponent(); - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity e2, float) - { - cro::Colour colour = m_readyState[h] ? TextGreenColour : LeaderboardTextDark; - auto& verts = e2.getComponent().getVertexData(); - for (auto& v : verts) + //add a ready status for that client - TODO replace this with animated sprite + auto entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ -11.f, iconPos.y - 6.f }); + entity.addComponent(); + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity e2, float) { - v.colour = colour; - } - }; - iconEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - - auto& verts = entity.getComponent().getVertexData(); - verts = - { - cro::Vertex2D(glm::vec2(0.f)), - cro::Vertex2D(glm::vec2(5.f, 0.f)), - cro::Vertex2D(glm::vec2(0.f, 5.f)), - cro::Vertex2D(glm::vec2(5.f)) - }; - entity.getComponent().updateLocalBounds(); - - //status text - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 8.f, 6.f, 0.1f }); - entity.addComponent(); - entity.addComponent(smallFont).setString(c.playerCount ? "Ready" : "Not Connected"); - entity.getComponent().setCharacterSize(InfoTextSize); - entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setShadowColour(LeaderboardTextDark); - entity.getComponent().setShadowOffset({ 1.f, -1.f }); - iconEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - + cro::Colour colour = m_readyState[h] ? TextGreenColour : LeaderboardTextDark; + auto& verts = e2.getComponent().getVertexData(); + for (auto& v : verts) + { + v.colour = colour; + } + }; + e.getComponent().addChild(entity.getComponent()); + children.push_back(entity); - //rank text - entity = m_uiScene.createEntity(); - entity.addComponent(); - entity.addComponent(); - entity.addComponent(smallFont).setString("Level"); - entity.getComponent().setCharacterSize(InfoTextSize); - entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setShadowColour(LeaderboardTextDark); - entity.getComponent().setShadowOffset({ 1.f, -1.f }); - - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) - { - if (m_sharedData.connectionData[h].playerCount == 0) + auto& verts = entity.getComponent().getVertexData(); + verts = { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else - { - ent.getComponent().setScale(glm::vec2(1.f)); - ent.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); - - float offset = ent.getComponent().getUserData(); - ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), 6.f, 0.1f }); - } - }; - iconEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - auto rankEnt = entity; + cro::Vertex2D(glm::vec2(0.f)), + cro::Vertex2D(glm::vec2(5.f, 0.f)), + cro::Vertex2D(glm::vec2(0.f, 5.f)), + cro::Vertex2D(glm::vec2(5.f)) + }; + entity.getComponent().updateLocalBounds(); - //add a rank badge - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ -16.f, -12.f, 0.2f }); - entity.addComponent(); - entity.addComponent() = m_sprites[SpriteID::LevelBadge]; - entity.addComponent(); - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) - { - if (m_sharedData.connectionData[h].playerCount == 0 - || m_sharedData.connectionData[h].level == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else + //rank text + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 0.f, -120.f }); + entity.addComponent(); + entity.addComponent(smallFont).setString("Level"); + entity.getComponent().setCharacterSize(InfoTextSize); + entity.getComponent().setFillColour(TextNormalColour); + entity.getComponent().setShadowColour(LeaderboardTextDark); + entity.getComponent().setShadowOffset({ 1.f, -1.f }); + + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) { - ent.getComponent().setScale(glm::vec2(1.f)); - auto index = std::min(5, m_sharedData.connectionData[h].level / 10); - ent.getComponent().play(index); - } - }; - rankEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); + if (m_sharedData.connectionData[h].playerCount == 0) + { + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + ent.getComponent().setScale(glm::vec2(1.f)); + ent.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); - //if this is our local client then add the current xp level - if (h == m_sharedData.clientConnection.connectionID) - { - constexpr float BarWidth = 80.f; - constexpr float BarHeight = 10.f; + float offset = ent.getComponent().getUserData(); + ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), -160.f, 0.1f }); + } + }; + e.getComponent().addChild(entity.getComponent()); + children.push_back(entity); + auto rankEnt = entity; + //add a rank badge entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ (BarWidth / 2.f) - 2.f, -4.f, -0.15f }); - - const auto CornerColour = cro::Colour(std::uint8_t(152), 122, 104); + entity.addComponent().setPosition({ -16.f, -12.f, 0.2f }); + entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::LevelBadge]; + entity.addComponent(); - const auto progress = Social::getLevelProgress(); - entity.addComponent().setVertexData( + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) + { + if (m_sharedData.connectionData[h].playerCount == 0 + || m_sharedData.connectionData[h].level == 0) + { + ent.getComponent().setScale(glm::vec2(0.f)); + } + else { - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), TextHighlightColour), + ent.getComponent().setScale(glm::vec2(1.f)); + auto index = std::min(5, m_sharedData.connectionData[h].level / 10); + ent.getComponent().play(index); + } + }; + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), TextHighlightColour), + //if this is our local client then add the current xp level + if (h == m_sharedData.clientConnection.connectionID) + { + constexpr float BarWidth = 80.f; + constexpr float BarHeight = 10.f; - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), LeaderboardTextDark), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), LeaderboardTextDark), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), LeaderboardTextDark), + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ (BarWidth / 2.f) - 2.f, -4.f, -0.15f }); - cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), LeaderboardTextDark), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), LeaderboardTextDark), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, -BarHeight / 2.f), LeaderboardTextDark), + const auto CornerColour = cro::Colour(std::uint8_t(152), 122, 104); - //corners - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, BarHeight / 2.f), CornerColour), + const auto progress = Social::getLevelProgress(); + entity.addComponent().setVertexData( + { + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, BarHeight / 2.f), TextHighlightColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), TextHighlightColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), TextHighlightColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), TextHighlightColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), TextHighlightColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), BarHeight / 2.f), LeaderboardTextDark), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), LeaderboardTextDark), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), LeaderboardTextDark), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), - cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, -BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), LeaderboardTextDark), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + (BarWidth * progress.progress), -BarHeight / 2.f), LeaderboardTextDark), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, -BarHeight / 2.f), LeaderboardTextDark), + //corners + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (BarHeight / 2.f) - 1.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (BarHeight / 2.f) - 1.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (BarHeight / 2.f) - 1.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, -BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2(-BarWidth / 2.f, -BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2((-BarWidth / 2.f) + 1.f, -BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), - cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, -BarHeight / 2.f), CornerColour), - cro::Vertex2D(glm::vec2(BarWidth / 2.f, -BarHeight / 2.f), CornerColour), - }); - entity.getComponent().setPrimitiveType(GL_TRIANGLES); - rankEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); - } - else - { - //text width - auto bounds = cro::Text::getLocalBounds(rankEnt); - rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); - } + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, (BarHeight / 2.f) - 1.f), CornerColour), + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, -BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), - //add a network status icon (not attached to icons) - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition(glm::vec3(iconPos, 0.1f)); - entity.getComponent().move({ -17.f, -12.f }); - if (iconPos.x > LobbyTextSpacing) - { - //Ican't even remember why I have t odo this any more... - entity.getComponent().setOrigin({ 17.f, 0.f }); - } - entity.addComponent(); - entity.addComponent() = m_sprites[SpriteID::NetStrength]; - entity.addComponent(); + cro::Vertex2D(glm::vec2(BarWidth / 2.f, (-BarHeight / 2.f) + 1.f), CornerColour), + cro::Vertex2D(glm::vec2((BarWidth / 2.f) - 1.f, -BarHeight / 2.f), CornerColour), + cro::Vertex2D(glm::vec2(BarWidth / 2.f, -BarHeight / 2.f), CornerColour), + }); + entity.getComponent().setPrimitiveType(GL_TRIANGLES); + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) - { - if (m_sharedData.connectionData[h].playerCount == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); + rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); } else { - ent.getComponent().setScale(glm::vec2(1.f)); - auto index = std::min(4u, m_sharedData.connectionData[h].pingTime / 30); - ent.getComponent().play(index); + //text width + auto bounds = cro::Text::getLocalBounds(rankEnt); + rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); } - }; - entity.addComponent().ID = CommandID::Menu::LobbyText; - e.getComponent().addChild(entity.getComponent()); - children.push_back(entity); + + + //add a network status icon (not attached to icons) + //entity = m_uiScene.createEntity(); + //entity.addComponent().setPosition(glm::vec3(iconPos, 0.1f)); + //entity.getComponent().move({ -17.f, -12.f }); + //if (iconPos.x > LobbyTextSpacing) + //{ + // //I can't even remember why I have to do this any more... + // entity.getComponent().setOrigin({ 17.f, 0.f }); + //} + //entity.addComponent(); + //entity.addComponent() = m_sprites[SpriteID::NetStrength]; + //entity.addComponent(); + + //entity.addComponent().active = true; + //entity.getComponent().function = + // [&, h](cro::Entity ent, float) + //{ + // if (m_sharedData.connectionData[h].playerCount == 0) + // { + // ent.getComponent().setScale(glm::vec2(0.f)); + // } + // else + // { + // ent.getComponent().setScale(glm::vec2(1.f)); + // auto index = std::min(4u, m_sharedData.connectionData[h].pingTime / 30); + // ent.getComponent().play(index); + // } + //}; + //entity.addComponent().ID = CommandID::Menu::LobbyText; + //e.getComponent().addChild(entity.getComponent()); + //children.push_back(entity); + } h++; } - //create tex columns for name lists - glm::vec2 textPos(1.f, 0.f); - for (const auto& str : stringCols) + //create text for name list + if(!nameString.empty()) { - if (!str.empty()) - { - auto entity = m_uiScene.createEntity(); - entity.addComponent().setPosition(glm::vec3(textPos, 0.2f)); - entity.addComponent(); - entity.addComponent(largeFont).setString(str); - entity.getComponent().setFillColour(LeaderboardTextDark); - entity.getComponent().setCharacterSize(UITextSize); - entity.getComponent().setVerticalSpacing(6.f); - - auto crop = cro::Text::getLocalBounds(entity); - crop.width = std::min(crop.width, MinLobbyCropWidth + m_lobbyExpansion); - entity.getComponent().setCroppingArea(crop); - if (textPos.x > LobbyTextSpacing) - { - entity.getComponent().move({ m_lobbyExpansion, 0.f }); - } + auto entity = m_uiScene.createEntity(); + entity.addComponent().setPosition(glm::vec3(1.f, 0.f, 0.2f)); + entity.addComponent(); + entity.addComponent(largeFont).setString(nameString); + entity.getComponent().setFillColour(LeaderboardTextDark); + entity.getComponent().setCharacterSize(UITextSize); + entity.getComponent().setVerticalSpacing(6.f); - //used to update spacing by resize callback from lobby background ent. - entity.addComponent().ID = CommandID::Menu::LobbyText; - e.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - } - textPos.x += LobbyTextSpacing; + //used to update spacing by resize callback from lobby background ent. + //TODO is this still used? + entity.addComponent().ID = CommandID::Menu::LobbyText; + e.getComponent().addChild(entity.getComponent()); + children.push_back(entity); } diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index f715e1606..093866b73 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1557,7 +1557,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity = m_uiScene.createEntity(); entity.addComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { 20.f, bounds.height - 25.f }; + entity.addComponent().absolutePosition = { 21.f, bounds.height - 28.f }; entity.getComponent().depth = 0.2f; entity.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::LobbyList; entity.addComponent().setUserData>(); //abuse this component to store handles to the text children. @@ -2664,8 +2664,8 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 156.f, -79.f }; - labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; + labelEnt.addComponent().absolutePosition = { 56.f, 79.f }; + //labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; labelEnt.getComponent().depth = 0.01f; labelEnt.getComponent().resizeCallback = resizeCallbackRight; bounds = cro::Text::getLocalBounds(labelEnt); @@ -2682,8 +2682,8 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 138.f, -86.f }; - labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; + labelEnt.addComponent().absolutePosition = { 38.f, 86.f }; + //labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; labelEnt.getComponent().depth = 0.01f; labelEnt.getComponent().resizeCallback = resizeCallbackRight; m_menuEntities[MenuID::Lobby].getComponent().addChild(labelEnt.getComponent()); From 28828fff439a6b2625c4759202d30592a5ac9463 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 15:56:58 +0100 Subject: [PATCH 005/233] begin updating course selection layout --- samples/golf/src/golf/MenuCreation.cpp | 64 +++++++++----------------- samples/golf/src/golf/MenuState.hpp | 2 +- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 093866b73..b1bb81758 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1450,7 +1450,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); menuTransform.addChild(entity.getComponent()); - m_menuEntities[MenuID::LobbyBackground] = entity; + m_lobbyWindowEntities[LobbyEntityID::Background] = entity; auto bgEnt = entity; #ifdef USE_GNS @@ -1566,21 +1566,19 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //displays the thumbnails for the selected course entity = m_uiScene.createEntity(); - entity.addComponent().setScale({ 0.f, 0.f }); + entity.addComponent(); entity.addComponent(); - entity.addComponent() = spriteSheet.getSprite("thumbnail_window"); - entity.addComponent().absolutePosition = { bounds.width / 2.f, bounds.height / 2.f }; + entity.addComponent() = spriteSheetV2.getSprite("course_background"); + entity.addComponent().absolutePosition = { 230.f, 31.f }; entity.getComponent().depth = 1.2f; - //entity.getComponent().resizeCallback = textResizeCallback; entity.addComponent().ID = CommandID::Menu::UIElement; bounds = entity.getComponent().getTextureBounds(); - entity.getComponent().setOrigin({ std::floor(bounds.width / 2.f), std::floor(bounds.height / 2.f) }); bgEnt.getComponent().addChild(entity.getComponent()); auto thumbBgEnt = entity; m_lobbyWindowEntities[LobbyEntityID::HoleSelection] = thumbBgEnt; entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 6.f, 34.f, 0.2f }); + entity.addComponent().setPosition({ 16.f, 53.f, 0.2f }); entity.addComponent(); entity.addComponent(); thumbBgEnt.getComponent().addChild(entity.getComponent()); @@ -1589,7 +1587,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //hole count entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 26.f, 0.1f }); + entity.addComponent().setPosition({ 86.f, 45.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseHoles; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); @@ -1602,7 +1600,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; entity.addComponent().ID = CommandID::Menu::UIElement; - entity.addComponent().absolutePosition = { 4.f, 3.f }; + entity.addComponent().absolutePosition = { 176.f, 141.f }; entity.getComponent().depth = 0.01f; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); @@ -1621,50 +1619,34 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st - - //background for course info - entity = m_uiScene.createEntity(); - entity.addComponent().setScale({ 0.f, 0.f }); - entity.addComponent(); - entity.addComponent() = spriteSheet.getSprite("course_desc"); - entity.addComponent().ID = CommandID::Menu::UIElement; - entity.addComponent().relativePosition = CourseDescPosition; - entity.getComponent().depth = -0.2f; - bounds = entity.getComponent().getTextureBounds(); - entity.getComponent().setOrigin({ std::floor( bounds.width / 2.f), std::floor(bounds.height / 2.f) + 3.f }); - menuTransform.addChild(entity.getComponent()); - bgEnt = entity; - - - //course title entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 68.f, 01.f }); + entity.addComponent().setPosition({ 134.f, 192.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseTitle; entity.addComponent(font).setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); - bgEnt.getComponent().addChild(entity.getComponent()); + thumbBgEnt.getComponent().addChild(entity.getComponent()); //course description entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 56.f, 01.f }); + entity.addComponent().setPosition({ 134.f, 180.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseDesc; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); - bgEnt.getComponent().addChild(entity.getComponent()); + thumbBgEnt.getComponent().addChild(entity.getComponent()); //current rules entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ bounds.width / 2.f, 42.f, 01.f }); + entity.addComponent().setPosition({ 180.f, 90.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseRules; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setString("IMPLEMENT ME"); - bgEnt.getComponent().addChild(entity.getComponent()); - centreText(entity); + entity.getComponent().setString("IMPLEMENT ME\nPLEASE\nCOURSE 5"); + thumbBgEnt.getComponent().addChild(entity.getComponent()); + //centreText(entity); //banner entity = m_uiScene.createEntity(); @@ -2410,7 +2392,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); buttonEnt = m_uiScene.createEntity(); @@ -2437,7 +2419,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); //choose gimme radius @@ -2465,7 +2447,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); buttonEnt = m_uiScene.createEntity(); @@ -2492,7 +2474,7 @@ void MenuState::addCourseSelectButtons() }; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::LobbyBackground].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); @@ -2504,7 +2486,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 32.f, 21.f }; + buttonEnt.addComponent().absolutePosition = { 46.f, 41.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2529,7 +2511,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 117.f, 21.f }; + buttonEnt.addComponent().absolutePosition = { 126.f, 41.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2557,7 +2539,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 3.f, 2.f }; + checkboxEnt.addComponent().absolutePosition = { 175.f, 140.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) @@ -2982,7 +2964,7 @@ void MenuState::updateCourseRuleString() } e.getComponent().setString(str); - centreText(e); + //centreText(e); }; m_uiScene.getSystem()->sendCommand(cmd); diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index 3021c37cc..564c91752 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -96,7 +96,6 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co Main, Avatar, Join, Lobby, ProfileFlyout, ConfirmQuit, - LobbyBackground, Count }; @@ -198,6 +197,7 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co HoleSelection, HoleThumb, CourseTicker, + Background, Count }; From 1827e58b772f2fde0eb95c03c6153e3c1c0666e6 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 5 Jun 2023 18:54:29 +0100 Subject: [PATCH 006/233] tweak menu layout --- samples/golf/src/golf/MenuCreation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index b1bb81758..3e7cae50e 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1505,7 +1505,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity = m_uiScene.createEntity(); entity.addComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { 366.f, 210.f }; + entity.addComponent().absolutePosition = { 364.f, 210.f }; entity.getComponent().depth = 0.1f; entity.addComponent(font).setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); @@ -1519,7 +1519,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent().setOrigin({ 74.f, 0.f }); entity.getComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { 366.f, 190.f }; + entity.addComponent().absolutePosition = { 364.f, 190.f }; entity.getComponent().depth = 0.1f; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); @@ -1535,7 +1535,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(font).setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString(GimmeString[m_sharedData.gimmeRadius]); - entity.addComponent().absolutePosition = {366.f, 100.f }; + entity.addComponent().absolutePosition = {364.f, 100.f }; entity.getComponent().depth = 0.1f; entity.addComponent().ID = CommandID::Menu::GimmeDesc | CommandID::Menu::UIElement; centreText(entity); @@ -1545,7 +1545,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent().setOrigin({ 74.f, 0.f }); entity.getComponent(); entity.addComponent(); - entity.addComponent().absolutePosition = { 366.f, 80.f }; + entity.addComponent().absolutePosition = { 364.f, 80.f }; entity.getComponent().depth = 0.1f; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); From 91f283bcf1f7b9dbf196bf21d3ccd10f0971ceda Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 6 Jun 2023 12:48:13 +0100 Subject: [PATCH 007/233] update course selection buttons update rule switch buttons --- samples/golf/src/golf/MenuAvatars.cpp | 35 +----- samples/golf/src/golf/MenuCallbacks.hpp | 2 + samples/golf/src/golf/MenuCreation.cpp | 154 +++++++++++------------- samples/golf/src/golf/MenuState.hpp | 3 +- 4 files changed, 82 insertions(+), 112 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 9e68745af..c6aec3770 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1331,29 +1331,19 @@ void MenuState::createMenuCallbacks() { if (activated(evt)) { - auto rect = e.getComponent().getTextureRect(); - auto area = e.getComponent().area; - float scale = m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y; if (scale == 0) { scale = 1.f; - rect.bottom -= 16.f; - area.left += area.width; m_audioEnts[AudioID::Back].getComponent().play(); } else { scale = 0.f; - rect.bottom += 16.f; - area.left -= area.width; m_audioEnts[AudioID::Accept].getComponent().play(); } m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 1.f, scale }); - e.getComponent().setTextureRect(rect); - e.getComponent().area = area; - e.getComponent().setRotation(0.f); //fudge to trigger transform callback and update area } }); @@ -1859,35 +1849,22 @@ void MenuState::updateLobbyAvatars() { clientCount++; - //add a ready status for that client - TODO replace this with animated sprite + //add a ready status for that client auto entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ -11.f, iconPos.y - 6.f }); + entity.addComponent().setPosition({ -11.f, iconPos.y - 7.f }); entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::ReadyStatus]; + entity.addComponent(); entity.addComponent().active = true; entity.getComponent().function = [&, h](cro::Entity e2, float) { - cro::Colour colour = m_readyState[h] ? TextGreenColour : LeaderboardTextDark; - - auto& verts = e2.getComponent().getVertexData(); - for (auto& v : verts) - { - v.colour = colour; - } + auto index = m_readyState[h] ? 1 : 0; + e2.getComponent().play(index); }; e.getComponent().addChild(entity.getComponent()); children.push_back(entity); - auto& verts = entity.getComponent().getVertexData(); - verts = - { - cro::Vertex2D(glm::vec2(0.f)), - cro::Vertex2D(glm::vec2(5.f, 0.f)), - cro::Vertex2D(glm::vec2(0.f, 5.f)), - cro::Vertex2D(glm::vec2(5.f)) - }; - entity.getComponent().updateLocalBounds(); - //rank text entity = m_uiScene.createEntity(); diff --git a/samples/golf/src/golf/MenuCallbacks.hpp b/samples/golf/src/golf/MenuCallbacks.hpp index a91bd17d5..2cc83127d 100644 --- a/samples/golf/src/golf/MenuCallbacks.hpp +++ b/samples/golf/src/golf/MenuCallbacks.hpp @@ -103,6 +103,8 @@ struct SpriteID final LobbyCheckboxHighlight, LobbyRuleButton, LobbyRuleButtonHighlight, + LobbyLeft, + LobbyRight, Envelope, LevelBadge, diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 3e7cae50e..6fa1a1a8b 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -188,13 +188,14 @@ void MenuState::parseCourseDirectory(const std::string& rootDir, bool isUser) auto& data = m_courseData.emplace_back(); if (!title.empty()) { - data.title = std::to_string(courseNumber) + ". " + title; + data.title = title; } if (!description.empty()) { data.description = description; } data.directory = dir; + data.courseNumber = courseNumber; data.isUser = isUser; data.holeCount[0] = "All " + std::to_string(std::min(holeCount, 18)) + " holes"; data.holeCount[1] = "Front " + std::to_string(std::max(holeCount / 2, 1)); @@ -1422,6 +1423,9 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st cro::SpriteSheet spriteSheetV2; spriteSheetV2.loadFromFile("assets/golf/sprites/lobby_menu_v2.spt", m_resources.textures); + m_sprites[SpriteID::ReadyStatus] = spriteSheetV2.getSprite("ready_indicator"); + m_sprites[SpriteID::LobbyLeft] = spriteSheetV2.getSprite("prev_course"); + m_sprites[SpriteID::LobbyRight] = spriteSheetV2.getSprite("next_course"); //title auto entity = m_uiScene.createEntity(); @@ -1495,12 +1499,6 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st m_lobbyWindowEntities[LobbyEntityID::CourseTicker] = entity; #endif - /*auto textResizeCallback = - [&,bgEnt](cro::Entity e) - { - e.getComponent().setPosition({ bgEnt.getComponent().getOrigin().x , e.getComponent().absolutePosition.y, e.getComponent().depth }); - };*/ - //display the score type entity = m_uiScene.createEntity(); entity.addComponent(); @@ -1517,7 +1515,6 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //and score description entity = m_uiScene.createEntity(); entity.addComponent().setOrigin({ 74.f, 0.f }); - entity.getComponent(); entity.addComponent(); entity.addComponent().absolutePosition = { 364.f, 190.f }; entity.getComponent().depth = 0.1f; @@ -1543,7 +1540,6 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity = m_uiScene.createEntity(); entity.addComponent().setOrigin({ 74.f, 0.f }); - entity.getComponent(); entity.addComponent(); entity.addComponent().absolutePosition = { 364.f, 80.f }; entity.getComponent().depth = 0.1f; @@ -1553,6 +1549,31 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent().ID = CommandID::Menu::UIElement; bgEnt.getComponent().addChild(entity.getComponent()); + //button for course selection + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 335.f, 35.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleGameRules; + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = + [&](cro::Entity e, float) + { + //hack to disable the button when course selction is visible + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; + }; + bgEnt.getComponent().addChild(entity.getComponent()); + + //display lobby members - updateLobbyAvatars() adds the text ents to this. entity = m_uiScene.createEntity(); entity.addComponent(); @@ -1617,7 +1638,21 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st }; thumbBgEnt.getComponent().addChild(entity.getComponent()); - + //button for rule selection + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 110.f, 4.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleGameRules; + entity.addComponent().ID = CommandID::Menu::UIElement; + thumbBgEnt.getComponent().addChild(entity.getComponent()); //course title entity = m_uiScene.createEntity(); @@ -1639,7 +1674,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //current rules entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 180.f, 90.f, 0.1f }); + entity.addComponent().setPosition({ 170.f, 80.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseRules; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); @@ -2375,7 +2410,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 366.f-85.f, 206.f }; + buttonEnt.addComponent().absolutePosition = { 364.f-91.f, 206.f }; buttonEnt.getComponent().depth = 0.01f; auto bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2402,7 +2437,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 366.f+82.f, 206.f }; + buttonEnt.addComponent().absolutePosition = { 364.f+92.f, 206.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2430,7 +2465,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 366.f - 85.f, 95.f }; + buttonEnt.addComponent().absolutePosition = { 364.f - 91.f, 95.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2457,7 +2492,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; buttonEnt.addComponent(); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 366.f + 82.f, 95.f }; + buttonEnt.addComponent().absolutePosition = { 364.f + 92.f, 95.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; @@ -2704,67 +2739,18 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent(); buttonEnt.addComponent() = m_menuSounds.getEmitter("switch"); buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; - buttonEnt.addComponent(); + buttonEnt.addComponent() = m_sprites[SpriteID::LobbyLeft]; + buttonEnt.getComponent().setColour(cro::Colour::Transparent); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { -108.f, -22.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; + buttonEnt.addComponent().absolutePosition = { 6.f, 101.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; - buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; - buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevCourse; - buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); - - - - //rules button - buttonEnt = m_uiScene.createEntity(); - buttonEnt.addComponent(); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::LobbyRuleButton]; - buttonEnt.addComponent(); - buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 0.f, -19.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; - buttonEnt.getComponent().depth = 0.01f; - bounds = buttonEnt.getComponent().getTextureBounds(); - buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - //hax. - e.getComponent().play(static_cast( - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y)); - }; - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); - - buttonEnt = m_uiScene.createEntity(); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_menuSounds.getEmitter("switch"); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::LobbyRuleButtonHighlight]; - buttonEnt.getComponent().setColour(cro::Colour::Transparent); - buttonEnt.addComponent(); - buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 0.f, -19.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; - buttonEnt.getComponent().depth = 0.025f; - bounds = buttonEnt.getComponent().getTextureBounds(); - auto area = bounds; - area.width /= 2.f; - area.left += area.width; - buttonEnt.addComponent().area = area; - buttonEnt.getComponent().setGroup(MenuID::Lobby); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; - buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleGameRules; - buttonEnt.getComponent().setOrigin({ std::floor(bounds.width / 2.f), std::floor(bounds.height / 2.f) }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevCourse; + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2772,22 +2758,19 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent(); buttonEnt.addComponent() = m_menuSounds.getEmitter("switch"); buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; - buttonEnt.addComponent(); + buttonEnt.addComponent() = m_sprites[SpriteID::LobbyRight]; + buttonEnt.getComponent().setColour(cro::Colour::Transparent); buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 108.f, -22.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; + buttonEnt.addComponent().absolutePosition = { 159.f, 101.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; - buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; + buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextCourse; - buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2950,17 +2933,24 @@ void MenuState::updateCourseRuleString() cmd.targetFlags = CommandID::Menu::CourseRules; cmd.action = [&, data](cro::Entity e, float) { - auto str = ScoreTypes[m_sharedData.scoreType]; - str += ", " + GimmeString[m_sharedData.gimmeRadius]; + std::string str; + if (data != m_courseData.end() + && !data->isUser) + { + str = "Course " + std::to_string(data->courseNumber) + "\n"; + } + + str += ScoreTypes[m_sharedData.scoreType]; + str += ",\n" + GimmeString[m_sharedData.gimmeRadius]; if (data != m_courseData.end()) { - str += ", " + data->holeCount[m_sharedData.holeCount]; + str += ",\n" + data->holeCount[m_sharedData.holeCount]; } if (m_sharedData.reverseCourse) { - str += ", Reversed"; + str += ",\nReversed"; } e.getComponent().setString(str); diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index 564c91752..88d91ebcb 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -228,7 +228,8 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co { cro::String directory; cro::String title = "Untitled"; - cro::String description = "No Description"; + cro::String description = "No Description"; + std::int32_t courseNumber = 0; //base 1 std::array holeCount = {}; bool isUser = false; }; From bdec3dbc29f52e6d4c46ad6372c17a85743775b2 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 6 Jun 2023 14:27:10 +0100 Subject: [PATCH 008/233] update lobby options layout --- samples/golf/src/golf/MenuConsts.hpp | 4 +- samples/golf/src/golf/MenuCreation.cpp | 79 +++++++------------------- 2 files changed, 23 insertions(+), 60 deletions(-) diff --git a/samples/golf/src/golf/MenuConsts.hpp b/samples/golf/src/golf/MenuConsts.hpp index 927c4065f..8813f0cae 100644 --- a/samples/golf/src/golf/MenuConsts.hpp +++ b/samples/golf/src/golf/MenuConsts.hpp @@ -67,8 +67,8 @@ static const std::array ScoreTypes = static const std::array GimmeString = { "No Gimme", - "Inside the leather", - "Inside the putter" + "Inside The Leather", + "Inside The Putter" }; static const std::array CourseTypes = diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 6fa1a1a8b..411bbc0d3 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1674,14 +1674,14 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //current rules entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 170.f, 80.f, 0.1f }); + entity.addComponent().setPosition({ 214.f, 80.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseRules; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); entity.getComponent().setFillColour(TextNormalColour); entity.getComponent().setString("IMPLEMENT ME\nPLEASE\nCOURSE 5"); + entity.getComponent().setAlignment(cro::Text::Alignment::Centre); thumbBgEnt.getComponent().addChild(entity.getComponent()); - //centreText(entity); //banner entity = m_uiScene.createEntity(); @@ -2598,21 +2598,13 @@ void MenuState::addCourseSelectButtons() if (Social::isAvailable()) { //friends only lobby - auto resizeCallback = - [&](cro::Entity e) - { - e.getComponent().move({ -m_lobbyExpansion, 0.f }); - }; - auto checkboxEnt = m_uiScene.createEntity(); checkboxEnt.addComponent(); checkboxEnt.addComponent(); checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { -189.f, -86.f }; - checkboxEnt.getComponent().relativePosition = LobbyBackgroundPosition; + checkboxEnt.addComponent().absolutePosition = { 176.f, 115.f }; checkboxEnt.getComponent().depth = 0.01f; - checkboxEnt.getComponent().resizeCallback = resizeCallback; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); checkboxEnt.addComponent().active = true; @@ -2627,7 +2619,7 @@ void MenuState::addCourseSelectButtons() e.getComponent().setTextureRect(b); }; - m_menuEntities[MenuID::Lobby].getComponent().addChild(checkboxEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); checkboxEnt = m_uiScene.createEntity(); @@ -2637,17 +2629,15 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { -190.f, -87.f }; - checkboxEnt.getComponent().relativePosition = LobbyBackgroundPosition; + checkboxEnt.addComponent().absolutePosition = { 175.f, 114.f }; checkboxEnt.getComponent().depth = 0.01f; - checkboxEnt.getComponent().resizeCallback = resizeCallback; bounds = checkboxEnt.getComponent().getTextureBounds(); checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFriendsOnly; - m_menuEntities[MenuID::Lobby].getComponent().addChild(checkboxEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); auto labelEnt = m_uiScene.createEntity(); labelEnt.addComponent(); @@ -2658,18 +2648,16 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { -176.f, -79.f }; - labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; + labelEnt.addComponent().absolutePosition = { 187.f, 122.f }; labelEnt.getComponent().depth = 0.01f; - labelEnt.getComponent().resizeCallback = resizeCallback; - m_menuEntities[MenuID::Lobby].getComponent().addChild(labelEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); checkboxEnt.getComponent().area.width += cro::Text::getLocalBounds(labelEnt).width; - auto resizeCallbackRight = + /*auto resizeCallbackRight = [&](cro::Entity e) { e.getComponent().move({ m_lobbyExpansion, 0.f }); - }; + };*/ labelEnt = m_uiScene.createEntity(); labelEnt.addComponent(); @@ -2681,17 +2669,15 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 56.f, 79.f }; - //labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; + labelEnt.addComponent().absolutePosition = { 212.f, 109.f }; labelEnt.getComponent().depth = 0.01f; - labelEnt.getComponent().resizeCallback = resizeCallbackRight; bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; labelEnt.getComponent().setGroup(MenuID::Lobby); labelEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectText; labelEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectText; labelEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.inviteFriends; - m_menuEntities[MenuID::Lobby].getComponent().addChild(labelEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); labelEnt = m_uiScene.createEntity(); @@ -2699,35 +2685,9 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 38.f, 86.f }; - //labelEnt.getComponent().relativePosition = LobbyBackgroundPosition; + labelEnt.addComponent().absolutePosition = { 194.f, 102.f }; labelEnt.getComponent().depth = 0.01f; - labelEnt.getComponent().resizeCallback = resizeCallbackRight; - m_menuEntities[MenuID::Lobby].getComponent().addChild(labelEnt.getComponent()); - - //tedious method of delaying this by one frame. Ensures everything is properly placed - auto timerEnt = m_uiScene.createEntity(); - timerEnt.addComponent().active = true; - timerEnt.getComponent().function = - [&](cro::Entity o, float) - { - cro::Command cmd; - cmd.targetFlags = CommandID::Menu::CourseSelect; - cmd.action = [](cro::Entity e, float) - { - if (e.hasComponent()) - { - if (e.getComponent().resizeCallback) - { - e.getComponent().resizeCallback(e); - } - } - }; - m_uiScene.getSystem()->sendCommand(cmd); - - o.getComponent().active = false; - m_uiScene.destroyEntity(o); - }; + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } @@ -2745,6 +2705,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent().absolutePosition = { 6.f, 101.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); + bounds.bottom -= 6.f; + bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; @@ -2764,6 +2726,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.addComponent().absolutePosition = { 159.f, 101.f }; buttonEnt.getComponent().depth = 0.01f; bounds = buttonEnt.getComponent().getTextureBounds(); + bounds.bottom -= 6.f; + bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; @@ -2941,20 +2905,19 @@ void MenuState::updateCourseRuleString() } str += ScoreTypes[m_sharedData.scoreType]; - str += ",\n" + GimmeString[m_sharedData.gimmeRadius]; + str += "\n" + GimmeString[m_sharedData.gimmeRadius]; if (data != m_courseData.end()) { - str += ",\n" + data->holeCount[m_sharedData.holeCount]; + str += "\n" + data->holeCount[m_sharedData.holeCount]; } if (m_sharedData.reverseCourse) { - str += ",\nReversed"; + str += "\nReversed"; } e.getComponent().setString(str); - //centreText(e); }; m_uiScene.getSystem()->sendCommand(cmd); From f5268576afde55531b987ea2509e1f40214c393c Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 6 Jun 2023 15:16:06 +0100 Subject: [PATCH 009/233] first pass of new lobby menu --- samples/golf/src/golf/MenuAvatars.cpp | 163 +++++++++++-------------- samples/golf/src/golf/MenuCreation.cpp | 108 ++++++++-------- 2 files changed, 119 insertions(+), 152 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index c6aec3770..89af33305 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1865,74 +1865,81 @@ void MenuState::updateLobbyAvatars() e.getComponent().addChild(entity.getComponent()); children.push_back(entity); - - //rank text - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 0.f, -120.f }); - entity.addComponent(); - entity.addComponent(smallFont).setString("Level"); - entity.getComponent().setCharacterSize(InfoTextSize); - entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setShadowColour(LeaderboardTextDark); - entity.getComponent().setShadowOffset({ 1.f, -1.f }); - - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) + //if this is our local client then add the current xp level + //TODO this made sense when it was applied to each client (and ideally + //we want to bring that back) otherwise we could do this just once in + //a more sane manner somewhere else. + if (h == m_sharedData.clientConnection.connectionID) { - if (m_sharedData.connectionData[h].playerCount == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else + //rank text + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 22.f, 16.f, 0.3f }); + entity.addComponent(); + entity.addComponent(smallFont).setString("Level"); + entity.getComponent().setCharacterSize(InfoTextSize); + entity.getComponent().setFillColour(TextNormalColour); + entity.getComponent().setShadowColour(LeaderboardTextDark); + entity.getComponent().setShadowOffset({ 1.f, -1.f }); + entity.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); + + /*entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) { - ent.getComponent().setScale(glm::vec2(1.f)); - ent.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); - - float offset = ent.getComponent().getUserData(); - ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), -160.f, 0.1f }); - } - }; - e.getComponent().addChild(entity.getComponent()); - children.push_back(entity); - auto rankEnt = entity; + //currently only applying to local player so there's + //always at least one + if (m_sharedData.connectionData[h].playerCount == 0) + { + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + ent.getComponent().setScale(glm::vec2(1.f)); + ent.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); - //add a rank badge - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ -16.f, -12.f, 0.2f }); - entity.addComponent(); - entity.addComponent() = m_sprites[SpriteID::LevelBadge]; - entity.addComponent(); + float offset = ent.getComponent().getUserData(); + ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), -160.f, 0.1f }); + } + };*/ + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(entity.getComponent()); + children.push_back(entity); + auto rankEnt = entity; - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) - { - if (m_sharedData.connectionData[h].playerCount == 0 - || m_sharedData.connectionData[h].level == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else + //add a rank badge + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ -18.f, -12.f, 0.2f }); + entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::LevelBadge]; + entity.addComponent(); + + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) { - ent.getComponent().setScale(glm::vec2(1.f)); - auto index = std::min(5, m_sharedData.connectionData[h].level / 10); - ent.getComponent().play(index); - } - }; - rankEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); + if (m_sharedData.connectionData[h].playerCount == 0 + || m_sharedData.connectionData[h].level == 0) + { + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + ent.getComponent().setScale(glm::vec2(1.f)); + auto index = std::min(5, m_sharedData.connectionData[h].level / 10); + ent.getComponent().play(index); + } + }; + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); - //if this is our local client then add the current xp level - if (h == m_sharedData.clientConnection.connectionID) - { + //level progress constexpr float BarWidth = 80.f; constexpr float BarHeight = 10.f; entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ (BarWidth / 2.f) - 2.f, -4.f, -0.15f }); - const auto CornerColour = cro::Colour(std::uint8_t(152), 122, 104); + const auto CornerColour = cro::Colour(std::uint8_t(58), 57, 65); //grey + //const auto CornerColour = cro::Colour(std::uint8_t(152), 122, 104); //beige const auto progress = Social::getLevelProgress(); entity.addComponent().setVertexData( @@ -1993,46 +2000,12 @@ void MenuState::updateLobbyAvatars() rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); } - else - { - //text width - auto bounds = cro::Text::getLocalBounds(rankEnt); - rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); - } - - - - //add a network status icon (not attached to icons) - //entity = m_uiScene.createEntity(); - //entity.addComponent().setPosition(glm::vec3(iconPos, 0.1f)); - //entity.getComponent().move({ -17.f, -12.f }); - //if (iconPos.x > LobbyTextSpacing) + //else //{ - // //I can't even remember why I have to do this any more... - // entity.getComponent().setOrigin({ 17.f, 0.f }); + // //text width + // auto bounds = cro::Text::getLocalBounds(rankEnt); + // rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); //} - //entity.addComponent(); - //entity.addComponent() = m_sprites[SpriteID::NetStrength]; - //entity.addComponent(); - - //entity.addComponent().active = true; - //entity.getComponent().function = - // [&, h](cro::Entity ent, float) - //{ - // if (m_sharedData.connectionData[h].playerCount == 0) - // { - // ent.getComponent().setScale(glm::vec2(0.f)); - // } - // else - // { - // ent.getComponent().setScale(glm::vec2(1.f)); - // auto index = std::min(4u, m_sharedData.connectionData[h].pingTime / 30); - // ent.getComponent().play(index); - // } - //}; - //entity.addComponent().ID = CommandID::Menu::LobbyText; - //e.getComponent().addChild(entity.getComponent()); - //children.push_back(entity); } h++; } @@ -2040,7 +2013,7 @@ void MenuState::updateLobbyAvatars() //create text for name list - if(!nameString.empty()) + if (!nameString.empty()) { auto entity = m_uiScene.createEntity(); entity.addComponent().setPosition(glm::vec3(1.f, 0.f, 0.2f)); diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 411bbc0d3..01faf9678 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -2653,12 +2653,6 @@ void MenuState::addCourseSelectButtons() m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); checkboxEnt.getComponent().area.width += cro::Text::getLocalBounds(labelEnt).width; - /*auto resizeCallbackRight = - [&](cro::Entity e) - { - e.getComponent().move({ m_lobbyExpansion, 0.f }); - };*/ - labelEnt = m_uiScene.createEntity(); labelEnt.addComponent(); labelEnt.addComponent() = m_menuSounds.getEmitter("switch"); @@ -2669,7 +2663,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 212.f, 109.f }; + labelEnt.addComponent().absolutePosition = { 212.f, 107.f }; labelEnt.getComponent().depth = 0.01f; bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; @@ -2685,7 +2679,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 194.f, 102.f }; + labelEnt.addComponent().absolutePosition = { 194.f, 100.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } @@ -2740,66 +2734,66 @@ void MenuState::addCourseSelectButtons() if (m_courseData.size() > m_courseIndices[Range::Official].count) { - //add arrows to scroll through course list sub indices. - buttonEnt = m_uiScene.createEntity(); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_menuSounds.getEmitter("switch"); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::PrevCourse]; - buttonEnt.addComponent(); - buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { -68.f, -34.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; - buttonEnt.getComponent().depth = 0.01f; - bounds = buttonEnt.getComponent().getTextureBounds(); - buttonEnt.addComponent().area = bounds; - buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; - buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; - buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleType; - buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + checkboxEnt = m_uiScene.createEntity(); + checkboxEnt.addComponent(); + checkboxEnt.addComponent() = m_menuSounds.getEmitter("switch"); + checkboxEnt.addComponent(); + checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; + checkboxEnt.getComponent().setColour(cro::Colour::Transparent); + checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; + checkboxEnt.addComponent().absolutePosition = { 175.f, 127.f }; + checkboxEnt.getComponent().depth = 0.01f; + bounds = checkboxEnt.getComponent().getTextureBounds(); + bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) + checkboxEnt.addComponent().area = bounds; + checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleType; + checkboxEnt.addComponent().active = true; + checkboxEnt.getComponent().function = + [&](cro::Entity e, float) + { + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; + }; + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); + + checkboxEnt = m_uiScene.createEntity(); + checkboxEnt.addComponent(); + checkboxEnt.addComponent(); + checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; + checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; + checkboxEnt.addComponent().absolutePosition = { 176.f, 128.f }; + checkboxEnt.getComponent().depth = 0.01f; + bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); + checkboxEnt.addComponent().active = true; + checkboxEnt.getComponent().function = + [&, bounds](cro::Entity e, float) + { + auto b = bounds; + if (m_currentRange != Range::Official) + { + b.bottom -= bounds.height; + } + e.getComponent().setTextureRect(b); + }; - - buttonEnt = m_uiScene.createEntity(); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_menuSounds.getEmitter("switch"); - buttonEnt.addComponent(); - buttonEnt.addComponent() = m_sprites[SpriteID::NextCourse]; - buttonEnt.addComponent(); - buttonEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - buttonEnt.addComponent().absolutePosition = { 68.f, -34.f }; - buttonEnt.getComponent().relativePosition = CourseDescPosition; - buttonEnt.getComponent().depth = 0.01f; - bounds = buttonEnt.getComponent().getTextureBounds(); - buttonEnt.addComponent().area = bounds; - buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; - buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; - buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextHoleType; - - buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); - - m_menuEntities[MenuID::Lobby].getComponent().addChild(buttonEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); auto labelEnt = m_uiScene.createEntity(); labelEnt.addComponent(); labelEnt.addComponent(); - labelEnt.addComponent(font).setString(CourseTypes[m_currentRange]); + labelEnt.addComponent(font).setString("User Courses"); labelEnt.getComponent().setCharacterSize(InfoTextSize); labelEnt.getComponent().setFillColour(TextNormalColour); - labelEnt.getComponent().setShadowColour(LeaderboardTextDark); - labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); - labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect | CommandID::Menu::CourseType;; - labelEnt.addComponent().absolutePosition = { 0.f, -30.f }; - labelEnt.getComponent().relativePosition = CourseDescPosition; + labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; + labelEnt.addComponent().absolutePosition = { 187.f, 135.f }; labelEnt.getComponent().depth = 0.1f; - - centreText(labelEnt); - m_menuEntities[MenuID::Lobby].getComponent().addChild(labelEnt.getComponent()); + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } } From 7b6e8bb370db9a47166e32746e76262abac6728e Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 6 Jun 2023 15:39:49 +0100 Subject: [PATCH 010/233] increase max network clients to 8 --- samples/golf/src/golf/CommonConsts.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/golf/src/golf/CommonConsts.hpp b/samples/golf/src/golf/CommonConsts.hpp index daf76a989..b11c1fd12 100644 --- a/samples/golf/src/golf/CommonConsts.hpp +++ b/samples/golf/src/golf/CommonConsts.hpp @@ -57,7 +57,7 @@ namespace ConstVal static constexpr std::size_t MaxStringDataSize = MaxStringChars * sizeof(std::uint32_t); static constexpr std::uint16_t GamePort = 16002; - static constexpr std::uint8_t MaxClients = 4; + static constexpr std::uint8_t MaxClients = 8; static constexpr std::uint8_t MaxPlayers = 8; static constexpr std::uint8_t NullValue = 255; static constexpr std::uint8_t NetChannelReliable = 1; From a0d55d6104633c2f74862a5dad397e694e9f2778 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 6 Jun 2023 19:35:06 +0100 Subject: [PATCH 011/233] update menu --- samples/golf/src/golf/MenuCreation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 01faf9678..4b6c556e3 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1552,10 +1552,11 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //button for course selection entity = m_uiScene.createEntity(); entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 335.f, 35.f }; + entity.addComponent().absolutePosition = { 275.f, 36.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1641,10 +1642,11 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //button for rule selection entity = m_uiScene.createEntity(); entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 110.f, 4.f }; + entity.addComponent().absolutePosition = { 185.f, 5.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); From 7389b4e201505f29a4d5d47d539dc20a6a85192b Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 7 Jun 2023 09:56:56 +0100 Subject: [PATCH 012/233] update lobby layout --- samples/golf/src/golf/MenuAvatars.cpp | 2 +- samples/golf/src/golf/MenuCreation.cpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 89af33305..df3350482 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1873,7 +1873,7 @@ void MenuState::updateLobbyAvatars() { //rank text entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 22.f, 16.f, 0.3f }); + entity.addComponent().setPosition({ 106.f, 28.f, 0.3f }); entity.addComponent(); entity.addComponent(smallFont).setString("Level"); entity.getComponent().setCharacterSize(InfoTextSize); diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 4b6c556e3..467b9605e 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1622,7 +1622,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; entity.addComponent().ID = CommandID::Menu::UIElement; - entity.addComponent().absolutePosition = { 176.f, 141.f }; + entity.addComponent().absolutePosition = { 176.f, 147.f }; entity.getComponent().depth = 0.01f; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); @@ -1676,7 +1676,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st //current rules entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 214.f, 80.f, 0.1f }); + entity.addComponent().setPosition({ 214.f, 86.f, 0.1f }); entity.addComponent(); entity.addComponent().ID = CommandID::Menu::CourseRules; entity.addComponent(smallFont).setCharacterSize(InfoTextSize); @@ -2576,7 +2576,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 175.f, 140.f }; + checkboxEnt.addComponent().absolutePosition = { 175.f, 146.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) @@ -2605,7 +2605,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent(); checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 176.f, 115.f }; + checkboxEnt.addComponent().absolutePosition = { 176.f, 121.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); @@ -2631,7 +2631,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 175.f, 114.f }; + checkboxEnt.addComponent().absolutePosition = { 175.f, 120.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); checkboxEnt.addComponent().area = bounds; @@ -2650,7 +2650,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 187.f, 122.f }; + labelEnt.addComponent().absolutePosition = { 187.f, 128.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); checkboxEnt.getComponent().area.width += cro::Text::getLocalBounds(labelEnt).width; @@ -2665,7 +2665,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 212.f, 107.f }; + labelEnt.addComponent().absolutePosition = { 212.f, 113.f }; labelEnt.getComponent().depth = 0.01f; bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; @@ -2681,7 +2681,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 194.f, 100.f }; + labelEnt.addComponent().absolutePosition = { 194.f, 106.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } @@ -2743,7 +2743,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 175.f, 127.f }; + checkboxEnt.addComponent().absolutePosition = { 175.f, 133.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) @@ -2767,7 +2767,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent(); checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 176.f, 128.f }; + checkboxEnt.addComponent().absolutePosition = { 176.f, 134.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); @@ -2793,7 +2793,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setCharacterSize(InfoTextSize); labelEnt.getComponent().setFillColour(TextNormalColour); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 187.f, 135.f }; + labelEnt.addComponent().absolutePosition = { 187.f, 141.f }; labelEnt.getComponent().depth = 0.1f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } From dbd959294c8e080f40cde5f1f0ddce29a9aca9d4 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 7 Jun 2023 11:36:10 +0100 Subject: [PATCH 013/233] add framework for 'fast CPU' turns --- samples/golf/src/GolfGame.cpp | 5 ++ samples/golf/src/golf/CPUGolfer.cpp | 4 +- samples/golf/src/golf/CPUGolfer.hpp | 2 + samples/golf/src/golf/GolfState.cpp | 39 +++++++++++++++ samples/golf/src/golf/MenuState.cpp | 3 ++ samples/golf/src/golf/PacketIDs.hpp | 2 + samples/golf/src/golf/SharedStateData.hpp | 1 + .../golf/src/golf/server/ServerGolfRules.cpp | 47 +++++++++++++++++++ .../golf/src/golf/server/ServerGolfState.cpp | 14 ++++++ .../golf/src/golf/server/ServerGolfState.hpp | 1 + .../golf/src/golf/server/ServerLobbyState.cpp | 7 +++ samples/golf/src/golf/server/ServerState.hpp | 3 +- 12 files changed, 126 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index d75941191..5a0866a04 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -885,6 +885,10 @@ void GolfGame::loadPreferences() { m_sharedData.trailBeaconColour = prop.getValue(); } + else if (name == "fast_cpu") + { + m_sharedData.fastCPU = prop.getValue(); + } } } } @@ -970,6 +974,7 @@ void GolfGame::savePreferences() cfg.addProperty("use_vibration").setValue(m_sharedData.enableRumble == 0 ? false : true); cfg.addProperty("use_trail").setValue(m_sharedData.showBallTrail); cfg.addProperty("use_beacon_colour").setValue(m_sharedData.trailBeaconColour); + cfg.addProperty("fast_cpu").setValue(m_sharedData.fastCPU); cfg.save(path); diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 685dab44c..40f88150a 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -111,6 +111,7 @@ CPUGolfer::CPUGolfer(const InputParser& ip, const ActivePlayer& ap, const Collis : m_inputParser (ip), m_activePlayer (ap), m_collisionMesh (cm), + m_fastCPU (false), m_puttFromTee (false), m_distanceToPin (1.f), m_target (0.f), @@ -204,7 +205,8 @@ void CPUGolfer::handleMessage(const cro::Message& msg) void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) { - if (m_state == State::Inactive) + if (!m_fastCPU && + m_state == State::Inactive) { m_puttFromTee = puttFromTee; m_fallbackTarget = fallback; diff --git a/samples/golf/src/golf/CPUGolfer.hpp b/samples/golf/src/golf/CPUGolfer.hpp index 1aa004270..1f5f887bf 100644 --- a/samples/golf/src/golf/CPUGolfer.hpp +++ b/samples/golf/src/golf/CPUGolfer.hpp @@ -60,11 +60,13 @@ class CPUGolfer final : public cro::GuiClient std::size_t getSkillIndex() const; + void setFastCPU(bool fast) { m_fastCPU = fast; } private: const InputParser& m_inputParser; //only reads the state - actual commands are send by raising events. const ActivePlayer& m_activePlayer; const CollisionMesh& m_collisionMesh; + bool m_fastCPU; bool m_puttFromTee; float m_distanceToPin; glm::vec3 m_target; diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 29581382a..589263bb3 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -234,6 +234,8 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared m_courseIndex (getCourseIndex(sd.mapDirectory.toAnsiString())), m_emoteWheel (sd, m_currentPlayer) { + m_cpuGolfer.setFastCPU(m_sharedData.fastCPU); + godmode = 1.f; registerCommand("god", [](const std::string&) { @@ -262,6 +264,37 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared } }); + registerCommand("fast_cpu", [&](const std::string& param) + { + if (m_sharedData.hosting) + { + const auto sendCmd = [&]() + { + m_sharedData.clientConnection.netClient.sendPacket(PacketID::FastCPU, m_sharedData.fastCPU ? 1 : 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + + m_cpuGolfer.setFastCPU(m_sharedData.fastCPU); + + //TODO set active or not if current player is CPU + + }; + + if (param == "0") + { + m_sharedData.fastCPU = false; + sendCmd(); + } + else if (param == "1") + { + m_sharedData.fastCPU = true; + sendCmd(); + } + else + { + cro::Console::print("Usage: fast_cpu <0|1>"); + } + } + }); + /*registerCommand("fog", [&](const std::string& param) { if (param.empty()) @@ -5195,6 +5228,10 @@ void GolfState::handleNetEvent(const net::NetEvent& evt) switch (evt.packet.getID()) { default: break; + case PacketID::FastCPU: + m_sharedData.fastCPU = evt.packet.as() != 0; + m_cpuGolfer.setFastCPU(m_sharedData.fastCPU); + break; case PacketID::PlayerXP: { auto value = evt.packet.as(); @@ -6564,6 +6601,8 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) msg2->terrain = m_currentPlayer.terrain; msg2->type = GolfEvent::SetNewPlayer; + m_sharedData.clientConnection.netClient.sendPacket(PacketID::NewPlayer, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + //this is just so that the particle director knows if we're on a new hole if (m_currentPlayer.position == m_holeData[m_currentHole].tee) { diff --git a/samples/golf/src/golf/MenuState.cpp b/samples/golf/src/golf/MenuState.cpp index 6967f73d1..644270207 100644 --- a/samples/golf/src/golf/MenuState.cpp +++ b/samples/golf/src/golf/MenuState.cpp @@ -1795,6 +1795,9 @@ void MenuState::handleNetEvent(const net::NetEvent& evt) updateCourseRuleString(); } break; + case PacketID::FastCPU: + m_sharedData.fastCPU = evt.packet.as() != 0; + break; case PacketID::GimmeRadius: { m_sharedData.gimmeRadius = evt.packet.as(); diff --git a/samples/golf/src/golf/PacketIDs.hpp b/samples/golf/src/golf/PacketIDs.hpp index 02dd17cb2..839da6770 100644 --- a/samples/golf/src/golf/PacketIDs.hpp +++ b/samples/golf/src/golf/PacketIDs.hpp @@ -105,6 +105,7 @@ namespace PacketID PlayerInfo, //< ConnectionData array ServerCommand, //< uint8_t command type TransitionComplete, //< uint8 clientID, signal hole transition completed + NewPlayer, //< animation completed on the client and new player active ReadyQuit, //< uint8 clientID - client wants to toggle skip viewing scores BallPlaced, //< BilliardBallInput with position in offset member SkipTurn, //< uint8 clientID - requests server fast forward current turn @@ -115,6 +116,7 @@ namespace PacketID TurnReady, //< uint8 clientID - ready to take their turn - rebroadcast by server to tell all clients to clear messages MapInfo, //< serialised cro::String containing course directory ScoreType, //< uint8 ScoreType of golf game + FastCPU, //< uint8 0 false else true GimmeRadius, //< uint8 gimme radius of golf HoleCount, //< uint8 0 - 2: all, front or back ReverseCourse, //< uint8 0 false else true diff --git a/samples/golf/src/golf/SharedStateData.hpp b/samples/golf/src/golf/SharedStateData.hpp index 4f5ae1ffb..ce60382e1 100644 --- a/samples/golf/src/golf/SharedStateData.hpp +++ b/samples/golf/src/golf/SharedStateData.hpp @@ -185,6 +185,7 @@ struct SharedStateData final bool showPuttingPower = false; bool showBallTrail = false; bool trailBeaconColour = true; //if false defaults to white + bool fastCPU = true; std::int32_t enableRumble = 1; std::int32_t baseState = 0; //used to tell which state we're returning to from errors etc diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index f84736bae..73cb93e63 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -1,9 +1,56 @@ #include "../PacketIDs.hpp" +#include "../BallSystem.hpp" #include "ServerMessages.hpp" #include "ServerGolfState.hpp" +#include +#include + using namespace sv; +void GolfState::makeCPUMove() +{ + if(m_sharedData.fastCPU + && m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].isCPU) + { + auto& ball = m_playerInfo[0].ballEntity.getComponent(); + if (ball.state == Ball::State::Idle) + { + //wrap in an ent so we can add a small delay + auto entity = m_scene.createEntity(); + entity.addComponent().active = true; + entity.getComponent().setUserData(2.5f); + entity.getComponent().function = + [&](cro::Entity e, float dt) + { + auto& currTime = e.getComponent().getUserData(); + currTime -= dt; + + if (currTime < 0) + { + //TODO pick a position based on CPU skill and current target + //TODO pick a position offset based on CPU skill / accuracy + //TODO test terrain height and correct final position + + auto pos = m_playerInfo[0].position - m_holeData[m_currentHole].pin; + pos = glm::normalize(pos) * 6.f; + + m_playerInfo[0].ballEntity.getComponent().setPosition(pos + m_holeData[m_currentHole].pin); + ball.terrain = TerrainID::Green; + ball.state = Ball::State::Paused; + + + m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + e.getComponent().active = false; + m_scene.destroyEntity(e); + } + }; + + m_sharedData.host.broadcastPacket(PacketID::CPUThink, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + } + } +} + void GolfState::handleDefaultRules(const GolfBallEvent& data) { if (data.type == GolfBallEvent::TurnEnded) diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 1039da33b..017bef347 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -256,6 +256,10 @@ void GolfState::netEvent(const net::NetEvent& evt) switch (evt.packet.getID()) { default: break; + case PacketID::NewPlayer: + //checks if player is CPU and requires fast move + makeCPUMove(); + break; case PacketID::SkipTurn: skipCurrentTurn(evt.packet.as()); break; @@ -295,6 +299,16 @@ void GolfState::netEvent(const net::NetEvent& evt) case PacketID::ReadyQuit: checkReadyQuit(evt.packet.as()); break; + case PacketID::FastCPU: + if (evt.peer.getID() == m_sharedData.hostID) + { + m_sharedData.scoreType = evt.packet.as(); + m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.scoreType, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + + //this checks current player is CPU and launch a move if inactive + makeCPUMove(); + } + break; } } } diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 375723d09..513e2a443 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -85,6 +85,7 @@ namespace sv void initScene(); void buildWorld(); + void makeCPUMove(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); diff --git a/samples/golf/src/golf/server/ServerLobbyState.cpp b/samples/golf/src/golf/server/ServerLobbyState.cpp index 3dd5d3964..7e9de86d7 100644 --- a/samples/golf/src/golf/server/ServerLobbyState.cpp +++ b/samples/golf/src/golf/server/ServerLobbyState.cpp @@ -107,6 +107,13 @@ void LobbyState::netEvent(const net::NetEvent& evt) m_sharedData.host.broadcastPacket(PacketID::ScoreType, m_sharedData.scoreType, net::NetFlag::Reliable, ConstVal::NetChannelReliable); } break; + case PacketID::FastCPU: + if (evt.peer.getID() == m_sharedData.hostID) + { + m_sharedData.scoreType = evt.packet.as(); + m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.scoreType, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + } + break; case PacketID::GimmeRadius: if (evt.peer.getID() == m_sharedData.hostID) { diff --git a/samples/golf/src/golf/server/ServerState.hpp b/samples/golf/src/golf/server/ServerState.hpp index 0dc44cf5f..ab3db0da0 100644 --- a/samples/golf/src/golf/server/ServerState.hpp +++ b/samples/golf/src/golf/server/ServerState.hpp @@ -75,7 +75,8 @@ namespace sv std::uint8_t scoreType = 0; std::uint8_t gimmeRadius = 0; std::uint8_t holeCount = 0; - std::uint8_t reverseCourse = false; + std::uint8_t reverseCourse = 0; + std::uint8_t fastCPU = 1; std::atomic_uint64_t hostID = 0; }; From 7c72db730a303f8318b14170dc0c82ec32c08e62 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 7 Jun 2023 15:26:44 +0100 Subject: [PATCH 014/233] begin implementing fast CPU logic --- samples/golf/src/golf/Clubs.cpp | 5 + samples/golf/src/golf/Clubs.hpp | 1 + samples/golf/src/golf/GolfState.cpp | 15 +- samples/golf/src/golf/MenuAvatars.cpp | 2 +- .../golf/src/golf/server/ServerGolfRules.cpp | 198 +++++++++++++++++- .../golf/src/golf/server/ServerGolfState.cpp | 16 +- .../golf/src/golf/server/ServerGolfState.hpp | 2 + 7 files changed, 223 insertions(+), 16 deletions(-) diff --git a/samples/golf/src/golf/Clubs.cpp b/samples/golf/src/golf/Clubs.cpp index 9de43cb8a..bc0dc2b5e 100644 --- a/samples/golf/src/golf/Clubs.cpp +++ b/samples/golf/src/golf/Clubs.cpp @@ -178,6 +178,11 @@ float Club::getBaseTarget() const return ClubStats[m_id].stats[0].target; } +float Club::getTargetAtLevel(std::int32_t level) const +{ + return ClubStats[m_id].stats[level].target; +} + float Club::getDefaultTarget() const { return ClubStats[m_id].stats[0].target; diff --git a/samples/golf/src/golf/Clubs.hpp b/samples/golf/src/golf/Clubs.hpp index 566929ff8..38bcf004a 100644 --- a/samples/golf/src/golf/Clubs.hpp +++ b/samples/golf/src/golf/Clubs.hpp @@ -88,6 +88,7 @@ class Club final float getBaseTarget() const; float getDefaultTarget() const; //target with no level-shift applied + float getTargetAtLevel(std::int32_t level) const; float getSideSpinMultiplier() const { return m_sidespin; } float getTopSpinMultiplier() const { return m_topspin; } diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 589263bb3..d566d30dc 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -264,6 +264,7 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared } }); +#ifdef CRO_DEBUG_ registerCommand("fast_cpu", [&](const std::string& param) { if (m_sharedData.hosting) @@ -314,6 +315,7 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared glUniform1f(uniform, std::clamp(f, 0.f, 1.f)); } });*/ +#endif shadowQuality.update(sd.hqShadows); @@ -6517,7 +6519,8 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) if (player.client != m_sharedData.localConnectionData.connectionID || (player.client == m_sharedData.localConnectionData.connectionID && - m_sharedData.localConnectionData.playerData[player.player].isCPU)) + m_sharedData.localConnectionData.playerData[player.player].isCPU && + !m_sharedData.fastCPU)) { static constexpr float MinCamDist = 25.f; if (cro::Util::Random::value(0,2) != 0 && @@ -6527,7 +6530,7 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) entity.addComponent().active = true; entity.getComponent().setUserData(2.7f); entity.getComponent().function = - [&/*, player, playerRotation*/](cro::Entity e, float dt) + [&](cro::Entity e, float dt) { auto& currTime = e.getComponent().getUserData(); currTime -= dt; @@ -6602,6 +6605,14 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) msg2->type = GolfEvent::SetNewPlayer; m_sharedData.clientConnection.netClient.sendPacket(PacketID::NewPlayer, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + //if we're using fast CPU we need to pre-increment stroke so + //that the UI displays correct score while waiting for the + //arbiter score from the server. + if (isCPU && m_sharedData.fastCPU) + { + m_sharedData.connectionData[m_currentPlayer.client].playerData[m_currentPlayer.player].holeScores[m_currentHole]++; + } + //this is just so that the particle director knows if we're on a new hole if (m_currentPlayer.position == m_holeData[m_currentHole].tee) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index df3350482..978dfd47a 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1998,7 +1998,7 @@ void MenuState::updateLobbyAvatars() rankEnt.getComponent().addChild(entity.getComponent()); children.push_back(entity); - rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); + //rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); } //else //{ diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 73cb93e63..35cc8f74f 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -1,5 +1,6 @@ #include "../PacketIDs.hpp" #include "../BallSystem.hpp" +#include "../Clubs.hpp" #include "ServerMessages.hpp" #include "ServerGolfState.hpp" @@ -7,6 +8,26 @@ #include #include +#include + +namespace +{ + glm::vec3 randomNormal() + { + glm::vec2 v( + static_cast(cro::Util::Random::value(1, 10)) / 10.f, + static_cast(cro::Util::Random::value(1, 10)) / 10.f); + + v.x *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; + v.y *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; + v = glm::normalize(v); + + CRO_ASSERT(!std::isnan(v.x), ""); + return { v.x, 0.f, -v.y }; + //return { 1.f, 0.f, 0.f }; + } +} + using namespace sv; void GolfState::makeCPUMove() { @@ -28,17 +49,7 @@ void GolfState::makeCPUMove() if (currTime < 0) { - //TODO pick a position based on CPU skill and current target - //TODO pick a position offset based on CPU skill / accuracy - //TODO test terrain height and correct final position - - auto pos = m_playerInfo[0].position - m_holeData[m_currentHole].pin; - pos = glm::normalize(pos) * 6.f; - - m_playerInfo[0].ballEntity.getComponent().setPosition(pos + m_holeData[m_currentHole].pin); - ball.terrain = TerrainID::Green; - ball.state = Ball::State::Paused; - + calcCPUPosition(); m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); e.getComponent().active = false; @@ -51,6 +62,171 @@ void GolfState::makeCPUMove() } } +void GolfState::calcCPUPosition() +{ + auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; + auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; + + glm::vec3 pos = glm::vec3(0.f); + std::int32_t skill = m_skillIndex; + std::int32_t offset = m_playerInfo[0].player % 2; + if (m_skillIndex > 2) + { + offset *= -1; + } + skill += offset; + + //get longest range available + auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); + if (dist > 115.f) //forces a cut-off for pitch n putt + { + dist = 1000.f; + } + + std::int32_t clubID = ClubID::SandWedge; + while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist + && clubID != ClubID::Driver) + { + do + { + clubID--; + } while (clubID != ClubID::Driver); + } + const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); + + + //if both the pin and the target are in front of the player + if (glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)) > 0.4) + { + //set the target depending on how close it is + const auto pinDist = glm::length2(pinDir); + const auto targetDist = glm::length2(targetDir); + if (pinDist < targetDist) + { + //always target pin if its closer + pos = m_holeData[m_currentHole].pin; + } + + //target the pin if its in range of our longest club + //and CPU skill > something + else if (/*pinDist < (clubDist * clubDist) + &&*/ m_skillIndex > 2) + { + pos = m_holeData[m_currentHole].pin; + } + + else + { + //target the pin if the target is too close + const float MinDist = m_holeData[m_currentHole].puttFromTee ? 9.f : 2500.f; + if (targetDist < MinDist) //remember this in len2 + { + pos = m_holeData[m_currentHole].pin; + } + else + { + pos = m_holeData[m_currentHole].target; + } + } + } + else + { + //else set the pin as the target + pos = m_holeData[m_currentHole].pin; + } + + //reduce the target distance so that it's in range of our longest club + if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > + (clubDist * clubDist)) + { + const float reduction = clubDist / std::sqrt(len2); + pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; + } + + //make sure there's only a slim chance of getting it in the hole + //if club is not a putter, and VERY slim chance if not a wedge + auto& ball = m_playerInfo[0].ballEntity.getComponent(); + if (ball.terrain == TerrainID::Green) + { + if (cro::Util::Random::value(0, 3 + skill) == 0) + { + //add target offset + pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); + + CRO_ASSERT(!std::isnan(pos.x), ""); + } + } + else + { + //we're probably chipping + if (glm::length2(pos - m_playerInfo[0].position) < (90.f * 90.f)) + { + if (cro::Util::Random::value(0, 4 + skill) < 8) + { + //add offset + pos += randomNormal() * static_cast(cro::Util::Random::value(6, 70)) / 10.f; + + CRO_ASSERT(!std::isnan(pos.x), ""); + } + } + else + { + //iron or driver so add some arbitrary offset + //based on bounce and wind strength/dir and distance to pos + + //TODO we need a good bounce as a percentage value... + constexpr float BouncePercent = 1.05f; + pos = ((pos - m_playerInfo[0].position) * BouncePercent) + m_playerInfo[0].position; + + auto step = pos / 5.f; + pos = m_playerInfo[0].position + step; + + for (auto i = 0; i < 4; ++i) + { + const auto wind = m_scene.getSystem()->getWindDirection(); + step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (6 - skill)); + pos += step; + } + CRO_ASSERT(!std::isnan(pos.x), ""); + } + } + pos.x = std::clamp(pos.x, 0.f, 320.f); + pos.z = std::clamp(pos.z, -200.f, 0.f); + + //test terrain height and correct final position + auto result = m_scene.getSystem()->getTerrain(pos); + pos.y = result.intersection.y; + + CRO_ASSERT(!std::isnan(pos.x), ""); + CRO_ASSERT(!std::isnan(pos.y), ""); + CRO_ASSERT(!std::isnan(pos.z), ""); + + m_playerInfo[0].ballEntity.getComponent().setPosition(pos); + m_playerInfo[0].holeScore[m_currentHole]++; + + ball.terrain = result.terrain; + switch (result.terrain) + { + default: + ball.state = Ball::State::Paused; + break; + case TerrainID::Fairway: + case TerrainID::Bunker: + case TerrainID::Rough: + ball.state = Ball::State::Flight; + break; + case TerrainID::Green: + case TerrainID::Hole: + ball.state = Ball::State::Putt; + break; + case TerrainID::Scrub: + case TerrainID::Stone: + case TerrainID::Water: + ball.state = Ball::State::Reset; + break; + } +} + void GolfState::handleDefaultRules(const GolfBallEvent& data) { if (data.type == GolfBallEvent::TurnEnded) diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 017bef347..51330f75c 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -36,6 +36,7 @@ source distribution. #include "ServerMessages.hpp" #include +#include #include #include @@ -70,12 +71,16 @@ GolfState::GolfState(SharedData& sd) m_allMapsLoaded (false), m_currentHole (0), m_skinsPot (1), - m_currentBest (MaxStrokes) + m_currentBest (MaxStrokes), + m_skillIndex (0) { if (m_mapDataValid = validateMap(); m_mapDataValid) { initScene(); buildWorld(); + + auto level = std::min(Social::getLevel(), 20) + 2; + m_skillIndex = level / 4; } LOG("Entered Server Golf State", cro::Logger::Type::Info); @@ -899,7 +904,7 @@ bool GolfState::validateMap() return false; } - static constexpr std::int32_t MaxProps = 4; + static constexpr std::int32_t MaxProps = 5; std::int32_t propCount = 0; auto& holeData = m_holeData.emplace_back(); @@ -923,6 +928,13 @@ bool GolfState::validateMap() holeData.tee.z = glm::clamp(holeData.tee.z, -200.f, 0.f); propCount++; } + else if (name == "target") + { + holeData.target = holeProp.getValue(); + holeData.target.x = glm::clamp(holeData.target.x, 0.f, 320.f); + holeData.target.z = glm::clamp(holeData.target.z, -200.f, 0.f); + propCount++; + } else if (name == "par") { holeData.par = holeProp.getValue(); diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 513e2a443..c58ac7bf6 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -72,6 +72,7 @@ namespace sv std::uint8_t m_currentBest; //current best score for hole, non-stroke games end if no-one can beat it cro::Clock m_turnTimer; + std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level void sendInitialGameState(std::uint8_t); void handlePlayerInput(const net::NetEvent::Packet&, bool predict); @@ -86,6 +87,7 @@ namespace sv void buildWorld(); void makeCPUMove(); + void calcCPUPosition(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From a822a1be76a9dd2858d4adc69fb215f10c196e34 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 7 Jun 2023 16:01:33 +0100 Subject: [PATCH 015/233] add fast CPU option to lobby window --- samples/golf/src/golf/GolfStateUI.cpp | 1 + samples/golf/src/golf/MenuAvatars.cpp | 12 ++++++ samples/golf/src/golf/MenuCreation.cpp | 53 +++++++++++++++++++++++--- samples/golf/src/golf/MenuState.hpp | 1 + 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index ce738e71e..c9bb1a9cc 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -898,6 +898,7 @@ void GolfState::buildUI() entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ 0.f, 82.f }); entity.getComponent().setRotation(-90.f * cro::Util::Const::degToRad); + entity.getComponent().setOrigin({ 0.f, 0.f, 0.1f }); entity.getComponent().setScale(glm::vec2(0.f)); entity.addComponent().setShader(&m_resources.shaders.get(ShaderID::MinimapView)); entity.addComponent(); diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 978dfd47a..03a7a2e55 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1326,6 +1326,18 @@ void MenuState::createMenuCallbacks() } }); + m_courseSelectCallbacks.toggleFastCPU = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + //we rely on the server reply to actually set shared data value + m_sharedData.clientConnection.netClient.sendPacket(PacketID::FastCPU, m_sharedData.fastCPU ? 0 : 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + + m_audioEnts[AudioID::Back].getComponent().play(); + } + }); + m_courseSelectCallbacks.toggleGameRules = m_uiScene.getSystem()->addCallback( [&](cro::Entity e, const cro::ButtonEvent& evt) { diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 467b9605e..f9efcb211 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1639,6 +1639,29 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st }; thumbBgEnt.getComponent().addChild(entity.getComponent()); + //checkbox to show fast CPU status + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().absolutePosition = { 176.f, 121.f }; + entity.getComponent().depth = 0.01f; + + bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); + entity.addComponent().active = true; + entity.getComponent().function = + [&, bounds](cro::Entity e, float) + { + auto b = bounds; + if (m_sharedData.fastCPU) + { + b.bottom -= bounds.height; + } + e.getComponent().setTextureRect(b); + }; + thumbBgEnt.getComponent().addChild(entity.getComponent()); + //button for rule selection entity = m_uiScene.createEntity(); entity.addComponent(); @@ -2595,6 +2618,26 @@ void MenuState::addCourseSelectButtons() m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); + //toggle CPU skip + checkboxEnt = m_uiScene.createEntity(); + checkboxEnt.addComponent(); + checkboxEnt.addComponent() = m_menuSounds.getEmitter("switch"); + checkboxEnt.addComponent(); + checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; + checkboxEnt.getComponent().setColour(cro::Colour::Transparent); + checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; + checkboxEnt.addComponent().absolutePosition = { 175.f, 120.f }; + checkboxEnt.getComponent().depth = 0.01f; + bounds = checkboxEnt.getComponent().getTextureBounds(); + checkboxEnt.addComponent().area = bounds; + checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFastCPU; + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); + + + auto& font = m_sharedData.sharedResources->fonts.get(FontID::Info); if (Social::isAvailable()) @@ -2605,7 +2648,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent(); checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckbox]; checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 176.f, 121.f }; + checkboxEnt.addComponent().absolutePosition = { 176.f, 109.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = m_sprites[SpriteID::LobbyCheckbox].getTextureRect(); @@ -2631,7 +2674,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent() = m_sprites[SpriteID::LobbyCheckboxHighlight]; checkboxEnt.getComponent().setColour(cro::Colour::Transparent); checkboxEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - checkboxEnt.addComponent().absolutePosition = { 175.f, 120.f }; + checkboxEnt.addComponent().absolutePosition = { 175.f, 108.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); checkboxEnt.addComponent().area = bounds; @@ -2650,7 +2693,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 187.f, 128.f }; + labelEnt.addComponent().absolutePosition = { 187.f, 116.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); checkboxEnt.getComponent().area.width += cro::Text::getLocalBounds(labelEnt).width; @@ -2665,7 +2708,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 212.f, 113.f }; + labelEnt.addComponent().absolutePosition = { 72.f, 29.f }; labelEnt.getComponent().depth = 0.01f; bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; @@ -2681,7 +2724,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 194.f, 106.f }; + labelEnt.addComponent().absolutePosition = { 54.f, 22.f }; //194, 106 labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index 88d91ebcb..dc54f0e83 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -171,6 +171,7 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co std::uint32_t nextHoleType = 0; std::uint32_t toggleReverseCourse = 0; std::uint32_t toggleFriendsOnly = 0; + std::uint32_t toggleFastCPU = 0; std::uint32_t toggleGameRules = 0; std::uint32_t inviteFriends = 0; std::uint32_t selected = 0; From 6d3bc643ece3023394f03b6e9caffea311aae521 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 7 Jun 2023 19:55:37 +0100 Subject: [PATCH 016/233] fix fastCPU param --- samples/golf/src/golf/server/ServerGolfState.cpp | 4 ++-- samples/golf/src/golf/server/ServerLobbyState.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 51330f75c..e64e85440 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -307,8 +307,8 @@ void GolfState::netEvent(const net::NetEvent& evt) case PacketID::FastCPU: if (evt.peer.getID() == m_sharedData.hostID) { - m_sharedData.scoreType = evt.packet.as(); - m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.scoreType, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + m_sharedData.fastCPU = evt.packet.as(); + m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.fastCPU, net::NetFlag::Reliable, ConstVal::NetChannelReliable); //this checks current player is CPU and launch a move if inactive makeCPUMove(); diff --git a/samples/golf/src/golf/server/ServerLobbyState.cpp b/samples/golf/src/golf/server/ServerLobbyState.cpp index 7e9de86d7..7647c9b21 100644 --- a/samples/golf/src/golf/server/ServerLobbyState.cpp +++ b/samples/golf/src/golf/server/ServerLobbyState.cpp @@ -110,8 +110,8 @@ void LobbyState::netEvent(const net::NetEvent& evt) case PacketID::FastCPU: if (evt.peer.getID() == m_sharedData.hostID) { - m_sharedData.scoreType = evt.packet.as(); - m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.scoreType, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + m_sharedData.fastCPU = evt.packet.as(); + m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.fastCPU, net::NetFlag::Reliable, ConstVal::NetChannelReliable); } break; case PacketID::GimmeRadius: From 11caea725719aebe379fe0017988c6abdf800ebb Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 10:07:57 +0100 Subject: [PATCH 017/233] realign lobby buttons --- samples/golf/src/golf/MenuCreation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index f9efcb211..1af3b8949 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1556,7 +1556,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 275.f, 36.f }; + entity.addComponent().absolutePosition = { 232.f, 33.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1669,7 +1669,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 185.f, 5.f }; + entity.addComponent().absolutePosition = { 92.f, 2.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); From 16e1e85f6c605b6a3c0e75f632a2de65c8eedf6b Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 11:17:39 +0100 Subject: [PATCH 018/233] add info tab to lobby --- samples/golf/src/golf/MenuAvatars.cpp | 21 -- samples/golf/src/golf/MenuCreation.cpp | 289 +++++++++++++++++++------ samples/golf/src/golf/MenuState.hpp | 2 +- 3 files changed, 229 insertions(+), 83 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 03a7a2e55..5cdac09c6 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1338,27 +1338,6 @@ void MenuState::createMenuCallbacks() } }); - m_courseSelectCallbacks.toggleGameRules = m_uiScene.getSystem()->addCallback( - [&](cro::Entity e, const cro::ButtonEvent& evt) - { - if (activated(evt)) - { - float scale = m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y; - if (scale == 0) - { - scale = 1.f; - m_audioEnts[AudioID::Back].getComponent().play(); - } - else - { - scale = 0.f; - m_audioEnts[AudioID::Accept].getComponent().play(); - } - - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 1.f, scale }); - } - }); - m_courseSelectCallbacks.inviteFriends = m_uiScene.getSystem()->addCallback( [&](cro::Entity, const cro::ButtonEvent& evt) { diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 1af3b8949..33eda3223 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1549,6 +1549,14 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent().ID = CommandID::Menu::UIElement; bgEnt.getComponent().addChild(entity.getComponent()); + const auto ruleButtonEnable = [&](cro::Entity e, float) + { + //hack to disable the button when course selection is visible + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y + + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().getScale().y == 0; + }; + //button for course selection entity = m_uiScene.createEntity(); entity.addComponent(); @@ -1562,18 +1570,56 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().setGroup(MenuID::Lobby); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; - entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleGameRules; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 1.f, 1.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 0.f, 0.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); entity.addComponent().ID = CommandID::Menu::UIElement; entity.addComponent().active = true; - entity.getComponent().function = - [&](cro::Entity e, float) - { - //hack to disable the button when course selction is visible - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; - }; + entity.getComponent().function = ruleButtonEnable; bgEnt.getComponent().addChild(entity.getComponent()); + //button for lobby info + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 409.f, 33.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 0.f, 0.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 1.f, 1.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = ruleButtonEnable; + bgEnt.getComponent().addChild(entity.getComponent()); + + //display lobby members - updateLobbyAvatars() adds the text ents to this. entity = m_uiScene.createEntity(); @@ -1594,7 +1640,6 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent().absolutePosition = { 230.f, 31.f }; entity.getComponent().depth = 1.2f; entity.addComponent().ID = CommandID::Menu::UIElement; - bounds = entity.getComponent().getTextureBounds(); bgEnt.getComponent().addChild(entity.getComponent()); auto thumbBgEnt = entity; m_lobbyWindowEntities[LobbyEntityID::HoleSelection] = thumbBgEnt; @@ -1662,6 +1707,14 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st }; thumbBgEnt.getComponent().addChild(entity.getComponent()); + + const auto courseButtonEnable = + [&](cro::Entity e, float) + { + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; + }; + //button for rule selection entity = m_uiScene.createEntity(); entity.addComponent(); @@ -1675,10 +1728,57 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().setGroup(MenuID::Lobby); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; - entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleGameRules; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 0.f, 0.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 0.f, 0.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = courseButtonEnable; thumbBgEnt.getComponent().addChild(entity.getComponent()); + //button for info page + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 180.f, 2.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 0.f, 0.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 1.f, 1.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = courseButtonEnable; + thumbBgEnt.getComponent().addChild(entity.getComponent()); + + + //course title entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ 134.f, 192.f, 0.1f }); @@ -1708,6 +1808,89 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().setAlignment(cro::Text::Alignment::Centre); thumbBgEnt.getComponent().addChild(entity.getComponent()); + + + //displays the player info + entity = m_uiScene.createEntity(); + entity.addComponent().setScale({ 0.f, 0.f }); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("info_background"); + entity.addComponent().absolutePosition = { 230.f, 31.f }; + entity.getComponent().depth = 1.3f; + entity.addComponent().ID = CommandID::Menu::UIElement; + bgEnt.getComponent().addChild(entity.getComponent()); + auto infoBgEnt = entity; + m_lobbyWindowEntities[LobbyEntityID::Info] = infoBgEnt; + + const auto infoButtonEnable = + [&](cro::Entity e, float) + { + e.getComponent().active = + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().getScale().y != 0.f; + }; + + //button for rule selection + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 92.f, 2.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 0.f, 0.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 0.f, 0.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = infoButtonEnable; + infoBgEnt.getComponent().addChild(entity.getComponent()); + + //button for course selection + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 3.f, 2.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().setScale({ 1.f, 1.f }); + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().setScale({ 0.f, 0.f }); + + m_audioEnts[AudioID::Accept].getComponent().play(); + m_uiScene.getActiveCamera().getComponent().active = true; //forces a visibility refresh + } + } + ); + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = infoButtonEnable; + infoBgEnt.getComponent().addChild(entity.getComponent()); + //banner entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ 0.f, BannerPosition, -0.1f }); @@ -2427,6 +2610,13 @@ void MenuState::quitLobby() void MenuState::addCourseSelectButtons() { + const auto gameRuleEnable = [&](cro::Entity e, float) + { + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y + + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().getScale().y == 0; + }; + //choose scoring type auto buttonEnt = m_uiScene.createEntity(); buttonEnt.addComponent(); @@ -2444,12 +2634,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRules; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; - }; + buttonEnt.getComponent().function = gameRuleEnable; + buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); @@ -2471,12 +2657,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRules; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; - }; + buttonEnt.getComponent().function = gameRuleEnable; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); @@ -2499,12 +2680,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRadius; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; - }; + buttonEnt.getComponent().function = gameRuleEnable; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); @@ -2526,17 +2702,17 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRadius; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y == 0; - }; + buttonEnt.getComponent().function = gameRuleEnable; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::Background].getComponent().addChild(buttonEnt.getComponent()); - + const auto courseButtonEnable = + [&](cro::Entity e, float) + { + e.getComponent().enabled = + m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; + }; //hole count buttons. buttonEnt = m_uiScene.createEntity(); @@ -2555,12 +2731,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleCount; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; - }; + buttonEnt.getComponent().function = courseButtonEnable; + buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2580,12 +2752,7 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextHoleCount; buttonEnt.addComponent().active = true; - buttonEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; - }; + buttonEnt.getComponent().function = courseButtonEnable; buttonEnt.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2609,12 +2776,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleReverseCourse; checkboxEnt.addComponent().active = true; - checkboxEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; - }; + checkboxEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); @@ -2634,6 +2796,8 @@ void MenuState::addCourseSelectButtons() checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFastCPU; + checkboxEnt.addComponent().active = true; + checkboxEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); @@ -2682,6 +2846,8 @@ void MenuState::addCourseSelectButtons() checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFriendsOnly; + checkboxEnt.addComponent().active = true; + checkboxEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); auto labelEnt = m_uiScene.createEntity(); @@ -2702,7 +2868,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_menuSounds.getEmitter("switch"); labelEnt.addComponent(); - labelEnt.addComponent(font).setString("Invite"); + labelEnt.addComponent(font).setString("Invite Friends"); labelEnt.getComponent().setCharacterSize(InfoTextSize); labelEnt.getComponent().setFillColour(TextNormalColour); labelEnt.getComponent().setShadowColour(LeaderboardTextDark); @@ -2716,6 +2882,8 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectText; labelEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectText; labelEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.inviteFriends; + labelEnt.addComponent().active = true; + labelEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); @@ -2724,7 +2892,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 54.f, 22.f }; //194, 106 + labelEnt.addComponent().absolutePosition = { 54.f, 22.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } @@ -2751,6 +2919,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevCourse; + buttonEnt.addComponent().active = true; + buttonEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2772,6 +2942,8 @@ void MenuState::addCourseSelectButtons() buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextCourse; + buttonEnt.addComponent().active = true; + buttonEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(buttonEnt.getComponent()); @@ -2796,12 +2968,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleType; checkboxEnt.addComponent().active = true; - checkboxEnt.getComponent().function = - [&](cro::Entity e, float) - { - e.getComponent().enabled = - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().y != 0; - }; + checkboxEnt.getComponent().function = courseButtonEnable; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(checkboxEnt.getComponent()); diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index dc54f0e83..64230971c 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -172,7 +172,6 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co std::uint32_t toggleReverseCourse = 0; std::uint32_t toggleFriendsOnly = 0; std::uint32_t toggleFastCPU = 0; - std::uint32_t toggleGameRules = 0; std::uint32_t inviteFriends = 0; std::uint32_t selected = 0; std::uint32_t unselected = 0; @@ -199,6 +198,7 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co HoleThumb, CourseTicker, Background, + Info, Count }; From 0b777693161a372796c590c830a7e22b39f11ef2 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 12:28:19 +0100 Subject: [PATCH 019/233] update info panel layout --- samples/golf/src/golf/MenuAvatars.cpp | 175 ++++++++++++++----------- samples/golf/src/golf/MenuCreation.cpp | 5 +- samples/golf/src/golf/MenuState.hpp | 2 - 3 files changed, 103 insertions(+), 79 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 5cdac09c6..73f5c38c3 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1383,19 +1383,6 @@ void MenuState::createMenuCallbacks() { e.getComponent().setFillColour(TextNormalColour); }); - - - - m_courseSelectCallbacks.showTip = m_uiScene.getSystem()->addCallback( - [&](cro::Entity e, glm::vec2, const cro::MotionEvent&) - { - showToolTip(RuleDescriptions[m_sharedData.scoreType]); - }); - m_courseSelectCallbacks.hideTip = m_uiScene.getSystem()->addCallback( - [&](cro::Entity e, glm::vec2, const cro::MotionEvent&) - { - hideToolTip(); - }); } void MenuState::createProfileLayout(cro::Entity parent, cro::Transform& menuTransform, const cro::SpriteSheet& spriteSheet) @@ -1856,72 +1843,109 @@ void MenuState::updateLobbyAvatars() e.getComponent().addChild(entity.getComponent()); children.push_back(entity); - //if this is our local client then add the current xp level - //TODO this made sense when it was applied to each client (and ideally - //we want to bring that back) otherwise we could do this just once in - //a more sane manner somewhere else. - if (h == m_sharedData.clientConnection.connectionID) + + //rank text + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 100.f, iconPos.y + 198.f, 0.3f }); + entity.addComponent(); + entity.addComponent(smallFont).setString("Level"); + entity.getComponent().setCharacterSize(InfoTextSize); + entity.getComponent().setFillColour(TextNormalColour); + entity.getComponent().setShadowColour(LeaderboardTextDark); + entity.getComponent().setShadowOffset({ 1.f, -1.f }); + entity.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); + + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) { - //rank text - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 106.f, 28.f, 0.3f }); - entity.addComponent(); - entity.addComponent(smallFont).setString("Level"); - entity.getComponent().setCharacterSize(InfoTextSize); - entity.getComponent().setFillColour(TextNormalColour); - entity.getComponent().setShadowColour(LeaderboardTextDark); - entity.getComponent().setShadowOffset({ 1.f, -1.f }); - entity.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); - - /*entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) + if (m_sharedData.connectionData[h].playerCount == 0) { - //currently only applying to local player so there's - //always at least one - if (m_sharedData.connectionData[h].playerCount == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else - { - ent.getComponent().setScale(glm::vec2(1.f)); - ent.getComponent().setString("Level " + std::to_string(m_sharedData.connectionData[h].level)); + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + std::string str = "Level " + std::to_string(m_sharedData.connectionData[h].level); + str += " " + std::to_string(m_sharedData.connectionData[h].playerCount) + " player(s)"; + ent.getComponent().setScale(glm::vec2(1.f)); + ent.getComponent().setString(str); - float offset = ent.getComponent().getUserData(); - ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), -160.f, 0.1f }); - } - };*/ - m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(entity.getComponent()); - children.push_back(entity); - auto rankEnt = entity; + //float offset = ent.getComponent().getUserData(); + //ent.getComponent().setPosition({ std::floor((56.f + (m_lobbyExpansion / 2.f)) - offset), -160.f, 0.1f }); + } + }; + m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().addChild(entity.getComponent()); + children.push_back(entity); + auto rankEnt = entity; - //add a rank badge - entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ -18.f, -12.f, 0.2f }); - entity.addComponent(); - entity.addComponent() = m_sprites[SpriteID::LevelBadge]; - entity.addComponent(); - - entity.addComponent().active = true; - entity.getComponent().function = - [&, h](cro::Entity ent, float) + //add a rank badge + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ -18.f, -12.f, 0.2f }); + entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::LevelBadge]; + entity.addComponent(); + + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) + { + if (m_sharedData.connectionData[h].playerCount == 0 + || m_sharedData.connectionData[h].level == 0) { - if (m_sharedData.connectionData[h].playerCount == 0 - || m_sharedData.connectionData[h].level == 0) - { - ent.getComponent().setScale(glm::vec2(0.f)); - } - else - { - ent.getComponent().setScale(glm::vec2(1.f)); - auto index = std::min(5, m_sharedData.connectionData[h].level / 10); - ent.getComponent().play(index); - } - }; - rankEnt.getComponent().addChild(entity.getComponent()); - children.push_back(entity); + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + ent.getComponent().setScale(glm::vec2(1.f)); + auto index = std::min(5, m_sharedData.connectionData[h].level / 10); + ent.getComponent().play(index); + } + }; + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); + + //add an avatar icon + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ -98.f, -11.f, 0.2f }); + entity.getComponent().setScale({ 0.2f, 0.2f }); + entity.addComponent(); + entity.addComponent(m_sharedData.nameTextures[h].getTexture()); + cro::FloatRect bounds = { 0.f, LabelTextureSize.y - (LabelIconSize.y * 4.f), LabelIconSize.x, LabelIconSize.y }; + entity.getComponent().setTextureRect(bounds); + entity.addComponent(); + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); + + //add a network icon + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition(glm::vec3(-82.f, -12.f, 0.1f)); + entity.addComponent(); + entity.addComponent() = m_sprites[SpriteID::NetStrength]; + entity.addComponent(); + entity.addComponent().active = true; + entity.getComponent().function = + [&, h](cro::Entity ent, float) + { + if (m_sharedData.connectionData[h].playerCount == 0) + { + ent.getComponent().setScale(glm::vec2(0.f)); + } + else + { + ent.getComponent().setScale(glm::vec2(1.f)); + auto index = std::min(4u, m_sharedData.connectionData[h].pingTime / 30); + ent.getComponent().play(index); + } + }; + entity.addComponent().ID = CommandID::Menu::LobbyText; + rankEnt.getComponent().addChild(entity.getComponent()); + children.push_back(entity); + + + //if this is our local client then add the current xp level + if (h == m_sharedData.clientConnection.connectionID) + { //level progress constexpr float BarWidth = 80.f; constexpr float BarHeight = 10.f; @@ -1991,11 +2015,12 @@ void MenuState::updateLobbyAvatars() //rankEnt.getComponent().setUserData((BarWidth / 2.f) - 7.f); } + //no need to do this now as we don't dynamically re-align //else //{ // //text width - // auto bounds = cro::Text::getLocalBounds(rankEnt); - // rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); + // //auto bounds = cro::Text::getLocalBounds(rankEnt); + // //rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); //} } h++; diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 33eda3223..e4eb61a36 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -2791,6 +2791,7 @@ void MenuState::addCourseSelectButtons() checkboxEnt.addComponent().absolutePosition = { 175.f, 120.f }; checkboxEnt.getComponent().depth = 0.01f; bounds = checkboxEnt.getComponent().getTextureBounds(); + bounds.width += 78.f; checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; @@ -2874,7 +2875,7 @@ void MenuState::addCourseSelectButtons() labelEnt.getComponent().setShadowColour(LeaderboardTextDark); labelEnt.getComponent().setShadowOffset({ 1.f, -1.f }); labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 72.f, 29.f }; + labelEnt.addComponent().absolutePosition = { 58.f, 29.f }; labelEnt.getComponent().depth = 0.01f; bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; @@ -2892,7 +2893,7 @@ void MenuState::addCourseSelectButtons() labelEnt.addComponent(); labelEnt.addComponent() = m_sprites[SpriteID::Envelope]; labelEnt.addComponent().ID = CommandID::Menu::UIElement | CommandID::Menu::CourseSelect; - labelEnt.addComponent().absolutePosition = { 54.f, 22.f }; + labelEnt.addComponent().absolutePosition = { 40.f, 22.f }; labelEnt.getComponent().depth = 0.01f; m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().addChild(labelEnt.getComponent()); } diff --git a/samples/golf/src/golf/MenuState.hpp b/samples/golf/src/golf/MenuState.hpp index 64230971c..586561d24 100644 --- a/samples/golf/src/golf/MenuState.hpp +++ b/samples/golf/src/golf/MenuState.hpp @@ -179,8 +179,6 @@ class MenuState final : public cro::State, public cro::GuiClient, public cro::Co std::uint32_t unselectHighlight = 0; std::uint32_t selectText = 0; std::uint32_t unselectText = 0; - std::uint32_t showTip = 0; - std::uint32_t hideTip = 0; }m_courseSelectCallbacks; std::array m_avatarEditCallbacks = {}; From bdffc9b34b37d281fba32e160670ef12cbedc5a0 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 13:23:21 +0100 Subject: [PATCH 020/233] tweak lobby layout send fast cpu status to clients on connect --- crogine.sln | 4 ++-- samples/golf/src/golf/MenuCreation.cpp | 12 ++++++------ samples/golf/src/golf/server/ServerGolfRules.cpp | 15 ++++++++++++--- samples/golf/src/golf/server/ServerLobbyState.cpp | 1 + 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/crogine.sln b/crogine.sln index 8cf326883..87b473872 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index e4eb61a36..f7f86545b 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1564,7 +1564,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 232.f, 33.f }; + entity.addComponent().absolutePosition = { 230.f, 31.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1595,7 +1595,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 409.f, 33.f }; + entity.addComponent().absolutePosition = { 408.f, 31.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1722,7 +1722,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 92.f, 2.f }; + entity.addComponent().absolutePosition = { 90.f, 0.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1753,7 +1753,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 180.f, 2.f }; + entity.addComponent().absolutePosition = { 178.f, 0.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1836,7 +1836,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("rules_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 92.f, 2.f }; + entity.addComponent().absolutePosition = { 90.f, 0.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); @@ -1867,7 +1867,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("course_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 3.f, 2.f }; + entity.addComponent().absolutePosition = { 0.f, 0.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 35cc8f74f..b4461160a 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -105,6 +105,7 @@ void GolfState::calcCPUPosition() { //always target pin if its closer pos = m_holeData[m_currentHole].pin; + LogI << pos << " (pin) - pin closer than target" << std::endl; } //target the pin if its in range of our longest club @@ -113,6 +114,7 @@ void GolfState::calcCPUPosition() &&*/ m_skillIndex > 2) { pos = m_holeData[m_currentHole].pin; + LogI << pos << " (pin) - skill increased distance" << std::endl; } else @@ -122,10 +124,12 @@ void GolfState::calcCPUPosition() if (targetDist < MinDist) //remember this in len2 { pos = m_holeData[m_currentHole].pin; + LogI << pos << " (pin) - target was too close" << std::endl; } else { pos = m_holeData[m_currentHole].target; + LogI << pos << " (target)" << std::endl; } } } @@ -133,6 +137,7 @@ void GolfState::calcCPUPosition() { //else set the pin as the target pos = m_holeData[m_currentHole].pin; + LogI << pos << " (pin) - target behind player" << std::endl; } //reduce the target distance so that it's in range of our longest club @@ -141,6 +146,7 @@ void GolfState::calcCPUPosition() { const float reduction = clubDist / std::sqrt(len2); pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; + LogI << pos << " reduced by " << reduction << std::endl; } //make sure there's only a slim chance of getting it in the hole @@ -152,7 +158,7 @@ void GolfState::calcCPUPosition() { //add target offset pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); - + LogI << pos << " offset on green" << std::endl; CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -165,7 +171,7 @@ void GolfState::calcCPUPosition() { //add offset pos += randomNormal() * static_cast(cro::Util::Random::value(6, 70)) / 10.f; - + LogI << pos << " offset on approach" << std::endl; CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -176,7 +182,7 @@ void GolfState::calcCPUPosition() //TODO we need a good bounce as a percentage value... constexpr float BouncePercent = 1.05f; - pos = ((pos - m_playerInfo[0].position) * BouncePercent) + m_playerInfo[0].position; + pos = ((pos - m_playerInfo[0].position) * BouncePercent);// +m_playerInfo[0].position; auto step = pos / 5.f; pos = m_playerInfo[0].position + step; @@ -187,6 +193,7 @@ void GolfState::calcCPUPosition() step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (6 - skill)); pos += step; } + LogI << pos << " offset from drive" << std::endl; CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -197,6 +204,8 @@ void GolfState::calcCPUPosition() auto result = m_scene.getSystem()->getTerrain(pos); pos.y = result.intersection.y; + LogI << pos << " height adjusted to " << result.intersection.y << std::endl; + CRO_ASSERT(!std::isnan(pos.x), ""); CRO_ASSERT(!std::isnan(pos.y), ""); CRO_ASSERT(!std::isnan(pos.z), ""); diff --git a/samples/golf/src/golf/server/ServerLobbyState.cpp b/samples/golf/src/golf/server/ServerLobbyState.cpp index 7647c9b21..5bb46dc04 100644 --- a/samples/golf/src/golf/server/ServerLobbyState.cpp +++ b/samples/golf/src/golf/server/ServerLobbyState.cpp @@ -237,5 +237,6 @@ void LobbyState::insertPlayerInfo(const net::NetEvent& evt) m_sharedData.host.broadcastPacket(PacketID::HoleCount, m_sharedData.holeCount, net::NetFlag::Reliable, ConstVal::NetChannelReliable); m_sharedData.host.broadcastPacket(PacketID::GimmeRadius, m_sharedData.gimmeRadius, net::NetFlag::Reliable, ConstVal::NetChannelReliable); m_sharedData.host.broadcastPacket(PacketID::ReverseCourse, m_sharedData.reverseCourse, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.fastCPU, net::NetFlag::Reliable, ConstVal::NetChannelReliable); } } \ No newline at end of file From 79b255ea208d30381d5fbdcc768730152cdcec13 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 14:28:01 +0100 Subject: [PATCH 021/233] fix NaN in fast CPU --- crogine.sln | 4 +- samples/golf/src/golf/GolfStateUI.cpp | 2 +- samples/golf/src/golf/MinimapShader.inl | 47 +++++++------------ .../golf/src/golf/server/ServerGolfRules.cpp | 16 ++----- 4 files changed, 24 insertions(+), 45 deletions(-) diff --git a/crogine.sln b/crogine.sln index 87b473872..8cf326883 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index c9bb1a9cc..aec81bb04 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -899,7 +899,7 @@ void GolfState::buildUI() entity.addComponent().setPosition({ 0.f, 82.f }); entity.getComponent().setRotation(-90.f * cro::Util::Const::degToRad); entity.getComponent().setOrigin({ 0.f, 0.f, 0.1f }); - entity.getComponent().setScale(glm::vec2(0.f)); + entity.getComponent().setScale(glm::vec2(0.01f)); entity.addComponent().setShader(&m_resources.shaders.get(ShaderID::MinimapView)); entity.addComponent(); entity.addComponent().ID = CommandID::UI::MiniMap; diff --git a/samples/golf/src/golf/MinimapShader.inl b/samples/golf/src/golf/MinimapShader.inl index 409bca4b2..3756b480a 100644 --- a/samples/golf/src/golf/MinimapShader.inl +++ b/samples/golf/src/golf/MinimapShader.inl @@ -112,47 +112,34 @@ static const std::string MinimapViewFragment = R"( OUTPUT - const float RadiusOuter = (0.4995 * 0.4995); - const float RadiusInner = (0.47 * 0.47); + //const float RadiusOuter = (0.4995 * 0.4995); + //const float RadiusInner = (0.47 * 0.47); //these ought to be uniforms for texture //res and screen scale const vec2 res = vec2(180.0, 100.0); const float scale = 2.0; -//https://stackoverflow.com/a/43994314/6740859 -vec2 uv = vec2(0.0); -float roundedRectangle (vec2 pos, vec2 size, float radius, float thickness) -{ - float d = length(max(abs(uv - pos),size) - size) - radius; - return smoothstep(0.66, 0.33, d / thickness * 5.0); -} + //https://stackoverflow.com/a/43994314/6740859 + vec2 uv = vec2(0.0); + float roundedRectangle (vec2 pos, vec2 size, float radius, float thickness) + { + float d = length(max(abs(uv - pos),size) - size) - radius; + return smoothstep(0.66, 0.33, d / thickness * 5.0); + } void main() { - //FRAG_OUT = TEXTURE(u_texture, v_texCoord0) * v_colour; - - //vec2 pos = (round(floor(v_texCoord1 * res) * scale) / scale) / res; - //pos -= vec2(0.5); - //float len2 = dot(pos, pos); - - //FRAG_OUT.a *= 1.0 - step(RadiusInner, len2); - - //vec4 bgColour = mix(vec4(vec3(0.0), 0.25), vec4(0.0), step(RadiusOuter, len2)); - //FRAG_OUT = mix(bgColour, FRAG_OUT, FRAG_OUT.a); - - - -uv = (round(floor(v_texCoord1 * res) * scale) / scale) / res; -uv = (2.0 * uv - 1.0); -uv.x *= res.x/res.y; + uv = (round(floor(v_texCoord1 * res) * scale) / scale) / res; + uv = (2.0 * uv - 1.0); + uv.x *= res.x/res.y; -vec4 col = TEXTURE(u_texture, v_texCoord0) * v_colour; -col.a *= roundedRectangle(vec2(0.0), vec2(1.28, 0.5), 0.42, 0.2); + vec4 col = TEXTURE(u_texture, v_texCoord0) * v_colour; + col.a *= roundedRectangle(vec2(0.0), vec2(1.28, 0.5), 0.42, 0.2); -FRAG_OUT = col; + FRAG_OUT = col; -vec4 bgColour = mix(vec4(0.0), vec4(vec3(0.0), 0.25), roundedRectangle(vec2(0.0), vec2(1.33, 0.55), 0.425, 0.2)); -FRAG_OUT = mix(bgColour, FRAG_OUT, FRAG_OUT.a); + vec4 bgColour = mix(vec4(0.0), vec4(vec3(0.0), 0.25), roundedRectangle(vec2(0.0), vec2(1.33, 0.55), 0.425, 0.2)); + FRAG_OUT = mix(bgColour, FRAG_OUT, FRAG_OUT.a); })"; \ No newline at end of file diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index b4461160a..d2a4c5081 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -105,7 +105,6 @@ void GolfState::calcCPUPosition() { //always target pin if its closer pos = m_holeData[m_currentHole].pin; - LogI << pos << " (pin) - pin closer than target" << std::endl; } //target the pin if its in range of our longest club @@ -114,7 +113,6 @@ void GolfState::calcCPUPosition() &&*/ m_skillIndex > 2) { pos = m_holeData[m_currentHole].pin; - LogI << pos << " (pin) - skill increased distance" << std::endl; } else @@ -124,12 +122,10 @@ void GolfState::calcCPUPosition() if (targetDist < MinDist) //remember this in len2 { pos = m_holeData[m_currentHole].pin; - LogI << pos << " (pin) - target was too close" << std::endl; } else { pos = m_holeData[m_currentHole].target; - LogI << pos << " (target)" << std::endl; } } } @@ -137,7 +133,6 @@ void GolfState::calcCPUPosition() { //else set the pin as the target pos = m_holeData[m_currentHole].pin; - LogI << pos << " (pin) - target behind player" << std::endl; } //reduce the target distance so that it's in range of our longest club @@ -146,7 +141,6 @@ void GolfState::calcCPUPosition() { const float reduction = clubDist / std::sqrt(len2); pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; - LogI << pos << " reduced by " << reduction << std::endl; } //make sure there's only a slim chance of getting it in the hole @@ -158,7 +152,6 @@ void GolfState::calcCPUPosition() { //add target offset pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); - LogI << pos << " offset on green" << std::endl; CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -171,7 +164,6 @@ void GolfState::calcCPUPosition() { //add offset pos += randomNormal() * static_cast(cro::Util::Random::value(6, 70)) / 10.f; - LogI << pos << " offset on approach" << std::endl; CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -182,7 +174,7 @@ void GolfState::calcCPUPosition() //TODO we need a good bounce as a percentage value... constexpr float BouncePercent = 1.05f; - pos = ((pos - m_playerInfo[0].position) * BouncePercent);// +m_playerInfo[0].position; + pos = ((pos - m_playerInfo[0].position) * BouncePercent); auto step = pos / 5.f; pos = m_playerInfo[0].position + step; @@ -193,7 +185,7 @@ void GolfState::calcCPUPosition() step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (6 - skill)); pos += step; } - LogI << pos << " offset from drive" << std::endl; + CRO_ASSERT(!std::isnan(pos.x), ""); } } @@ -204,8 +196,6 @@ void GolfState::calcCPUPosition() auto result = m_scene.getSystem()->getTerrain(pos); pos.y = result.intersection.y; - LogI << pos << " height adjusted to " << result.intersection.y << std::endl; - CRO_ASSERT(!std::isnan(pos.x), ""); CRO_ASSERT(!std::isnan(pos.y), ""); CRO_ASSERT(!std::isnan(pos.z), ""); @@ -223,10 +213,12 @@ void GolfState::calcCPUPosition() case TerrainID::Bunker: case TerrainID::Rough: ball.state = Ball::State::Flight; + ball.velocity.x = 0.001f; //add a tiny bit of velocity to prevent div0/nan in BallSystem break; case TerrainID::Green: case TerrainID::Hole: ball.state = Ball::State::Putt; + ball.velocity.x = 0.001f; break; case TerrainID::Scrub: case TerrainID::Stone: From 94f8b500b812e335b1a2bfd899d2c3f692430779 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 15:03:10 +0100 Subject: [PATCH 022/233] set sort order for lobby ui inputs --- samples/golf/src/golf/MenuCallbacks.cpp | 14 +++++++------- samples/golf/src/golf/MenuCreation.cpp | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/samples/golf/src/golf/MenuCallbacks.cpp b/samples/golf/src/golf/MenuCallbacks.cpp index e280e9226..f224628b7 100644 --- a/samples/golf/src/golf/MenuCallbacks.cpp +++ b/samples/golf/src/golf/MenuCallbacks.cpp @@ -59,13 +59,13 @@ void MenuCallback::operator()(cro::Entity e, float dt) *menuContext.currentMenu = menuData.targetMenu; //if we're hosting a lobby update the stride - if ((menuData.targetMenu == MenuState::MenuID::Lobby - && menuContext.sharedData->hosting) - /*|| menuData.targetMenu == MenuState::MenuID::Avatar*/) - { - menuContext.uiScene->getSystem()->setColumnCount(2); - } - else + //if ((menuData.targetMenu == MenuState::MenuID::Lobby + // && menuContext.sharedData->hosting) + // /*|| menuData.targetMenu == MenuState::MenuID::Avatar*/) + //{ + // menuContext.uiScene->getSystem()->setColumnCount(2); + //} + //else { menuContext.uiScene->getSystem()->setColumnCount(1); } diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index f7f86545b..7492e4598 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1568,6 +1568,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(1); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1599,6 +1600,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1726,6 +1728,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(1); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1757,6 +1760,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1825,7 +1829,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st const auto infoButtonEnable = [&](cro::Entity e, float) { - e.getComponent().active = + e.getComponent().enabled = m_lobbyWindowEntities[LobbyEntityID::Info].getComponent().getScale().y != 0.f; }; @@ -1840,6 +1844,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1871,6 +1876,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(1); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -2179,6 +2185,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent() = m_sprites[SpriteID::PrevMenu]; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(0); entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnterHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExitHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = @@ -2203,6 +2210,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent() = m_sprites[SpriteID::ReadyUp]; //which sprite is set by sending a message to this ent when we know if we're hosting or joining entity.addComponent().area = m_sprites[SpriteID::ReadyUp].getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnter; entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExit; entity.getComponent().callbacks[cro::UIInput::ButtonDown] = @@ -2630,6 +2638,7 @@ void MenuState::addCourseSelectButtons() auto bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(4); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRules; @@ -2653,6 +2662,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(5); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRules; @@ -2676,6 +2686,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(6); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRadius; @@ -2698,6 +2709,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(7); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRadius; @@ -2727,6 +2739,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(10); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleCount; @@ -2748,6 +2761,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(11); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextHoleCount; @@ -2772,6 +2786,7 @@ void MenuState::addCourseSelectButtons() bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().setSelectionIndex(4); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleReverseCourse; @@ -2794,6 +2809,7 @@ void MenuState::addCourseSelectButtons() bounds.width += 78.f; checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().setSelectionIndex(7); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFastCPU; @@ -2844,6 +2860,7 @@ void MenuState::addCourseSelectButtons() bounds = checkboxEnt.getComponent().getTextureBounds(); checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().setSelectionIndex(6); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFriendsOnly; @@ -2917,6 +2934,7 @@ void MenuState::addCourseSelectButtons() bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(8); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevCourse; @@ -2940,6 +2958,7 @@ void MenuState::addCourseSelectButtons() bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); + buttonEnt.getComponent().setSelectionIndex(9); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextCourse; @@ -2965,6 +2984,7 @@ void MenuState::addCourseSelectButtons() bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); + checkboxEnt.getComponent().setSelectionIndex(5); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleType; From ef2845834284b2535a873c5793e3659c19892068 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 8 Jun 2023 15:51:43 +0100 Subject: [PATCH 023/233] update fast CPU logic --- samples/golf/src/golf/GolfState.cpp | 7 +++++ samples/golf/src/golf/GolfStateUI.cpp | 6 ++-- .../golf/src/golf/server/ServerGolfRules.cpp | 31 +++++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index d566d30dc..31f1e8a01 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -759,6 +759,13 @@ bool GolfState::handleEvent(const cro::Event& evt) } resetIdle(); } + else if (evt.type == SDL_MOUSEBUTTONDOWN) + { + if (evt.button.button == SDL_BUTTON_RIGHT) + { + closeMessage(); + } + } else if (evt.type == SDL_CONTROLLERDEVICEREMOVED) { diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index aec81bb04..541ea146e 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -899,7 +899,7 @@ void GolfState::buildUI() entity.addComponent().setPosition({ 0.f, 82.f }); entity.getComponent().setRotation(-90.f * cro::Util::Const::degToRad); entity.getComponent().setOrigin({ 0.f, 0.f, 0.1f }); - entity.getComponent().setScale(glm::vec2(0.01f)); + entity.getComponent().setScale(glm::vec2(0.05f, 0.f)); entity.addComponent().setShader(&m_resources.shaders.get(ShaderID::MinimapView)); entity.addComponent(); entity.addComponent().ID = CommandID::UI::MiniMap; @@ -1604,8 +1604,8 @@ void GolfState::showCountdown(std::uint8_t seconds) //if we weren't the last player to take a turn in a network game //we need to reenable achievements to enter into the leaderboard... - Achievements::setActive(m_allowAchievements); - cro::Logger::log("LEADERBOARD attempting to insert score: " + std::to_string(score) + "\n", cro::Logger::Type::Info, cro::Logger::Output::File); + //Achievements::setActive(m_allowAchievements); //moved to beginning of showCountdown(); + //cro::Logger::log("LEADERBOARD attempting to insert score: " + std::to_string(score) + "\n", cro::Logger::Type::Info, cro::Logger::Output::File); Social::insertScore(m_sharedData.mapDirectory, m_sharedData.holeCount, score); break; } diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index d2a4c5081..b559abce2 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -94,9 +94,29 @@ void GolfState::calcCPUPosition() } const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); + const auto pickTarget = [&](float dp) + { + if (m_holeData[m_currentHole].puttFromTee) + { + if (dp > 0.98f) + { + return m_holeData[m_currentHole].pin; + } + else + { + return m_holeData[m_currentHole].target; + } + } + else + { + LogI << "putt from tee is false" << std::endl; + return m_holeData[m_currentHole].pin; + } + }; + //if both the pin and the target are in front of the player - if (glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)) > 0.4) + if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) { //set the target depending on how close it is const auto pinDist = glm::length2(pinDir); @@ -104,15 +124,14 @@ void GolfState::calcCPUPosition() if (pinDist < targetDist) { //always target pin if its closer - pos = m_holeData[m_currentHole].pin; + pos = pickTarget(dp); } //target the pin if its in range of our longest club //and CPU skill > something - else if (/*pinDist < (clubDist * clubDist) - &&*/ m_skillIndex > 2) + else if (m_skillIndex > 2) { - pos = m_holeData[m_currentHole].pin; + pos = pickTarget(dp); } else @@ -132,7 +151,7 @@ void GolfState::calcCPUPosition() else { //else set the pin as the target - pos = m_holeData[m_currentHole].pin; + pos = pickTarget(dp); } //reduce the target distance so that it's in range of our longest club From 472bbffec2e1bc43b328538c32ea1cbe3eef9f31 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 9 Jun 2023 09:53:36 +0100 Subject: [PATCH 024/233] fix up row spacing of lobby info page --- samples/golf/src/golf/MenuAvatars.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 73f5c38c3..7e276ca13 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1745,6 +1745,7 @@ void MenuState::updateLobbyAvatars() cro::String nameString; + constexpr float RowSpacing = -14.f; for (const auto& c : m_sharedData.connectionData) { //update the name label texture @@ -1809,7 +1810,7 @@ void MenuState::updateLobbyAvatars() glm::vec2 iconPos(1.f, 0.f); const std::int32_t row = playerCount; - iconPos.y = row * -14.f; //TODO constify row spacing + iconPos.y = row * RowSpacing; //add list of names on the connected client for (auto i = 0u; i < c.playerCount; ++i) @@ -1825,8 +1826,6 @@ void MenuState::updateLobbyAvatars() if (c.playerCount != 0) { - clientCount++; - //add a ready status for that client auto entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ -11.f, iconPos.y - 7.f }); @@ -1846,7 +1845,7 @@ void MenuState::updateLobbyAvatars() //rank text entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 100.f, iconPos.y + 198.f, 0.3f }); + entity.addComponent().setPosition({ 100.f, (RowSpacing * clientCount) + 198.f, 0.3f }); entity.addComponent(); entity.addComponent(smallFont).setString("Level"); entity.getComponent().setCharacterSize(InfoTextSize); @@ -2022,12 +2021,11 @@ void MenuState::updateLobbyAvatars() // //auto bounds = cro::Text::getLocalBounds(rankEnt); // //rankEnt.getComponent().setUserData((bounds.width / 2.f) + 8.f); //} + clientCount++; } h++; } - - //create text for name list if (!nameString.empty()) { From 023df1a67b33cebf3021a30c6d42e33fb070c735 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 9 Jun 2023 11:54:24 +0100 Subject: [PATCH 025/233] refine fast CPU putting --- .../golf/src/golf/server/ServerGolfRules.cpp | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index b559abce2..5d70d1186 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -76,40 +76,60 @@ void GolfState::calcCPUPosition() } skill += offset; + auto& ball = m_playerInfo[0].ballEntity.getComponent(); + std::int32_t clubID = ClubID::Putter; + //get longest range available - auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); - if (dist > 115.f) //forces a cut-off for pitch n putt + if (ball.terrain != TerrainID::Green) { - dist = 1000.f; - } + auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); + if (dist > 115.f) //forces a cut-off for pitch n putt + { + dist = 1000.f; + } - std::int32_t clubID = ClubID::SandWedge; - while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist - && clubID != ClubID::Driver) - { - do + clubID = ClubID::SandWedge; + while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist + && clubID != ClubID::Driver) { - clubID--; - } while (clubID != ClubID::Driver); + do + { + clubID--; + } while (clubID != ClubID::Driver); + } } const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); + + const auto pinDist = glm::length2(pinDir); + const auto targetDist = glm::length2(targetDir); + const float MinDist = m_scene.getSystem()->getPuttFromTee() ? 9.f : 2500.f; const auto pickTarget = [&](float dp) { - if (m_holeData[m_currentHole].puttFromTee) + if (m_scene.getSystem()->getPuttFromTee()) { - if (dp > 0.98f) + //LogI << "Putt from tee is true" << std::endl; + if (dp > 0.97f) { + //LogI << "returning pin (dp is " << dp << ")" << std::endl; return m_holeData[m_currentHole].pin; } else { + if (targetDist < MinDist + || pinDist < targetDist) + { + //LogI << "returning pin (target too close, or pin is closer)" << std::endl; + return m_holeData[m_currentHole].pin; + } + + //LogI << "returning target" << std::endl; return m_holeData[m_currentHole].target; } } else { - LogI << "putt from tee is false" << std::endl; + //LogI << "putt from tee is false" << std::endl; return m_holeData[m_currentHole].pin; } }; @@ -119,8 +139,6 @@ void GolfState::calcCPUPosition() if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) { //set the target depending on how close it is - const auto pinDist = glm::length2(pinDir); - const auto targetDist = glm::length2(targetDir); if (pinDist < targetDist) { //always target pin if its closer @@ -137,7 +155,6 @@ void GolfState::calcCPUPosition() else { //target the pin if the target is too close - const float MinDist = m_holeData[m_currentHole].puttFromTee ? 9.f : 2500.f; if (targetDist < MinDist) //remember this in len2 { pos = m_holeData[m_currentHole].pin; @@ -164,7 +181,6 @@ void GolfState::calcCPUPosition() //make sure there's only a slim chance of getting it in the hole //if club is not a putter, and VERY slim chance if not a wedge - auto& ball = m_playerInfo[0].ballEntity.getComponent(); if (ball.terrain == TerrainID::Green) { if (cro::Util::Random::value(0, 3 + skill) == 0) @@ -173,6 +189,27 @@ void GolfState::calcCPUPosition() pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); CRO_ASSERT(!std::isnan(pos.x), ""); } + + //add offset based on length of putt + const auto len2 = glm::length2(pos - m_playerInfo[0].position); + const auto length = std::min(1.f, len2 / (10.f * 10.f)); + auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); + accuracy /= 4.f; + + //lower skill has more chance of going off + if (cro::Util::Random::value(0, 3 + skill) == 0) + { + accuracy *= 1.6f + (3 - (std::min(skill, 3))); + } + + //higher skill has more chance of being accurate + if (cro::Util::Random::value(0, 3 + skill) > 3) + { + accuracy /= 2.f; + } + + pos += randomNormal() * accuracy; + LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; } else { @@ -222,6 +259,8 @@ void GolfState::calcCPUPosition() m_playerInfo[0].ballEntity.getComponent().setPosition(pos); m_playerInfo[0].holeScore[m_currentHole]++; + const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; + ball.terrain = result.terrain; switch (result.terrain) { @@ -232,12 +271,12 @@ void GolfState::calcCPUPosition() case TerrainID::Bunker: case TerrainID::Rough: ball.state = Ball::State::Flight; - ball.velocity.x = 0.001f; //add a tiny bit of velocity to prevent div0/nan in BallSystem + ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem break; case TerrainID::Green: case TerrainID::Hole: ball.state = Ball::State::Putt; - ball.velocity.x = 0.001f; + ball.velocity = velOffset; break; case TerrainID::Scrub: case TerrainID::Stone: From 2c9d0ca9263053673b69f359f831f0e6fba6ed8d Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 9 Jun 2023 14:56:33 +0100 Subject: [PATCH 026/233] update selection order of lobby input buttons when scrolling with keyboard or controller --- samples/golf/src/golf/GolfStateUI.cpp | 23 ++++++++++++--- samples/golf/src/golf/MenuCreation.cpp | 39 +++++++++++++------------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index 541ea146e..b6a31239f 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -935,6 +935,7 @@ void GolfState::buildUI() auto oldCam = m_gameScene.setActiveCamera(m_mapCam); m_gameScene.getSystem()->process(0.f); + //m_gameScene.simulate(0.f); cro::Colour c = cro::Colour::Transparent; //cro::Colour c(std::uint8_t(39), 56, 153); @@ -1164,14 +1165,15 @@ void GolfState::buildUI() m_minimapZoom.updateShader(); glm::vec2 viewSize(MapSize); - miniCam.setOrthographic(-viewSize.x / 2.f, viewSize.x / 2.f, -viewSize.y / 2.f, viewSize.y / 2.f, -0.1f, 50.f); + miniCam.setOrthographic(-viewSize.x / 2.f, viewSize.x / 2.f, -viewSize.y / 2.f, viewSize.y / 2.f, -0.1f, 60.f); miniCam.viewport = { 0.f, 0.f, 1.f, 1.f }; }; m_mapCam = m_gameScene.createEntity(); - m_mapCam.addComponent().setPosition({ static_cast(MapSize.x) / 2.f, 38.f, -static_cast(MapSize.y) / 2.f}); + m_mapCam.addComponent().setPosition({ static_cast(MapSize.x) / 2.f, /*38.f*/36.f, -static_cast(MapSize.y) / 2.f}); m_mapCam.getComponent().rotate(cro::Transform::X_AXIS, -90.f * cro::Util::Const::degToRad); auto& miniCam = m_mapCam.addComponent(); + updateMiniView(miniCam); miniCam.renderFlags = RenderFlags::MiniMap; miniCam.active = false; //this is a hack to stop the entire terrain being drawn in shadow @@ -1179,7 +1181,6 @@ void GolfState::buildUI() miniCam.shadowMapBuffer.clear(); miniCam.shadowMapBuffer.display(); //miniCam.resizeCallback = updateMiniView; //don't do this on resize as recreating the buffer clears it.. - updateMiniView(miniCam); @@ -1583,6 +1584,7 @@ void GolfState::showCountdown(std::uint8_t seconds) bool personalBest = false; + cro::String bestString("PERSONAL BEST!"); #ifndef CRO_DEBUG_ //enter score into leaderboard @@ -1602,6 +1604,19 @@ void GolfState::showCountdown(std::uint8_t seconds) personalBest = true; } + if (!personalBest) + { + //see if we at least got a new monthly score + best = Social::getMonthlyBest(m_sharedData.mapDirectory, m_sharedData.holeCount); + + if (score < best + || best == 0) + { + personalBest = true; + bestString = "MONTHLY BEST!"; + } + } + //if we weren't the last player to take a turn in a network game //we need to reenable achievements to enter into the leaderboard... //Achievements::setActive(m_allowAchievements); //moved to beginning of showCountdown(); @@ -1684,7 +1699,7 @@ void GolfState::showCountdown(std::uint8_t seconds) entity.getComponent().setFillColour(TextHighlightColour); //entity.getComponent().setOutlineColour(LeaderboardTextDark); //entity.getComponent().setOutlineThickness(1.f); - entity.getComponent().setString("PERSONAL BEST!"); + entity.getComponent().setString(bestString); centreText(entity); cmd.targetFlags = CommandID::UI::Scoreboard; diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 7492e4598..8a8d14771 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1568,7 +1568,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(1); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1600,7 +1600,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1728,7 +1728,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(1); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1760,7 +1760,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1844,7 +1844,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1876,7 +1876,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(1); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -2185,7 +2185,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent() = m_sprites[SpriteID::PrevMenu]; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(0); + entity.getComponent().setSelectionIndex(1); entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnterHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExitHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = @@ -2210,7 +2210,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent() = m_sprites[SpriteID::ReadyUp]; //which sprite is set by sending a message to this ent when we know if we're hosting or joining entity.addComponent().area = m_sprites[SpriteID::ReadyUp].getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(3); + entity.getComponent().setSelectionIndex(4); entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnter; entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExit; entity.getComponent().callbacks[cro::UIInput::ButtonDown] = @@ -2638,7 +2638,7 @@ void MenuState::addCourseSelectButtons() auto bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(4); + buttonEnt.getComponent().setSelectionIndex(5); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRules; @@ -2662,7 +2662,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(5); + buttonEnt.getComponent().setSelectionIndex(6); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRules; @@ -2686,7 +2686,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(6); + buttonEnt.getComponent().setSelectionIndex(7); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevRadius; @@ -2709,7 +2709,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(7); + buttonEnt.getComponent().setSelectionIndex(8); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextRadius; @@ -2739,7 +2739,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(10); + buttonEnt.getComponent().setSelectionIndex(11); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleCount; @@ -2761,7 +2761,7 @@ void MenuState::addCourseSelectButtons() bounds = buttonEnt.getComponent().getTextureBounds(); buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(11); + buttonEnt.getComponent().setSelectionIndex(12); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selected; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselected; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextHoleCount; @@ -2786,7 +2786,7 @@ void MenuState::addCourseSelectButtons() bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); - checkboxEnt.getComponent().setSelectionIndex(4); + checkboxEnt.getComponent().setSelectionIndex(5); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleReverseCourse; @@ -2860,7 +2860,7 @@ void MenuState::addCourseSelectButtons() bounds = checkboxEnt.getComponent().getTextureBounds(); checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); - checkboxEnt.getComponent().setSelectionIndex(6); + checkboxEnt.getComponent().setSelectionIndex(8); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.toggleFriendsOnly; @@ -2897,6 +2897,7 @@ void MenuState::addCourseSelectButtons() bounds = cro::Text::getLocalBounds(labelEnt); labelEnt.addComponent().area = bounds; labelEnt.getComponent().setGroup(MenuID::Lobby); + labelEnt.getComponent().setSelectionIndex(13); labelEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectText; labelEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectText; labelEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.inviteFriends; @@ -2934,7 +2935,7 @@ void MenuState::addCourseSelectButtons() bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(8); + buttonEnt.getComponent().setSelectionIndex(9); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevCourse; @@ -2958,7 +2959,7 @@ void MenuState::addCourseSelectButtons() bounds.height += 12.f; buttonEnt.addComponent().area = bounds; buttonEnt.getComponent().setGroup(MenuID::Lobby); - buttonEnt.getComponent().setSelectionIndex(9); + buttonEnt.getComponent().setSelectionIndex(10); buttonEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; buttonEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.nextCourse; @@ -2984,7 +2985,7 @@ void MenuState::addCourseSelectButtons() bounds.width += 78.f; //wild stab at the width of the text (it's not here to measure...) checkboxEnt.addComponent().area = bounds; checkboxEnt.getComponent().setGroup(MenuID::Lobby); - checkboxEnt.getComponent().setSelectionIndex(5); + checkboxEnt.getComponent().setSelectionIndex(6); checkboxEnt.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; checkboxEnt.getComponent().callbacks[cro::UIInput::ButtonUp] = m_courseSelectCallbacks.prevHoleType; From fb981767c2742d3a6044b67aad2e0cec28b05d2d Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 9 Jun 2023 21:17:37 +0100 Subject: [PATCH 027/233] fix resize callback for cached states fix resize callback in options menu when using apply button --- crogine/src/core/StateStack.cpp | 9 +++++++-- libsocial/include/Social.hpp | 1 + samples/golf/src/golf/OptionsState.cpp | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crogine/src/core/StateStack.cpp b/crogine/src/core/StateStack.cpp index 305953196..4b0fbbb2e 100644 --- a/crogine/src/core/StateStack.cpp +++ b/crogine/src/core/StateStack.cpp @@ -139,8 +139,13 @@ std::int32_t StateStack::getTopmostState() const void StateStack::cacheState(StateID id) { CRO_ASSERT(m_stateCache.count(id) == 0, "State is already cached"); - m_stateCache.insert(std::make_pair(id, createState(id, true))); - m_stateCache.at(id)->m_cached = true; + auto& state = m_stateCache.insert(std::make_pair(id, createState(id, true))).first->second; + state->m_cached = true; + + //do one update to make sure the scene is properly initialised and ready + //this fixes instances where resize callbacks aren't applied to cached + //states which haven't been openeed and have no fully registered their cameras + state->simulate(0.f); } void StateStack::uncacheState(StateID id) diff --git a/libsocial/include/Social.hpp b/libsocial/include/Social.hpp index 1d83683d5..bd37eb91a 100644 --- a/libsocial/include/Social.hpp +++ b/libsocial/include/Social.hpp @@ -127,6 +127,7 @@ class Social final static void readDrivingStats(std::array&); static void insertScore(const std::string&, std::uint8_t, std::int32_t) {} static std::int32_t getPersonalBest(const std::string&, std::uint8_t) { return -1; } + static std::int32_t getMonthlyBest(const std::string&, std::uint8_t) { return -1; } static std::vector getLeaderboardResults(std::int32_t, std::int32_t) { return {}; } static void courseComplete(const std::string&, std::uint8_t); static void setStatus(std::int32_t, const std::vector&) {} diff --git a/samples/golf/src/golf/OptionsState.cpp b/samples/golf/src/golf/OptionsState.cpp index a96935425..a49dd7792 100644 --- a/samples/golf/src/golf/OptionsState.cpp +++ b/samples/golf/src/golf/OptionsState.cpp @@ -3428,6 +3428,10 @@ void OptionsState::createButtons(cro::Entity parent, std::int32_t menuID, std::u cro::App::getWindow().setSize(m_sharedData.resolutions[m_videoSettings.resolutionIndex]); } m_audioEnts[AudioID::Accept].getComponent().play(); + + auto& cam = m_scene.getActiveCamera().getComponent(); + cam.active = true; + cam.resizeCallback(cam); } }); entity.addComponent().active = true; From d9a509a279719bf299626f033b04776dcac156b9 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 10 Jun 2023 09:49:16 +0100 Subject: [PATCH 028/233] re-activate camera on resize callback --- crogine.sln | 4 ++-- crogine/src/core/StateStack.cpp | 2 +- crogine/src/ecs/systems/CameraSystem.cpp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crogine.sln b/crogine.sln index 8cf326883..87b473872 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/crogine/src/core/StateStack.cpp b/crogine/src/core/StateStack.cpp index 4b0fbbb2e..7a16340d3 100644 --- a/crogine/src/core/StateStack.cpp +++ b/crogine/src/core/StateStack.cpp @@ -144,7 +144,7 @@ void StateStack::cacheState(StateID id) //do one update to make sure the scene is properly initialised and ready //this fixes instances where resize callbacks aren't applied to cached - //states which haven't been openeed and have no fully registered their cameras + //states which haven't been opened and have not fully registered their cameras state->simulate(0.f); } diff --git a/crogine/src/ecs/systems/CameraSystem.cpp b/crogine/src/ecs/systems/CameraSystem.cpp index 22aa8082f..93b99759b 100644 --- a/crogine/src/ecs/systems/CameraSystem.cpp +++ b/crogine/src/ecs/systems/CameraSystem.cpp @@ -61,6 +61,7 @@ void CameraSystem::handleMessage(const Message& msg) if (camera.resizeCallback) { camera.resizeCallback(camera); + camera.active = true; } resizeGBuffer(entity); } From 8760d8b328c1a1147fc642f85597e192e8bc16cb Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 10 Jun 2023 12:21:23 +0100 Subject: [PATCH 029/233] suspend inactive ui systems --- crogine.sln | 4 ++-- samples/golf/src/golf/GameConsts.hpp | 6 +++--- samples/golf/src/golf/KeyboardState.cpp | 6 +++++- samples/golf/src/golf/MenuCreation.cpp | 6 +++--- samples/golf/src/golf/OptionsState.cpp | 11 ++++++++--- samples/golf/src/golf/PracticeState.cpp | 6 ++++++ samples/golf/src/golf/ProfileState.cpp | 4 ++++ 7 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crogine.sln b/crogine.sln index 87b473872..8cf326883 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/samples/golf/src/golf/GameConsts.hpp b/samples/golf/src/golf/GameConsts.hpp index 05f277ad5..60ee9f798 100644 --- a/samples/golf/src/golf/GameConsts.hpp +++ b/samples/golf/src/golf/GameConsts.hpp @@ -437,17 +437,17 @@ static inline float getViewScale(glm::vec2 size = GolfGame::getActiveTarget()->g if (ratio < 1.7) { //4:3 - return std::min(4.f, std::floor(size.x / 512.f)); + return std::min(8.f, std::floor(size.x / 512.f)); } if (ratio < 2.37f) { //widescreen - return std::min(4.f, std::floor(size.x / 540.f)); + return std::min(8.f, std::floor(size.x / 540.f)); } //ultrawide - return std::min(4.f, std::floor(size.y / 360.f)); + return std::min(8.f, std::floor(size.y / 360.f)); } static inline void togglePixelScale(SharedStateData& sharedData, bool on) diff --git a/samples/golf/src/golf/KeyboardState.cpp b/samples/golf/src/golf/KeyboardState.cpp index 348b1336d..08306aa3b 100644 --- a/samples/golf/src/golf/KeyboardState.cpp +++ b/samples/golf/src/golf/KeyboardState.cpp @@ -1,6 +1,6 @@ /*----------------------------------------------------------------------- -Matt Marchant 2021 +Matt Marchant 2021 - 2023 http://trederia.blogspot.com Super Video Golf - zlib licence. @@ -270,6 +270,8 @@ void KeyboardState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); + m_scene.setSystemActive(false); + cro::SpriteSheet spriteSheet; spriteSheet.loadFromFile("assets/sprites/osk.spt", m_sharedData.sharedResources->textures); @@ -302,6 +304,8 @@ void KeyboardState::buildScene() pos.y = 0.f; e.getComponent().setOrigin(pos); m_highlightEntity.getComponent().setColour(cro::Colour::White); + + m_scene.setSystemActive(true); } } break; diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 8a8d14771..475145851 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -392,9 +392,9 @@ void MenuState::createUI() refreshUI(); }; - entity = m_uiScene.createEntity(); - entity.addComponent(); - entity.addComponent().resizeCallback = updateView; + entity = m_uiScene.getActiveCamera(); + entity.getComponent(); + entity.getComponent().resizeCallback = updateView; m_uiScene.setActiveCamera(entity); updateView(entity.getComponent()); } diff --git a/samples/golf/src/golf/OptionsState.cpp b/samples/golf/src/golf/OptionsState.cpp index a49dd7792..07a1dda56 100644 --- a/samples/golf/src/golf/OptionsState.cpp +++ b/samples/golf/src/golf/OptionsState.cpp @@ -634,6 +634,9 @@ void OptionsState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); + m_scene.setSystemActive(false); + + struct RootCallbackData final { enum @@ -663,6 +666,7 @@ void OptionsState::buildScene() state = RootCallbackData::FadeOut; e.getComponent().active = false; + m_scene.setSystemActive(true); m_scene.getSystem()->setActiveGroup(MenuID::Video); } break; @@ -1127,9 +1131,9 @@ void OptionsState::buildScene() rootNode.getComponent().setPosition(size / 2.f); }; - entity = m_scene.createEntity(); - entity.addComponent().setPosition({ 0.f, 0.f, CameraDepth }); - entity.addComponent().resizeCallback = updateView; + entity = m_scene.getActiveCamera(); + entity.getComponent().setPosition({ 0.f, 0.f, CameraDepth }); + entity.getComponent().resizeCallback = updateView; m_scene.setActiveCamera(entity); updateView(entity.getComponent()); @@ -3514,6 +3518,7 @@ void OptionsState::updateActiveCallbacks() void OptionsState::quitState() { + m_scene.setSystemActive(false); m_rootNode.getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } \ No newline at end of file diff --git a/samples/golf/src/golf/PracticeState.cpp b/samples/golf/src/golf/PracticeState.cpp index 3dc76bb5d..bdb7ffa9f 100644 --- a/samples/golf/src/golf/PracticeState.cpp +++ b/samples/golf/src/golf/PracticeState.cpp @@ -183,6 +183,8 @@ void PracticeState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); + m_scene.setSystemActive(false); + m_menuSounds.loadFromFile("assets/golf/sound/menu.xas", m_sharedData.sharedResources->audio); m_audioEnts[AudioID::Accept] = m_scene.createEntity(); m_audioEnts[AudioID::Accept].addComponent() = m_menuSounds.getEmitter("accept"); @@ -217,6 +219,8 @@ void PracticeState::buildScene() { state = RootCallbackData::FadeOut; e.getComponent().active = false; + + m_scene.setSystemActive(true); } break; case RootCallbackData::FadeOut: @@ -544,6 +548,8 @@ void PracticeState::buildScene() void PracticeState::quitState() { + m_scene.setSystemActive(false); + m_rootNode.getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } \ No newline at end of file diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index 2f3fbdeeb..c24677855 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -438,6 +438,8 @@ void ProfileState::addSystems() m_uiScene.addSystem(mb); m_uiScene.addSystem(mb); + m_uiScene.setSystemActive(false); + m_modelScene.addSystem(mb); m_modelScene.addSystem(mb); m_modelScene.addSystem(mb); @@ -492,6 +494,7 @@ void ProfileState::buildScene() state = RootCallbackData::FadeOut; e.getComponent().active = false; + m_uiScene.setSystemActive(true); m_uiScene.getSystem()->setActiveGroup(MenuID::Main); } @@ -1551,6 +1554,7 @@ void ProfileState::quitState() m_menuEntities[EntityID::Root].getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } + m_uiScene.setSystemActive(false); } std::size_t ProfileState::indexFromAvatarID(std::uint32_t skinID) const From 42783be47eaafa5429311982d10675bbc3f1e0f0 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 10 Jun 2023 14:23:16 +0100 Subject: [PATCH 030/233] disable audio systems of sub-states until menu active --- samples/golf/src/golf/OptionsState.cpp | 6 +++--- samples/golf/src/golf/PauseState.cpp | 4 ++++ samples/golf/src/golf/ProfileState.cpp | 6 +++--- samples/golf/src/golf/TrophyState.cpp | 4 ++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/OptionsState.cpp b/samples/golf/src/golf/OptionsState.cpp index 07a1dda56..5553bac59 100644 --- a/samples/golf/src/golf/OptionsState.cpp +++ b/samples/golf/src/golf/OptionsState.cpp @@ -634,7 +634,7 @@ void OptionsState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); - m_scene.setSystemActive(false); + m_scene.setSystemActive(false); struct RootCallbackData final @@ -666,7 +666,7 @@ void OptionsState::buildScene() state = RootCallbackData::FadeOut; e.getComponent().active = false; - m_scene.setSystemActive(true); + m_scene.setSystemActive(true); m_scene.getSystem()->setActiveGroup(MenuID::Video); } break; @@ -3518,7 +3518,7 @@ void OptionsState::updateActiveCallbacks() void OptionsState::quitState() { - m_scene.setSystemActive(false); + m_scene.setSystemActive(false); m_rootNode.getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } \ No newline at end of file diff --git a/samples/golf/src/golf/PauseState.cpp b/samples/golf/src/golf/PauseState.cpp index 5c337280b..182e667a3 100644 --- a/samples/golf/src/golf/PauseState.cpp +++ b/samples/golf/src/golf/PauseState.cpp @@ -194,6 +194,8 @@ void PauseState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); + m_scene.setSystemActive(false); + m_menuSounds.loadFromFile("assets/golf/sound/menu.xas", m_sharedData.sharedResources->audio); m_audioEnts[AudioID::Accept] = m_scene.createEntity(); m_audioEnts[AudioID::Accept].addComponent() = m_menuSounds.getEmitter("accept"); @@ -237,6 +239,7 @@ void PauseState::buildScene() state = RootCallbackData::FadeOut; e.getComponent().active = false; + m_scene.setSystemActive(true); m_audioEnts[AudioID::Accept].getComponent().play(); } break; @@ -549,6 +552,7 @@ void PauseState::buildScene() void PauseState::quitState() { + m_scene.setSystemActive(false); m_rootNode.getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } \ No newline at end of file diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index c24677855..d7db53d7c 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -438,7 +438,7 @@ void ProfileState::addSystems() m_uiScene.addSystem(mb); m_uiScene.addSystem(mb); - m_uiScene.setSystemActive(false); + m_uiScene.setSystemActive(false); m_modelScene.addSystem(mb); m_modelScene.addSystem(mb); @@ -494,7 +494,7 @@ void ProfileState::buildScene() state = RootCallbackData::FadeOut; e.getComponent().active = false; - m_uiScene.setSystemActive(true); + m_uiScene.setSystemActive(true); m_uiScene.getSystem()->setActiveGroup(MenuID::Main); } @@ -1554,7 +1554,7 @@ void ProfileState::quitState() m_menuEntities[EntityID::Root].getComponent().active = true; m_audioEnts[AudioID::Back].getComponent().play(); } - m_uiScene.setSystemActive(false); + m_uiScene.setSystemActive(false); } std::size_t ProfileState::indexFromAvatarID(std::uint32_t skinID) const diff --git a/samples/golf/src/golf/TrophyState.cpp b/samples/golf/src/golf/TrophyState.cpp index bee10e168..efee1f607 100644 --- a/samples/golf/src/golf/TrophyState.cpp +++ b/samples/golf/src/golf/TrophyState.cpp @@ -272,6 +272,8 @@ void TrophyState::buildScene() m_scene.addSystem(mb); m_scene.addSystem(mb); + m_scene.setSystemActive(false); + m_menuSounds.loadFromFile("assets/golf/sound/menu.xas", m_sharedData.sharedResources->audio); m_audioEnts[AudioID::Accept] = m_scene.createEntity(); m_audioEnts[AudioID::Accept].addComponent() = m_menuSounds.getEmitter("accept"); @@ -395,6 +397,7 @@ void TrophyState::buildScene() leftDoor.getComponent().active = true; rightDoor.getComponent().active = true; + m_scene.setSystemActive(true); m_scene.getSystem()->setActiveGroup(MenuID::Main); } break; @@ -406,6 +409,7 @@ void TrophyState::buildScene() requestStackPop(); state = RootCallbackData::FadeIn; + m_scene.setSystemActive(true); /*leftDoor.getComponent().setVertexData(doorVerts); leftDoor.getComponent().setFacing(cro::Drawable2D::Facing::Front); From 3e6292b557b0ea47281f9ffcabad1a7a27143a6d Mon Sep 17 00:00:00 2001 From: fallahn Date: Sun, 11 Jun 2023 14:25:02 +0100 Subject: [PATCH 031/233] refine fast CPU behaviour --- samples/golf/src/golf/GolfSoundDirector.cpp | 7 +- samples/golf/src/golf/GolfSoundDirector.hpp | 2 +- samples/golf/src/golf/GolfState.cpp | 8 +- samples/golf/src/golf/MenuCreation.cpp | 4 +- .../golf/src/golf/server/ServerGolfRules.cpp | 245 +++++++++++------- 5 files changed, 169 insertions(+), 97 deletions(-) diff --git a/samples/golf/src/golf/GolfSoundDirector.cpp b/samples/golf/src/golf/GolfSoundDirector.cpp index 0885f0696..26e8fee0e 100644 --- a/samples/golf/src/golf/GolfSoundDirector.cpp +++ b/samples/golf/src/golf/GolfSoundDirector.cpp @@ -542,15 +542,16 @@ void GolfSoundDirector::setPlayerIndex(std::size_t client, std::size_t player, s m_playerIndices[client][player] = index; } -void GolfSoundDirector::setActivePlayer(std::size_t client, std::size_t player) +void GolfSoundDirector::setActivePlayer(std::size_t client, std::size_t player, bool skipAudio) { m_currentClient = client; m_currentPlayer = player; - + if (m_newHole) { std::int32_t honour = (client << 8) | player; - if (honour != m_honourID) + if (honour != m_honourID + && !skipAudio) //don't play this if fast CPU as it overlaps other audio { playSoundDelayed(AudioID::Honour, glm::vec3(0.f), 1.f, 1.f, MixerChannel::Voice); } diff --git a/samples/golf/src/golf/GolfSoundDirector.hpp b/samples/golf/src/golf/GolfSoundDirector.hpp index bd2c6ac58..e03162169 100644 --- a/samples/golf/src/golf/GolfSoundDirector.hpp +++ b/samples/golf/src/golf/GolfSoundDirector.hpp @@ -53,7 +53,7 @@ class GolfSoundDirector final : public SoundEffectsDirector void addAudioScape(const std::string& path, cro::AudioResource& resource); void setPlayerIndex(std::size_t client, std::size_t player, std::int32_t index); - void setActivePlayer(std::size_t client, std::size_t player); + void setActivePlayer(std::size_t client, std::size_t player, bool skipAudio); private: struct AudioID final diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 31f1e8a01..d7e1b67d3 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -6285,7 +6285,11 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) m_skipState = {}; m_ballTrail.setNext(); - m_gameScene.getDirector()->setActivePlayer(player.client, player.player); + + auto localPlayer = (player.client == m_sharedData.clientConnection.connectionID); + auto isCPU = m_sharedData.localConnectionData.playerData[player.player].isCPU; + + m_gameScene.getDirector()->setActivePlayer(player.client, player.player, isCPU && m_sharedData.fastCPU); m_avatars[player.client][player.player].ballModel.getComponent().setScale(glm::vec3(1.f)); m_resolutionUpdate.targetFade = player.terrain == TerrainID::Green ? GreenFadeDistance : CourseFadeDistance; @@ -6293,8 +6297,6 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) updateScoreboard(); showScoreboard(false); - auto localPlayer = (player.client == m_sharedData.clientConnection.connectionID); - auto isCPU = m_sharedData.localConnectionData.playerData[player.player].isCPU; m_sharedData.inputBinding.playerID = localPlayer ? player.player : 0; //this also affects who can emote, so if we're currently emoting when it's not our turn always be player 0(??) m_inputParser.setActive(localPlayer && !m_photoMode, m_currentPlayer.terrain, isCPU); diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 475145851..20051c5f6 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -2345,11 +2345,11 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st case ScoreType::Stroke: if (score.score < 0) { - names.back() += " Under PAR"; + names.back() += " Under Par"; } else if (score.score > 0) { - names.back() += " Over PAR"; + names.back() += " Over Par"; } break; case ScoreType::Skins: diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 5d70d1186..48cab6aac 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -40,7 +40,7 @@ void GolfState::makeCPUMove() //wrap in an ent so we can add a small delay auto entity = m_scene.createEntity(); entity.addComponent().active = true; - entity.getComponent().setUserData(2.5f); + entity.getComponent().setUserData(1.f); entity.getComponent().function = [&](cro::Entity e, float dt) { @@ -74,7 +74,10 @@ void GolfState::calcCPUPosition() { offset *= -1; } - skill += offset; + skill = std::clamp(skill + offset, 0, 6); + //skill += offset; + + //std::int32_t skill = m_playerInfo[0].player * 3; auto& ball = m_playerInfo[0].ballEntity.getComponent(); std::int32_t clubID = ClubID::Putter; @@ -108,10 +111,8 @@ void GolfState::calcCPUPosition() { if (m_scene.getSystem()->getPuttFromTee()) { - //LogI << "Putt from tee is true" << std::endl; if (dp > 0.97f) { - //LogI << "returning pin (dp is " << dp << ")" << std::endl; return m_holeData[m_currentHole].pin; } else @@ -119,137 +120,205 @@ void GolfState::calcCPUPosition() if (targetDist < MinDist || pinDist < targetDist) { - //LogI << "returning pin (target too close, or pin is closer)" << std::endl; return m_holeData[m_currentHole].pin; } - //LogI << "returning target" << std::endl; return m_holeData[m_currentHole].target; } } else { - //LogI << "putt from tee is false" << std::endl; return m_holeData[m_currentHole].pin; } }; - - //if both the pin and the target are in front of the player - if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) + //if we're less than 20cm from the hole we'll assume + //it's pretty much a dead cert going in + if (pinDist > (0.2f * 0.2f)) { - //set the target depending on how close it is - if (pinDist < targetDist) - { - //always target pin if its closer - pos = pickTarget(dp); - } - - //target the pin if its in range of our longest club - //and CPU skill > something - else if (m_skillIndex > 2) + //if both the pin and the target are in front of the player + if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) { - pos = pickTarget(dp); - } + //set the target depending on how close it is + if (pinDist < targetDist) + { + //always target pin if its closer + pos = pickTarget(dp); + } - else - { - //target the pin if the target is too close - if (targetDist < MinDist) //remember this in len2 + //target the pin if its in range of our longest club + //and CPU skill > something + else if (skill > 2) { - pos = m_holeData[m_currentHole].pin; + pos = pickTarget(dp); } + else { - pos = m_holeData[m_currentHole].target; + //target the pin if the target is too close + if (targetDist < MinDist) //remember this is len2 + { + pos = m_holeData[m_currentHole].pin; + } + else + { + pos = m_holeData[m_currentHole].target; + } } } - } - else - { - //else set the pin as the target - pos = pickTarget(dp); - } - - //reduce the target distance so that it's in range of our longest club - if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > - (clubDist * clubDist)) - { - const float reduction = clubDist / std::sqrt(len2); - pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; - } - - //make sure there's only a slim chance of getting it in the hole - //if club is not a putter, and VERY slim chance if not a wedge - if (ball.terrain == TerrainID::Green) - { - if (cro::Util::Random::value(0, 3 + skill) == 0) - { - //add target offset - pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); - CRO_ASSERT(!std::isnan(pos.x), ""); - } - - //add offset based on length of putt - const auto len2 = glm::length2(pos - m_playerInfo[0].position); - const auto length = std::min(1.f, len2 / (10.f * 10.f)); - auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); - accuracy /= 4.f; - - //lower skill has more chance of going off - if (cro::Util::Random::value(0, 3 + skill) == 0) + else { - accuracy *= 1.6f + (3 - (std::min(skill, 3))); + //else set the pin as the target + pos = pickTarget(dp); } - //higher skill has more chance of being accurate - if (cro::Util::Random::value(0, 3 + skill) > 3) + //reduce the target distance so that it's in range of our longest club + if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > + (clubDist * clubDist)) { - accuracy /= 2.f; + const float reduction = clubDist / std::sqrt(len2); + pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; } - pos += randomNormal() * accuracy; - LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; - } - else - { - //we're probably chipping - if (glm::length2(pos - m_playerInfo[0].position) < (90.f * 90.f)) + //make sure there's only a slim chance of getting it in the hole + //if club is not a putter, and VERY slim chance if not a wedge + if (ball.terrain == TerrainID::Green) { - if (cro::Util::Random::value(0, 4 + skill) < 8) + if (cro::Util::Random::value(0, 3 + (skill / 2)) == 0) { - //add offset - pos += randomNormal() * static_cast(cro::Util::Random::value(6, 70)) / 10.f; + //add target offset + pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); CRO_ASSERT(!std::isnan(pos.x), ""); } + + //add offset based on length of putt + const auto len2 = glm::length2(pos - m_playerInfo[0].position); + const auto length = std::min(1.f, len2 / (10.f * 10.f)); + auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); + accuracy /= 4.f; + + //lower skill has more chance of going off + if (cro::Util::Random::value(0, 3 + skill) == 0) + { + accuracy *= 1.6f + (3 - (std::min(skill, 3))); + } + + //higher skill has more chance of being accurate + if (cro::Util::Random::value(0, 3 + skill) > 3) + { + accuracy /= 2.f; + } + + pos += randomNormal() * accuracy; + //LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; } else { - //iron or driver so add some arbitrary offset - //based on bounce and wind strength/dir and distance to pos + //we're probably chipping if < 90m + if (auto l2 = glm::length2(pos - m_playerInfo[0].position); l2 < (90.f * 90.f)) + { + //if len > 20 almost always add offset so we don't hole the ball + if (l2 > (20.f * 20.f)) + { + if (cro::Util::Random::value(0, 100 - (skill * 10)) != 0) + { + pos += randomNormal() * static_cast(cro::Util::Random::value(3, 10)) / 10.f; + } + } + + if (cro::Util::Random::value(0, 4 + skill) < 8) + { + //add offset based on how close we are to the pin/target + pos += randomNormal() * (static_cast(cro::Util::Random::value(6, 70)) / 10.f) * std::min(0.2f, std::max(1.f, std::sqrt(l2) / 100.f)); + CRO_ASSERT(!std::isnan(pos.x), ""); + } + + //more likely to overshoot with distance and lower skill + if (cro::Util::Random::value(0, 4 + skill) < 4) + { + auto dir = pos - m_playerInfo[0].position; + dir *= 1.f + (0.065f * std::min(1.f, std::sqrt(l2) / 80.f)); + pos = m_playerInfo[0].position + dir; + } + } + else + { + //iron or driver so add some arbitrary offset + //based on bounce and wind strength/dir and distance to pos + + //TODO we need a good bounce as a percentage value... + constexpr float BouncePercent = 1.05f; + pos = ((pos - m_playerInfo[0].position) * BouncePercent); - //TODO we need a good bounce as a percentage value... - constexpr float BouncePercent = 1.05f; - pos = ((pos - m_playerInfo[0].position) * BouncePercent); + auto step = pos / 5.f; + pos = m_playerInfo[0].position + step; - auto step = pos / 5.f; - pos = m_playerInfo[0].position + step; + for (auto i = 0; i < 4; ++i) + { + const auto wind = m_scene.getSystem()->getWindDirection(); + step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (7 - skill)); + pos += step; + } - for (auto i = 0; i < 4; ++i) + CRO_ASSERT(!std::isnan(pos.x), ""); + } + + //reduce the distance if we're in the rough based on skill + if (ball.terrain == TerrainID::Rough + || ball.terrain == TerrainID::Bunker) { - const auto wind = m_scene.getSystem()->getWindDirection(); - step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (6 - skill)); - pos += step; + auto dir = pos - m_playerInfo[0].position; + + float pc = 1.f - (static_cast(std::max(0, 6 - skill)) / 100.f); + dir *= pc; + + pos = m_playerInfo[0].position + dir; } + } + } + else + { + pos = m_holeData[m_currentHole].pin; - CRO_ASSERT(!std::isnan(pos.x), ""); + //if we're really daft maybe we'll hit it short + if (cro::Util::Random::value(0, 2 + (skill * 2)) == 0) + { + pos = m_playerInfo[0].position + (pinDir / 2.f); } } + + + + pos.x = std::clamp(pos.x, 0.f, 320.f); pos.z = std::clamp(pos.z, -200.f, 0.f); //test terrain height and correct final position auto result = m_scene.getSystem()->getTerrain(pos); + + //technically this means CPU players never make really bad shots + //but otherwise they just get stuck in a loop + switch (result.terrain) + { + case TerrainID::Water: + case TerrainID::Stone: + case TerrainID::Scrub: + { + std::int32_t tries = 300; + auto dir = glm::normalize(pos - m_playerInfo[0].position); + do + { + pos -= dir; + result = m_scene.getSystem()->getTerrain(pos); + } while (tries-- + && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) + && glm::length2(pos) > 1); + } + break; + default: break; + } + + pos.y = result.intersection.y; CRO_ASSERT(!std::isnan(pos.x), ""); From e53bacd1ea3cc1ad1d7f974a867654314176e84f Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 11:17:05 +0100 Subject: [PATCH 032/233] tweak fast CPU logic tweak menu layout --- samples/golf/src/golf/MenuCreation.cpp | 14 +++++----- .../golf/src/golf/server/ServerGolfRules.cpp | 28 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 20051c5f6..a2987bbde 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1568,7 +1568,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1600,7 +1600,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(3); + entity.getComponent().setSelectionIndex(4); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1728,7 +1728,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1760,7 +1760,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(3); + entity.getComponent().setSelectionIndex(4); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1844,7 +1844,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(3); + entity.getComponent().setSelectionIndex(4); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -1876,7 +1876,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(2); + entity.getComponent().setSelectionIndex(3); entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( @@ -2210,7 +2210,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent() = m_sprites[SpriteID::ReadyUp]; //which sprite is set by sending a message to this ent when we know if we're hosting or joining entity.addComponent().area = m_sprites[SpriteID::ReadyUp].getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); - entity.getComponent().setSelectionIndex(4); + entity.getComponent().setSelectionIndex(2); entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnter; entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExit; entity.getComponent().callbacks[cro::UIInput::ButtonDown] = diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 48cab6aac..605a881e1 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -69,13 +69,16 @@ void GolfState::calcCPUPosition() glm::vec3 pos = glm::vec3(0.f); std::int32_t skill = m_skillIndex; - std::int32_t offset = m_playerInfo[0].player % 2; - if (m_skillIndex > 2) - { - offset *= -1; - } - skill = std::clamp(skill + offset, 0, 6); - //skill += offset; + //std::int32_t offset = m_playerInfo[0].player % 2; + //if (m_skillIndex > 2) + //{ + // offset *= -1; + //} + + std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; + + skill = std::clamp((skill - offset), 0, 6); + //std::int32_t skill = m_playerInfo[0].player * 3; @@ -226,12 +229,17 @@ void GolfState::calcCPUPosition() } } - if (cro::Util::Random::value(0, 4 + skill) < 8) + if (cro::Util::Random::value(0, 4 + skill) < 9) { //add offset based on how close we are to the pin/target pos += randomNormal() * (static_cast(cro::Util::Random::value(6, 70)) / 10.f) * std::min(0.2f, std::max(1.f, std::sqrt(l2) / 100.f)); CRO_ASSERT(!std::isnan(pos.x), ""); } + else + { + //even if we have the skill reduce the chance of it going in + pos += randomNormal() * static_cast(cro::Util::Random::value(1, 30)) / 10.f; + } //more likely to overshoot with distance and lower skill if (cro::Util::Random::value(0, 4 + skill) < 4) @@ -248,7 +256,7 @@ void GolfState::calcCPUPosition() //TODO we need a good bounce as a percentage value... constexpr float BouncePercent = 1.05f; - pos = ((pos - m_playerInfo[0].position) * BouncePercent); + pos = ((pos - m_playerInfo[0].position) * (BouncePercent - (static_cast((m_playerInfo[0].player % 3) * 2) / 100.f))); auto step = pos / 5.f; pos = m_playerInfo[0].position + step; @@ -256,7 +264,7 @@ void GolfState::calcCPUPosition() for (auto i = 0; i < 4; ++i) { const auto wind = m_scene.getSystem()->getWindDirection(); - step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.1f * (7 - skill)); + step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.15f * (7 - skill)); pos += step; } From 006fdad8cbdea3e49fd247c3071ab2834c7e05ba Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 12:38:38 +0100 Subject: [PATCH 033/233] play golfer animation in fast cpu mode remember fast cpu setting between sessions --- samples/golf/src/golf/CPUGolfer.cpp | 16 ++++++++++------ samples/golf/src/golf/ClubhouseStateUI.cpp | 2 +- samples/golf/src/golf/GolfState.cpp | 2 +- samples/golf/src/golf/MenuAvatars.cpp | 2 +- samples/golf/src/golf/MessageOverlayState.cpp | 2 +- samples/golf/src/golf/PracticeState.cpp | 2 +- samples/golf/src/golf/server/Server.cpp | 3 ++- samples/golf/src/golf/server/Server.hpp | 2 +- samples/golf/src/golf/server/ServerGolfRules.cpp | 4 ++++ samples/golf/src/golf/server/ServerGolfState.cpp | 2 +- 10 files changed, 23 insertions(+), 14 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 40f88150a..7b2cec662 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -389,13 +389,17 @@ void CPUGolfer::setPuttingPower(float power) std::size_t CPUGolfer::getSkillIndex() const { - std::int32_t offset = m_activePlayer.player % 2; - if (m_skillIndex > 2) - { - offset *= -1; - } + //std::int32_t offset = m_activePlayer.player % 2; + //if (m_skillIndex > 2) + //{ + // offset *= -1; + //} + + std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; + + return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); - return std::min(static_cast(m_skills.size() - 1), static_cast(m_skillIndex) + offset); + //return std::min(static_cast(m_skills.size() - 1), static_cast(m_skillIndex) + offset); } //private diff --git a/samples/golf/src/golf/ClubhouseStateUI.cpp b/samples/golf/src/golf/ClubhouseStateUI.cpp index 645c4e79f..1f8f21e85 100644 --- a/samples/golf/src/golf/ClubhouseStateUI.cpp +++ b/samples/golf/src/golf/ClubhouseStateUI.cpp @@ -1014,7 +1014,7 @@ void ClubhouseState::createAvatarMenu(cro::Entity parent, std::uint32_t mouseEnt { if (!m_sharedData.clientConnection.connected) { - m_sharedData.serverInstance.launch(2, Server::GameMode::Billiards); + m_sharedData.serverInstance.launch(2, Server::GameMode::Billiards, m_sharedData.fastCPU); //small delay for server to get ready cro::Clock clock; diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index d7e1b67d3..ce873566f 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -979,7 +979,7 @@ void GolfState::handleMessage(const cro::Message& msg) } } - m_gameScene.setSystemActive(true); + m_gameScene.setSystemActive(!m_sharedData.fastCPU); } else if (data.userType == cro::Message::SkeletalAnimationEvent::Stopped) { diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 7e276ca13..f3bdff0fb 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1128,7 +1128,7 @@ void MenuState::createAvatarMenu(cro::Entity parent) { if (!m_sharedData.clientConnection.connected) { - m_sharedData.serverInstance.launch(ConstVal::MaxClients, Server::GameMode::Golf); + m_sharedData.serverInstance.launch(ConstVal::MaxClients, Server::GameMode::Golf, m_sharedData.fastCPU); #ifndef USE_GNS //small delay for server to get ready diff --git a/samples/golf/src/golf/MessageOverlayState.cpp b/samples/golf/src/golf/MessageOverlayState.cpp index dabd412af..3ecf2cdf1 100644 --- a/samples/golf/src/golf/MessageOverlayState.cpp +++ b/samples/golf/src/golf/MessageOverlayState.cpp @@ -362,7 +362,7 @@ void MessageOverlayState::buildScene() //start a local server and connect if (!m_sharedData.clientConnection.connected) { - m_sharedData.serverInstance.launch(1, Server::GameMode::Golf); + m_sharedData.serverInstance.launch(1, Server::GameMode::Golf, m_sharedData.fastCPU); //small delay for server to get ready cro::Clock clock; diff --git a/samples/golf/src/golf/PracticeState.cpp b/samples/golf/src/golf/PracticeState.cpp index bdb7ffa9f..39e9da433 100644 --- a/samples/golf/src/golf/PracticeState.cpp +++ b/samples/golf/src/golf/PracticeState.cpp @@ -359,7 +359,7 @@ void PracticeState::buildScene() //start a local server and connect if (!m_sharedData.clientConnection.connected) { - m_sharedData.serverInstance.launch(1, Server::GameMode::Golf); + m_sharedData.serverInstance.launch(1, Server::GameMode::Golf, m_sharedData.fastCPU); //small delay for server to get ready cro::Clock clock; diff --git a/samples/golf/src/golf/server/Server.cpp b/samples/golf/src/golf/server/Server.cpp index a9e9875c9..19cc43637 100644 --- a/samples/golf/src/golf/server/Server.cpp +++ b/samples/golf/src/golf/server/Server.cpp @@ -69,7 +69,7 @@ Server::~Server() } //public -void Server::launch(std::size_t maxConnections, std::int32_t gameMode) +void Server::launch(std::size_t maxConnections, std::int32_t gameMode, bool fastCPU) { //stop any existing instance first stop(); @@ -97,6 +97,7 @@ void Server::launch(std::size_t maxConnections, std::int32_t gameMode) break; } + m_sharedData.fastCPU = fastCPU; m_running = true; m_thread = std::make_unique(&Server::run, this); } diff --git a/samples/golf/src/golf/server/Server.hpp b/samples/golf/src/golf/server/Server.hpp index 5196e385b..207edc0aa 100644 --- a/samples/golf/src/golf/server/Server.hpp +++ b/samples/golf/src/golf/server/Server.hpp @@ -58,7 +58,7 @@ class Server final //max connections are not game mode dependent as the //tutorial shares a game mode with golf, but must be //limited to a single connection. - void launch(std::size_t maxConnections, std::int32_t gameMode); + void launch(std::size_t maxConnections, std::int32_t gameMode, bool fastCPU); bool running() const { return m_running; } void stop(); diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 605a881e1..958706e60 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -1,6 +1,7 @@ #include "../PacketIDs.hpp" #include "../BallSystem.hpp" #include "../Clubs.hpp" +#include "../GameConsts.hpp" #include "ServerMessages.hpp" #include "ServerGolfState.hpp" @@ -83,6 +84,9 @@ void GolfState::calcCPUPosition() //std::int32_t skill = m_playerInfo[0].player * 3; auto& ball = m_playerInfo[0].ballEntity.getComponent(); + auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Swing; + m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); + std::int32_t clubID = ClubID::Putter; //get longest range available diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index e64e85440..3953540df 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -79,7 +79,7 @@ GolfState::GolfState(SharedData& sd) initScene(); buildWorld(); - auto level = std::min(Social::getLevel(), 20) + 2; + auto level = std::min(Social::getLevel(), 24) + 2; m_skillIndex = level / 4; } From 3be846b68771c92db6bd67f0c462326261b9815f Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 13:56:29 +0100 Subject: [PATCH 034/233] play incidental crowd sounds --- samples/golf/src/golf/GolfSoundDirector.cpp | 34 ++++++++++++++++++++- samples/golf/src/golf/GolfSoundDirector.hpp | 12 +++++++- samples/golf/src/golf/GolfState.cpp | 3 ++ samples/golf/src/golf/MenuState.cpp | 3 +- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/samples/golf/src/golf/GolfSoundDirector.cpp b/samples/golf/src/golf/GolfSoundDirector.cpp index 26e8fee0e..1af274dbc 100644 --- a/samples/golf/src/golf/GolfSoundDirector.cpp +++ b/samples/golf/src/golf/GolfSoundDirector.cpp @@ -54,13 +54,17 @@ source distribution. namespace { constexpr float VoiceDelay = 0.5f; + + const cro::Time MinCrowdTime = cro::seconds(43.f); } GolfSoundDirector::GolfSoundDirector(cro::AudioResource& ar) : m_currentClient (0), m_currentPlayer (0), m_honourID (0), - m_newHole (false) + m_newHole (false), + m_crowdPositions (nullptr), + m_crowdTime (MinCrowdTime) { //this must match with AudioID enum static const std::array FilePaths = @@ -149,6 +153,10 @@ GolfSoundDirector::GolfSoundDirector(cro::AudioResource& ar) "assets/golf/sound/ambience/foot04.wav", "assets/golf/sound/bad.wav", + + "assets/golf/sound/ambience/crowd_clear_throat.wav", + "assets/golf/sound/ambience/crowd_cough.wav", + "assets/golf/sound/ambience/crowd_sigh.wav", }; std::fill(m_audioSources.begin(), m_audioSources.end(), nullptr); @@ -163,6 +171,7 @@ GolfSoundDirector::GolfSoundDirector(cro::AudioResource& ar) { //get a default sound so we at least don't have nullptr m_audioSources[i] = &ar.get(1010101); + LogW << "Failed opening " << FilePaths[i] << std::endl; } } @@ -530,6 +539,29 @@ void GolfSoundDirector::handleMessage(const cro::Message& msg) } } +void GolfSoundDirector::process(float) +{ + if (m_crowdTimer.elapsed() > m_crowdTime) + { + if (m_crowdPositions) + { + const auto& cp = *m_crowdPositions; + if (!cp.empty()) + { + auto pos = glm::vec3((cp[cro::Util::Random::value(0u, cp.size() - 1)][3])); + auto& emitter = playSound(AudioID::CrowdClearThroat + cro::Util::Random::value(0, 2), pos).getComponent(); + emitter.setMixerChannel(MixerChannel::Environment); + //emitter.setRolloff(0.5f); + + LogI << "Crowd sound at " << pos << std::endl; + } + } + + m_crowdTimer.restart(); + m_crowdTime = cro::seconds(5.f);// MinCrowdTime + cro::seconds(static_cast(cro::Util::Random::value(-5, 25))); + } +} + void GolfSoundDirector::addAudioScape(const std::string& path, cro::AudioResource& resource) { //we emplace back even if it fails/has empty path so indices match the player indices. diff --git a/samples/golf/src/golf/GolfSoundDirector.hpp b/samples/golf/src/golf/GolfSoundDirector.hpp index e03162169..402cf4bf5 100644 --- a/samples/golf/src/golf/GolfSoundDirector.hpp +++ b/samples/golf/src/golf/GolfSoundDirector.hpp @@ -1,6 +1,6 @@ /*----------------------------------------------------------------------- -Matt Marchant 2021 - 2022 +Matt Marchant 2021 - 2023 http://trederia.blogspot.com Super Video Golf - zlib licence. @@ -50,10 +50,12 @@ class GolfSoundDirector final : public SoundEffectsDirector explicit GolfSoundDirector(cro::AudioResource&); void handleMessage(const cro::Message&) override; + void process(float) override; void addAudioScape(const std::string& path, cro::AudioResource& resource); void setPlayerIndex(std::size_t client, std::size_t player, std::int32_t index); void setActivePlayer(std::size_t client, std::size_t player, bool skipAudio); + void setCrowdPositions(const std::vector& v) { m_crowdPositions = &v; } private: struct AudioID final @@ -143,6 +145,10 @@ class GolfSoundDirector final : public SoundEffectsDirector Foot04, BadSport, + CrowdClearThroat, + CrowdCough, + CrowdSigh, + Count }; }; @@ -155,6 +161,10 @@ class GolfSoundDirector final : public SoundEffectsDirector std::size_t m_honourID; bool m_newHole; + const std::vector* m_crowdPositions; + cro::Clock m_crowdTimer; + cro::Time m_crowdTime; + cro::Entity playSound(std::int32_t, glm::vec3, float = 1.f); void playSoundDelayed(std::int32_t, glm::vec3, float, float = 1.f, std::uint8_t = 1/*MixerChannel::Effects*/); cro::Entity playAvatarSound(std::int32_t, const std::string&, glm::vec3); diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index ce873566f..d7d674610 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -6175,6 +6175,9 @@ void GolfState::setCurrentHole(std::uint16_t holeInfo) m_depthMap.forceSwap(); //make sure we're reading the correct texture anyway } m_waterEnt.getComponent().setMaterialProperty(0, "u_depthMap", m_depthMap.getTexture()); + + + m_gameScene.getDirector()->setCrowdPositions(m_holeData[m_currentHole].crowdPositions); } void GolfState::setCameraPosition(glm::vec3 position, float height, float viewOffset) diff --git a/samples/golf/src/golf/MenuState.cpp b/samples/golf/src/golf/MenuState.cpp index 644270207..43bf3ae7b 100644 --- a/samples/golf/src/golf/MenuState.cpp +++ b/samples/golf/src/golf/MenuState.cpp @@ -181,12 +181,13 @@ MenuState::MenuState(cro::StateStack& stack, cro::State::Context context, Shared #endif updateUnlockedItems(); - +#ifndef CRO_DEBUG_ cacheState(StateID::Unlock); cacheState(StateID::Options); cacheState(StateID::Profile); cacheState(StateID::Practice); cacheState(StateID::Keyboard); +#endif }); context.mainWindow.setMouseCaptured(false); From 811ab36fbf370f278fc2a2336182d26a178d2ade Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 14:19:30 +0100 Subject: [PATCH 035/233] show correct hole number when course reversed --- samples/golf/src/golf/GolfSoundDirector.cpp | 4 +++- samples/golf/src/golf/GolfState.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/GolfSoundDirector.cpp b/samples/golf/src/golf/GolfSoundDirector.cpp index 1af274dbc..967392c32 100644 --- a/samples/golf/src/golf/GolfSoundDirector.cpp +++ b/samples/golf/src/golf/GolfSoundDirector.cpp @@ -539,8 +539,10 @@ void GolfSoundDirector::handleMessage(const cro::Message& msg) } } -void GolfSoundDirector::process(float) +void GolfSoundDirector::process(float dt) { + SoundEffectsDirector::process(dt); + if (m_crowdTimer.elapsed() > m_crowdTime) { if (m_crowdPositions) diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index d7d674610..473c90e9c 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -6103,9 +6103,14 @@ void GolfState::setCurrentHole(std::uint16_t holeInfo) [&](cro::Entity e, float) { auto holeNumber = m_currentHole + 1; + if (m_sharedData.reverseCourse) + { + holeNumber = (m_holeData.size() + 1) - holeNumber; + } + if (m_sharedData.holeCount == 2) { - holeNumber += 9; + holeNumber += m_holeData.size();// 9; } auto& data = e.getComponent().getUserData(); From 050e06da2170d2470c88f8368ba49a48d27c85be Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 15:13:06 +0100 Subject: [PATCH 036/233] tweak fast CPU randomiser --- samples/golf/src/golf/GolfSoundDirector.cpp | 1 - samples/golf/src/golf/GolfState.cpp | 5 +++-- samples/golf/src/golf/server/ServerGolfRules.cpp | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/GolfSoundDirector.cpp b/samples/golf/src/golf/GolfSoundDirector.cpp index 967392c32..a8d0753fc 100644 --- a/samples/golf/src/golf/GolfSoundDirector.cpp +++ b/samples/golf/src/golf/GolfSoundDirector.cpp @@ -171,7 +171,6 @@ GolfSoundDirector::GolfSoundDirector(cro::AudioResource& ar) { //get a default sound so we at least don't have nullptr m_audioSources[i] = &ar.get(1010101); - LogW << "Failed opening " << FilePaths[i] << std::endl; } } diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 473c90e9c..3cb6ac2cc 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -876,8 +876,9 @@ void GolfState::handleMessage(const cro::Message& msg) m_gameScene.getSystem()->setActiveClub(getClub()); + auto isCPU = m_sharedData.localConnectionData.playerData[m_currentPlayer.player].isCPU; if (m_currentPlayer.client == m_sharedData.localConnectionData.connectionID - && !m_sharedData.localConnectionData.playerData[m_currentPlayer.player].isCPU) + && !isCPU) { auto strLow = static_cast(50000.f * m_inputParser.getPower()) * m_sharedData.enableRumble; auto strHigh = static_cast(35000.f * m_inputParser.getPower()) * m_sharedData.enableRumble; @@ -979,7 +980,7 @@ void GolfState::handleMessage(const cro::Message& msg) } } - m_gameScene.setSystemActive(!m_sharedData.fastCPU); + m_gameScene.setSystemActive(!(isCPU && m_sharedData.fastCPU)); } else if (data.userType == cro::Message::SkeletalAnimationEvent::Stopped) { diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 958706e60..89679b0c0 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -77,9 +77,14 @@ void GolfState::calcCPUPosition() //} std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; - skill = std::clamp((skill - offset), 0, 6); + //randomly invert skill + if (cro::Util::Random::value(0, 49) == 25) + { + skill = (skill + 3) % 6; + } + //std::int32_t skill = m_playerInfo[0].player * 3; @@ -191,7 +196,7 @@ void GolfState::calcCPUPosition() //if club is not a putter, and VERY slim chance if not a wedge if (ball.terrain == TerrainID::Green) { - if (cro::Util::Random::value(0, 3 + (skill / 2)) == 0) + if (cro::Util::Random::value(0, 3 + (skill / 2)) == (m_playerInfo[0].player % 4)) { //add target offset pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); @@ -227,7 +232,7 @@ void GolfState::calcCPUPosition() //if len > 20 almost always add offset so we don't hole the ball if (l2 > (20.f * 20.f)) { - if (cro::Util::Random::value(0, 100 - (skill * 10)) != 0) + if (cro::Util::Random::value(0, 100 - (skill * 10)) != m_playerInfo[0].player) { pos += randomNormal() * static_cast(cro::Util::Random::value(3, 10)) / 10.f; } From bf5d2e03772a092d7a0112388e9804607cbba576 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 12 Jun 2023 20:59:02 +0100 Subject: [PATCH 037/233] update incidental audio --- samples/golf/src/golf/GolfSoundDirector.cpp | 8 +++----- samples/golf/src/golf/GolfState.cpp | 12 +++++++++++- samples/golf/src/golf/server/ServerGolfRules.cpp | 12 ++++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/samples/golf/src/golf/GolfSoundDirector.cpp b/samples/golf/src/golf/GolfSoundDirector.cpp index a8d0753fc..53538f57e 100644 --- a/samples/golf/src/golf/GolfSoundDirector.cpp +++ b/samples/golf/src/golf/GolfSoundDirector.cpp @@ -550,16 +550,14 @@ void GolfSoundDirector::process(float dt) if (!cp.empty()) { auto pos = glm::vec3((cp[cro::Util::Random::value(0u, cp.size() - 1)][3])); - auto& emitter = playSound(AudioID::CrowdClearThroat + cro::Util::Random::value(0, 2), pos).getComponent(); + auto& emitter = playSound(AudioID::CrowdClearThroat + cro::Util::Random::value(0, 2), pos, 1.6f).getComponent(); emitter.setMixerChannel(MixerChannel::Environment); - //emitter.setRolloff(0.5f); - - LogI << "Crowd sound at " << pos << std::endl; + emitter.setRolloff(0.45f); } } m_crowdTimer.restart(); - m_crowdTime = cro::seconds(5.f);// MinCrowdTime + cro::seconds(static_cast(cro::Util::Random::value(-5, 25))); + m_crowdTime = MinCrowdTime + cro::seconds(static_cast(cro::Util::Random::value(-5, 25))); } } diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 3cb6ac2cc..121386af4 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -5542,7 +5542,17 @@ void GolfState::handleNetEvent(const net::NetEvent& evt) } }*/ - m_activeAvatar->model.getComponent().play(m_activeAvatar->animationIDs[animID], 1.f, 0.4f); + /*bool isCPU = m_sharedData.connectionData[m_currentPlayer.client].playerData[m_currentPlayer.player].isCPU + && m_sharedData.fastCPU;*/ + + auto anim = m_activeAvatar->animationIDs[animID]; + auto& skel = m_activeAvatar->model.getComponent(); + + if (skel.getState() == cro::Skeleton::Stopped + || skel.getActiveAnimations().second != anim) + { + skel.play(anim, /*isCPU ? 2.f :*/ 1.f, 0.4f); + } } } break; diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 89679b0c0..5fed257a0 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -41,7 +41,7 @@ void GolfState::makeCPUMove() //wrap in an ent so we can add a small delay auto entity = m_scene.createEntity(); entity.addComponent().active = true; - entity.getComponent().setUserData(1.f); + entity.getComponent().setUserData(1.2f); entity.getComponent().function = [&](cro::Entity e, float dt) { @@ -89,8 +89,7 @@ void GolfState::calcCPUPosition() //std::int32_t skill = m_playerInfo[0].player * 3; auto& ball = m_playerInfo[0].ballEntity.getComponent(); - auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Swing; - m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); + auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; std::int32_t clubID = ClubID::Putter; @@ -353,9 +352,11 @@ void GolfState::calcCPUPosition() default: ball.state = Ball::State::Paused; break; - case TerrainID::Fairway: case TerrainID::Bunker: case TerrainID::Rough: + animID = AnimationID::Disappoint; + [[fallthrough]]; + case TerrainID::Fairway: ball.state = Ball::State::Flight; ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem break; @@ -368,8 +369,11 @@ void GolfState::calcCPUPosition() case TerrainID::Stone: case TerrainID::Water: ball.state = Ball::State::Reset; + animID = AnimationID::Disappoint; break; } + + m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); } void GolfState::handleDefaultRules(const GolfBallEvent& data) From 3ea28bc27d7318499238c8f75afa9f78ddc6a7f4 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 13 Jun 2023 12:39:12 +0100 Subject: [PATCH 038/233] add sub-menu for hall of fame --- samples/golf/src/GolfGame.cpp | 4 +- samples/golf/src/golf/ClubhouseState.cpp | 82 ++++-- samples/golf/src/golf/ClubhouseState.hpp | 22 ++ samples/golf/src/golf/ClubhouseStateUI.cpp | 293 +++++++++++++++++++-- samples/golf/src/golf/TrophyState.cpp | 40 +-- 5 files changed, 369 insertions(+), 72 deletions(-) diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 5a0866a04..c0341cc90 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -715,9 +715,9 @@ bool GolfGame::initialise() #ifdef CRO_DEBUG_ //m_stateStack.pushState(StateID::DrivingRange); //can't go straight to this because menu needs to parse avatar data //m_stateStack.pushState(StateID::Bush); - //m_stateStack.pushState(StateID::Clubhouse); + m_stateStack.pushState(StateID::Clubhouse); //m_stateStack.pushState(StateID::SplashScreen); - m_stateStack.pushState(StateID::Menu); + //m_stateStack.pushState(StateID::Menu); //m_stateStack.pushState(StateID::SQLite); #else m_stateStack.pushState(StateID::SplashScreen); diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index 2dae70099..1a6db1d74 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -127,7 +127,8 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha m_viewScale (2.f), m_currentMenu (MenuID::Main), m_prevMenu (MenuID::Main), - m_gameCreationIndex (0) + m_gameCreationIndex (0), + m_arcadeIndex (0) { //if we were returning from arcade this tidies up, else does nothing gg.unloadPlugin(); @@ -280,27 +281,28 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha Social::setGroup(0); #ifdef CRO_DEBUG_ - //registerWindow([&]() - // { - // if (ImGui::Begin("Buns")) - // { - // float maxDepth = m_backgroundScene.getActiveCamera().getComponent().getMaxShadowDistance(); - // if (ImGui::SliderFloat("Depth", &maxDepth, 1.f, 30.f)) - // { - // m_backgroundScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDepth); - // } - - // float ext = m_backgroundScene.getActiveCamera().getComponent().getShadowExpansion(); - // if (ImGui::SliderFloat("ext", &ext, 0.f, 30.f)) - // { - // m_backgroundScene.getActiveCamera().getComponent().setShadowExpansion(ext); - // } - - // auto rot = m_backgroundScene.getSunlight().getComponent().getRotation(); - // ImGui::Text("%3.3f, %3.3f, %3.3f, %3.3f", rot.x, rot.y, rot.z, rot.w); - // } - // ImGui::End(); - // }); + registerWindow([&]() + { + if (ImGui::Begin("Buns")) + { + ImGui::Text("Index %u", m_arcadeIndex); + //float maxDepth = m_backgroundScene.getActiveCamera().getComponent().getMaxShadowDistance(); + //if (ImGui::SliderFloat("Depth", &maxDepth, 1.f, 30.f)) + //{ + // m_backgroundScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDepth); + //} + + //float ext = m_backgroundScene.getActiveCamera().getComponent().getShadowExpansion(); + //if (ImGui::SliderFloat("ext", &ext, 0.f, 30.f)) + //{ + // m_backgroundScene.getActiveCamera().getComponent().setShadowExpansion(ext); + //} + + //auto rot = m_backgroundScene.getSunlight().getComponent().getRotation(); + //ImGui::Text("%3.3f, %3.3f, %3.3f, %3.3f", rot.x, rot.y, rot.z, rot.w); + } + ImGui::End(); + }); #endif } @@ -321,7 +323,7 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) m_uiScene.getSystem()->setActiveGroup(MenuID::Dummy); m_menuEntities[m_currentMenu].getComponent().getUserData().targetMenu = MenuID::Main; m_menuEntities[m_currentMenu].getComponent().active = true; - + m_currentMenu = MenuID::Dummy; m_audioEnts[AudioID::Back].getComponent().play(); break; case MenuID::Join: @@ -329,13 +331,20 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) m_uiScene.getSystem()->setActiveGroup(MenuID::Dummy); m_menuEntities[m_currentMenu].getComponent().getUserData().targetMenu = MenuID::PlayerSelect; m_menuEntities[m_currentMenu].getComponent().active = true; - + m_currentMenu = MenuID::Dummy; m_audioEnts[AudioID::Back].getComponent().play(); break; case MenuID::Lobby: m_audioEnts[AudioID::Back].getComponent().play(); quitLobby(); break; + case MenuID::HallOfFame: + m_uiScene.getSystem()->setActiveGroup(MenuID::Dummy); + m_menuEntities[m_currentMenu].getComponent().getUserData().state = 2; + m_menuEntities[m_currentMenu].getComponent().active = true; + m_currentMenu = MenuID::Dummy; + m_audioEnts[AudioID::Back].getComponent().play(); + break; } }; @@ -343,10 +352,33 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) { switch (evt.key.keysym.sym) { - default: break; + default: + if (m_arcadeIndex < Arcade.size()) + { + if (evt.key.keysym.sym == Arcade[m_arcadeIndex]) + { + m_arcadeIndex++; + if (m_arcadeIndex == Arcade.size()) + { + //MAGIC + if (m_arcadeEnt.isValid()) + { + m_arcadeEnt.getComponent().setScale(glm::vec2(1.f)); + m_arcadeEnt.getComponent().enabled = true; + } + } + } + else + { + m_arcadeIndex = 0; + } + } + + break; case SDLK_RETURN: case SDLK_RETURN2: case SDLK_KP_ENTER: + m_arcadeIndex = 0; applyTextEdit(); break; case SDLK_ESCAPE: diff --git a/samples/golf/src/golf/ClubhouseState.hpp b/samples/golf/src/golf/ClubhouseState.hpp index 6e5256b48..5ce8cf0de 100644 --- a/samples/golf/src/golf/ClubhouseState.hpp +++ b/samples/golf/src/golf/ClubhouseState.hpp @@ -78,6 +78,12 @@ struct TableClientData final : public TableData std::int32_t ballSkinIndex = 0; }; +struct HOFCallbackData final +{ + std::int32_t state = 0; //in,hold,out + float progress = 0.f; +}; + struct SharedStateData; struct SharedProfileData; class ClubhouseState final : public cro::State, public cro::GuiClient @@ -102,6 +108,7 @@ class ClubhouseState final : public cro::State, public cro::GuiClient Dummy, Main, PlayerSelect, Join, Lobby, + HallOfFame, Inactive, Count @@ -196,6 +203,20 @@ class ClubhouseState final : public cro::State, public cro::GuiClient LobbyPager m_lobbyPager; + static constexpr std::array Arcade = + { + SDLK_UP, + SDLK_UP, + SDLK_DOWN, + SDLK_DOWN, + SDLK_LEFT, + SDLK_RIGHT, + SDLK_LEFT, + SDLK_RIGHT, + }; + std::size_t m_arcadeIndex; + cro::Entity m_arcadeEnt; + void addSystems(); void loadResources(); void validateTables(); @@ -208,6 +229,7 @@ class ClubhouseState final : public cro::State, public cro::GuiClient void createJoinMenu(cro::Entity, std::uint32_t, std::uint32_t); void createBrowserMenu(cro::Entity, std::uint32_t, std::uint32_t); void createLobbyMenu(cro::Entity, std::uint32_t, std::uint32_t); + void createStatMenu(cro::Entity, std::uint32_t, std::uint32_t); void updateLobbyData(const net::NetEvent&); void updateLobbyList(); diff --git a/samples/golf/src/golf/ClubhouseStateUI.cpp b/samples/golf/src/golf/ClubhouseStateUI.cpp index 1f8f21e85..e3f7433b2 100644 --- a/samples/golf/src/golf/ClubhouseStateUI.cpp +++ b/samples/golf/src/golf/ClubhouseStateUI.cpp @@ -50,6 +50,7 @@ source distribution. #include #include #include +#include #include @@ -114,6 +115,7 @@ void ClubhouseState::createUI() createJoinMenu(rootNode, mouseEnterCallback, mouseExitCallback); #endif createLobbyMenu(rootNode, mouseEnterCallback, mouseExitCallback); + createStatMenu(rootNode, mouseEnterCallback, mouseExitCallback); //hack to activate main menu - this will eventually be done //by animation callback @@ -341,6 +343,40 @@ void ClubhouseState::createMainMenu(cro::Entity parent, std::uint32_t mouseEnter entity = createButton("No Tables Found"); } + + + //trophy shelf + entity = createButton("Hall Of Fame"); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + //requestStackPush(StateID::Trophy); + m_currentMenu = MenuID::Dummy; + m_uiScene.getSystem()->setActiveGroup(m_currentMenu); + + m_menuEntities[MenuID::HallOfFame].getComponent().getUserData().state = 0; + m_menuEntities[MenuID::HallOfFame].getComponent().active = true; + + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); + + //options + entity = createButton("Options"); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + requestStackPush(StateID::Options); + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); + + + //arcade bool hasArcade = false; if (hasArcade = cro::FileSystem::directoryExists("plugins"); hasArcade) @@ -369,6 +405,11 @@ void ClubhouseState::createMainMenu(cro::Entity parent, std::uint32_t mouseEnter m_golfGame.loadPlugin(pluginPath); } }); + + entity.getComponent().enabled = false; + entity.getComponent().setScale(glm::vec2(0.f)); + m_arcadeEnt = entity; + } else { @@ -376,31 +417,6 @@ void ClubhouseState::createMainMenu(cro::Entity parent, std::uint32_t mouseEnter } } } - - //trophy shelf - entity = createButton("Trophy Shelf"); - entity.getComponent().callbacks[cro::UIInput::ButtonUp] = - m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) - { - if (activated(evt)) - { - requestStackPush(StateID::Trophy); - m_audioEnts[AudioID::Accept].getComponent().play(); - } - }); - - //options - entity = createButton("Options"); - entity.getComponent().callbacks[cro::UIInput::ButtonUp] = - m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) - { - if (activated(evt)) - { - requestStackPush(StateID::Options); - m_audioEnts[AudioID::Accept].getComponent().play(); - } - }); - if (!hasArcade) { textPos.y -= LineSpacing; //remove this when adding back arcade @@ -2152,6 +2168,233 @@ void ClubhouseState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnte #endif } +void ClubhouseState::createStatMenu(cro::Entity parent, std::uint32_t mouseEnter, std::uint32_t mouseExit) +{ + //transform node + auto entity = m_uiScene.createEntity(); + entity.addComponent(); + + entity.addComponent().setUserData(); + entity.getComponent().function = + [&, parent](cro::Entity e, float dt) + { + const float Speed = dt * 3.f; + auto& [state, progress] = e.getComponent().getUserData(); + switch (state) + { + default: break; + case 0: + //in + progress = std::min(1.f, progress + Speed); + if (progress == 1) + { + state = 1; + m_currentMenu = MenuID::HallOfFame; + m_uiScene.getSystem()->setActiveGroup(m_currentMenu); + } + break; + case 1: + //hold + + break; + case 2: + //out + progress = std::max(0.f, progress - Speed); + if (progress == 0) + { + state = 0; + m_currentMenu = MenuID::Main; + m_uiScene.getSystem()->setActiveGroup(m_currentMenu); + e.getComponent().active = false; + } + break; + } + + //centre in screen + auto pos = glm::vec2(cro::App::getWindow().getSize()) / parent.getComponent().getScale().x; + pos /= 2.f; + pos.x = std::floor(pos.x); + pos.y = std::floor(pos.y); + e.getComponent().setPosition(glm::vec3(pos, 0.2f)); + }; + + parent.getComponent().addChild(entity.getComponent()); + m_menuEntities[MenuID::HallOfFame] = entity; + + //background node + const auto c = cro::Colour::Black; + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 0.f, 0.f, 0.1f }); + entity.addComponent().setVertexData( + { + cro::Vertex2D(glm::vec2(-0.5f, 0.5f), c), + cro::Vertex2D(glm::vec2(-0.5f), c), + cro::Vertex2D(glm::vec2(0.5f), c), + cro::Vertex2D(glm::vec2(0.5f, -0.5f), c) + }); + entity.getComponent().setPrimitiveType(GL_TRIANGLE_STRIP); + entity.addComponent().active = true; + entity.getComponent().function = + [&, parent](cro::Entity e, float) + { + if (m_menuEntities[MenuID::HallOfFame].getComponent().active) + { + const auto progress = m_menuEntities[MenuID::HallOfFame].getComponent().getUserData().progress; + + auto scale = glm::vec2(cro::App::getWindow().getSize()) / parent.getComponent().getScale().x; + e.getComponent().setScale(scale); + + float alpha = BackgroundAlpha * progress; + auto& verts = e.getComponent().getVertexData(); + for (auto& v : verts) + { + v.colour.setAlpha(alpha); + } + } + else + { + e.getComponent().setScale(glm::vec2(0.f)); + } + }; + m_menuEntities[MenuID::HallOfFame].getComponent().addChild(entity.getComponent()); + + cro::SpriteSheet spriteSheet; + spriteSheet.loadFromFile("assets/golf/sprites/main_menu.spt", m_resources.textures); + + //cursor entity + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ -100.f, 0.f, 0.1f }); + entity.addComponent(); + entity.addComponent() = spriteSheet.getSprite("cursor"); + entity.addComponent().play(0); + auto cursorEnt = entity; + + //TODO this could be recycled from main menu + mouseEnter = m_uiScene.getSystem()->addCallback( + [entity](cro::Entity e) mutable + { + e.getComponent().setFillColour(TextGoldColour); + e.getComponent().play(); + entity.getComponent().setPosition(e.getComponent().getPosition() + glm::vec3(-20.f, -7.f, 0.f)); + entity.getComponent().setScale(glm::vec2(1.f)); + }); + + + spriteSheet.loadFromFile("assets/golf/sprites/clubhouse_menu.spt", m_resources.textures); + + //menu panel node + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 0.f, 0.f, 0.2f }); + entity.getComponent().addChild(cursorEnt.getComponent()); + entity.addComponent(); + entity.addComponent() = spriteSheet.getSprite("menu_background"); + auto bounds = entity.getComponent().getTextureBounds(); + entity.getComponent().setOrigin({ std::floor(bounds.width / 2.f), std::floor(bounds.height / 2.f) }); + + entity.addComponent().active = true; + entity.getComponent().function = + [&, parent](cro::Entity e, float) + { + if (m_menuEntities[MenuID::HallOfFame].getComponent().active) + { + const auto [state, progress] = m_menuEntities[MenuID::HallOfFame].getComponent().getUserData(); + + switch (state) + { + default: + { + e.getComponent().setScale(glm::vec2(1.f)); + } + break; + case 0: + { + float scale = cro::Util::Easing::easeOutQuint(progress); + e.getComponent().setScale(glm::vec2(1.f, scale)); + } + break; + case 2: + { + float scale = cro::Util::Easing::easeOutQuad(progress); + e.getComponent().setScale(glm::vec2(scale, 1.f)); + } + break; + } + } + else + { + e.getComponent().setScale(glm::vec2(0.f)); + } + }; + m_menuEntities[MenuID::HallOfFame].getComponent().addChild(entity.getComponent()); + + + auto boardEntity = entity; + + auto& font = m_sharedData.sharedResources->fonts.get(FontID::UI); + static constexpr float TextOffset = 28.f; + static constexpr float LineSpacing = 10.f; + glm::vec3 textPos = { TextOffset, 62.f, 0.1f }; + + auto createButton = [&](const std::string& label) + { + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition(textPos); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent(font).setString(label); + entity.getComponent().setCharacterSize(UITextSize); + entity.getComponent().setFillColour(TextNormalColour); + entity.addComponent().area = cro::Text::getLocalBounds(entity); + entity.getComponent().setGroup(MenuID::HallOfFame); + entity.getComponent().callbacks[cro::UIInput::Selected] = mouseEnter; + entity.getComponent().callbacks[cro::UIInput::Unselected] = mouseExit; + + boardEntity.getComponent().addChild(entity.getComponent()); + textPos.y -= LineSpacing; + + return entity; + }; + + //trophy cab + entity = createButton("Trophy Cabinet"); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + requestStackPush(StateID::Trophy); + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); + + + //personal stats + entity = createButton("Personal Stats"); + + //monthly challenge - move to personal stats? + + + //leaderboards + entity = createButton("Leaderboards"); + textPos.y -= LineSpacing; + + //back + entity = createButton("Back"); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_uiScene.getSystem()->setActiveGroup(MenuID::Dummy); + m_menuEntities[m_currentMenu].getComponent().getUserData().state = 2; + m_menuEntities[m_currentMenu].getComponent().active = true; + m_currentMenu = MenuID::Dummy; + + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); +} + void ClubhouseState::updateLobbyData(const net::NetEvent& evt) { ConnectionData cd; diff --git a/samples/golf/src/golf/TrophyState.cpp b/samples/golf/src/golf/TrophyState.cpp index efee1f607..2f7f92843 100644 --- a/samples/golf/src/golf/TrophyState.cpp +++ b/samples/golf/src/golf/TrophyState.cpp @@ -137,26 +137,26 @@ TrophyState::TrophyState(cro::StateStack& ss, cro::State::Context ctx, SharedSta };*/ #ifdef CRO_DEBUG_ - registerWindow([&]() - { - if (ImGui::Begin("buns")) - { - static float overshoot = 0.f; - if (ImGui::SliderFloat("expansion", &overshoot, 0.f, 15.f)) - { - m_trophyScene.getActiveCamera().getComponent().setShadowExpansion(overshoot); - } - - static float maxDistance = 10.f; - if (ImGui::SliderFloat("max dist", &maxDistance, 0.4f, 10.f)) - { - m_trophyScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDistance); - } - - ImGui::Image(m_trophyScene.getActiveCamera().getComponent().shadowMapBuffer.getTexture(), { 256.f, 256.f }, { 0.f, 1.f }, { 1.f, 0.f }); - } - ImGui::End(); - }); +// registerWindow([&]() +// { +// if (ImGui::Begin("buns")) +// { +// static float overshoot = 0.f; +// if (ImGui::SliderFloat("expansion", &overshoot, 0.f, 15.f)) +// { +// m_trophyScene.getActiveCamera().getComponent().setShadowExpansion(overshoot); +// } +// +// static float maxDistance = 10.f; +// if (ImGui::SliderFloat("max dist", &maxDistance, 0.4f, 10.f)) +// { +// m_trophyScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDistance); +// } +// +// ImGui::Image(m_trophyScene.getActiveCamera().getComponent().shadowMapBuffer.getTexture(), { 256.f, 256.f }, { 0.f, 1.f }, { 1.f, 0.f }); +// } +// ImGui::End(); +// }); #endif } From a449e87658bf7d9ea22dd12fd94b90dfe96d7c64 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 13 Jun 2023 13:58:17 +0100 Subject: [PATCH 039/233] stub out leaderboard state --- samples/golf/golf.vcxproj | 2 + samples/golf/golf.vcxproj.filters | 6 + samples/golf/src/GolfGame.cpp | 2 + samples/golf/src/StateIDs.hpp | 1 + samples/golf/src/golf/CMakeLists.txt | 1 + samples/golf/src/golf/ClubhouseState.cpp | 1 + samples/golf/src/golf/ClubhouseStateUI.cpp | 13 + samples/golf/src/golf/LeaderboardState.cpp | 556 +++++++++++++++++++++ samples/golf/src/golf/LeaderboardState.hpp | 88 ++++ 9 files changed, 670 insertions(+) create mode 100644 samples/golf/src/golf/LeaderboardState.cpp create mode 100644 samples/golf/src/golf/LeaderboardState.hpp diff --git a/samples/golf/golf.vcxproj b/samples/golf/golf.vcxproj index 58ac97d4e..09e87e84c 100644 --- a/samples/golf/golf.vcxproj +++ b/samples/golf/golf.vcxproj @@ -480,6 +480,7 @@ + @@ -583,6 +584,7 @@ + diff --git a/samples/golf/golf.vcxproj.filters b/samples/golf/golf.vcxproj.filters index 5d5a6eaa8..b5825f9e4 100644 --- a/samples/golf/golf.vcxproj.filters +++ b/samples/golf/golf.vcxproj.filters @@ -360,6 +360,9 @@ Source Files\golf\server + + Source Files\golf\client\states + @@ -722,6 +725,9 @@ Header Files\sqlite + + Header Files\golf\client\states + diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index c0341cc90..53989814a 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -46,6 +46,7 @@ source distribution. #include "golf/CreditsState.hpp" #include "golf/UnlockState.hpp" #include "golf/ProfileState.hpp" +#include "golf/LeaderboardState.hpp" #include "golf/EventOverlay.hpp" #include "golf/MenuConsts.hpp" #include "golf/GameConsts.hpp" @@ -174,6 +175,7 @@ GolfGame::GolfGame() m_stateStack.registerState(StateID::Billiards, m_sharedData); m_stateStack.registerState(StateID::Trophy, m_sharedData); m_stateStack.registerState(StateID::Playlist, m_sharedData); + m_stateStack.registerState(StateID::Leaderboard, m_sharedData); m_stateStack.registerState(StateID::Bush, m_sharedData); m_stateStack.registerState(StateID::MessageOverlay, m_sharedData); m_stateStack.registerState(StateID::EventOverlay); diff --git a/samples/golf/src/StateIDs.hpp b/samples/golf/src/StateIDs.hpp index 6b8f6bcfa..3eb2435f9 100644 --- a/samples/golf/src/StateIDs.hpp +++ b/samples/golf/src/StateIDs.hpp @@ -55,6 +55,7 @@ struct StateID final EventOverlay, //consumes events if the overlay is open Unlock, Profile, + Leaderboard, SQLite, //used for testing SQLite features diff --git a/samples/golf/src/golf/CMakeLists.txt b/samples/golf/src/golf/CMakeLists.txt index 32e842c0f..c4d48d9b3 100644 --- a/samples/golf/src/golf/CMakeLists.txt +++ b/samples/golf/src/golf/CMakeLists.txt @@ -35,6 +35,7 @@ set(GOLF_SRC # ${PROJECT_DIR}/golf/InterpolationComponent.cpp # ${PROJECT_DIR}/golf/InterpolationSystem.cpp ${PROJECT_DIR}/golf/KeyboardState.cpp + ${PROJECT_DIR}/golf/LeaderboardState.cpp ${PROJECT_DIR}/golf/LeaderboardTexture.cpp ${PROJECT_DIR}/golf/MenuAvatars.cpp ${PROJECT_DIR}/golf/MenuCallbacks.cpp diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index 1a6db1d74..c60d222af 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -142,6 +142,7 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha cacheState(StateID::Options); cacheState(StateID::Trophy); + cacheState(StateID::Leaderboard); }); ctx.mainWindow.setMouseCaptured(false); diff --git a/samples/golf/src/golf/ClubhouseStateUI.cpp b/samples/golf/src/golf/ClubhouseStateUI.cpp index e3f7433b2..c0bb15fd7 100644 --- a/samples/golf/src/golf/ClubhouseStateUI.cpp +++ b/samples/golf/src/golf/ClubhouseStateUI.cpp @@ -2375,7 +2375,20 @@ void ClubhouseState::createStatMenu(cro::Entity parent, std::uint32_t mouseEnter //leaderboards +#ifdef USE_GNS entity = createButton("Leaderboards"); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + m_uiScene.getSystem()->addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + requestStackPush(StateID::Leaderboard); + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); +#else + textPos.y -= LineSpacing; +#endif textPos.y -= LineSpacing; //back diff --git a/samples/golf/src/golf/LeaderboardState.cpp b/samples/golf/src/golf/LeaderboardState.cpp new file mode 100644 index 000000000..c19766064 --- /dev/null +++ b/samples/golf/src/golf/LeaderboardState.cpp @@ -0,0 +1,556 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#include "LeaderboardState.hpp" +#include "SharedStateData.hpp" +#include "CommonConsts.hpp" +#include "CommandIDs.hpp" +#include "MenuConsts.hpp" +#include "GameConsts.hpp" +#include "TextAnimCallback.hpp" +#include "MessageIDs.hpp" +#include "../GolfGame.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + struct MenuID final + { + enum + { + Main, Confirm + }; + }; + + std::function resetConfirmation; +} + +LeaderboardState::LeaderboardState(cro::StateStack& ss, cro::State::Context ctx, SharedStateData& sd) + : cro::State (ss, ctx), + m_scene (ctx.appInstance.getMessageBus()), + m_sharedData (sd), + m_viewScale (2.f), + m_requestRestart (false), + m_confirmationType (ConfirmType::Quit) +{ + ctx.mainWindow.setMouseCaptured(false); + + buildScene(); +} + +//public +bool LeaderboardState::handleEvent(const cro::Event& evt) +{ + if (ImGui::GetIO().WantCaptureKeyboard + || ImGui::GetIO().WantCaptureMouse + || m_rootNode.getComponent().active) + { + return false; + } + + if (evt.type == SDL_KEYUP) + { + if (evt.key.keysym.sym == SDLK_BACKSPACE + || evt.key.keysym.sym == SDLK_ESCAPE + || evt.key.keysym.sym == SDLK_p) + { + quitState(); + return false; + } + } + else if (evt.type == SDL_KEYDOWN) + { + switch (evt.key.keysym.sym) + { + default: break; + case SDLK_UP: + case SDLK_DOWN: + case SDLK_LEFT: + case SDLK_RIGHT: + cro::App::getWindow().setMouseCaptured(true); + break; + } + } + else if (evt.type == SDL_CONTROLLERBUTTONUP) + { + cro::App::getWindow().setMouseCaptured(true); + if (evt.cbutton.button == cro::GameController::ButtonB + || evt.cbutton.button == cro::GameController::ButtonStart) + { + quitState(); + return false; + } + } + else if (evt.type == SDL_MOUSEBUTTONUP) + { + if (evt.button.button == SDL_BUTTON_RIGHT) + { + quitState(); + return false; + } + } + else if (evt.type == SDL_CONTROLLERAXISMOTION) + { + if (evt.caxis.value > LeftThumbDeadZone) + { + cro::App::getWindow().setMouseCaptured(true); + } + } + else if (evt.type == SDL_MOUSEMOTION) + { + cro::App::getWindow().setMouseCaptured(false); + } + + m_scene.getSystem()->handleEvent(evt); + m_scene.forwardEvent(evt); + return false; +} + +void LeaderboardState::handleMessage(const cro::Message& msg) +{ + m_scene.forwardMessage(msg); +} + +bool LeaderboardState::simulate(float dt) +{ + m_scene.simulate(dt); + return true; +} + +void LeaderboardState::render() +{ + m_scene.render(); +} + +//private +void LeaderboardState::buildScene() +{ + auto& mb = getContext().appInstance.getMessageBus(); + m_scene.addSystem(mb);// ->setActiveControllerID(m_sharedData.inputBinding.controllerID); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + m_scene.addSystem(mb); + + m_scene.setSystemActive(false); + + m_menuSounds.loadFromFile("assets/golf/sound/menu.xas", m_sharedData.sharedResources->audio); + m_audioEnts[AudioID::Accept] = m_scene.createEntity(); + m_audioEnts[AudioID::Accept].addComponent() = m_menuSounds.getEmitter("accept"); + m_audioEnts[AudioID::Back] = m_scene.createEntity(); + m_audioEnts[AudioID::Back].addComponent() = m_menuSounds.getEmitter("back"); + + + struct RootCallbackData final + { + enum + { + FadeIn, FadeOut + }state = FadeIn; + float currTime = 0.f; + }; + + auto rootNode = m_scene.createEntity(); + rootNode.addComponent(); + rootNode.addComponent().active = true; + rootNode.getComponent().setUserData(); + rootNode.getComponent().function = + [&](cro::Entity e, float dt) + { + auto& [state, currTime] = e.getComponent().getUserData(); + + switch (state) + { + default: break; + case RootCallbackData::FadeIn: + currTime = std::min(1.f, currTime + (dt * 2.f)); + e.getComponent().setScale(m_viewScale * cro::Util::Easing::easeOutQuint(currTime)); + + { + auto reset = (m_sharedData.baseState == StateID::DrivingRange); + m_restartButton.getComponent().enabled = reset; + m_restartButton.getComponent().setScale(glm::vec2(reset ? 1.f : 0.f)); + } + + if (currTime == 1) + { + state = RootCallbackData::FadeOut; + e.getComponent().active = false; + + m_scene.setSystemActive(true); + m_audioEnts[AudioID::Accept].getComponent().play(); + } + break; + case RootCallbackData::FadeOut: + currTime = std::max(0.f, currTime - (dt * 2.f)); + e.getComponent().setScale(m_viewScale * cro::Util::Easing::easeOutQuint(currTime)); + if (currTime == 0) + { + resetConfirmation(); + requestStackPop(); + + state = RootCallbackData::FadeIn; + + if (m_requestRestart) + { + auto* msg = postMessage(MessageID::SystemMessage); + msg->type = SystemEvent::RestartActiveMode; + + m_requestRestart = false; + } + } + break; + } + + }; + + m_rootNode = rootNode; + + + //quad to darken the screen + auto entity = m_scene.createEntity(); + entity.addComponent().setPosition({ 0.f, 0.f, -0.4f }); + entity.addComponent().getVertexData() = + { + cro::Vertex2D(glm::vec2(-0.5f, 0.5f), cro::Colour::Black), + cro::Vertex2D(glm::vec2(-0.5f), cro::Colour::Black), + cro::Vertex2D(glm::vec2(0.5f), cro::Colour::Black), + cro::Vertex2D(glm::vec2(0.5f, -0.5f), cro::Colour::Black) + }; + entity.getComponent().updateLocalBounds(); + entity.addComponent().active = true; + entity.getComponent().function = + [&, rootNode](cro::Entity e, float) + { + auto size = glm::vec2(GolfGame::getActiveTarget()->getSize()); + e.getComponent().setScale(size); + e.getComponent().setPosition(size / 2.f); + + auto scale = rootNode.getComponent().getScale().x; + scale = std::min(1.f, scale / m_viewScale.x); + + auto& verts = e.getComponent().getVertexData(); + for (auto& v : verts) + { + v.colour.setAlpha(BackgroundAlpha * scale); + } + }; + + + //background + cro::SpriteSheet spriteSheet; + spriteSheet.loadFromFile("assets/golf/sprites/ui.spt", m_sharedData.sharedResources->textures); + + entity = m_scene.createEntity(); + entity.addComponent().setPosition({ 0.f, 0.f, -0.2f }); + entity.addComponent(); + entity.addComponent() = spriteSheet.getSprite("message_board"); + auto bounds = entity.getComponent().getTextureBounds(); + entity.getComponent().setOrigin({ bounds.width / 2.f, bounds.height / 2.f }); + rootNode.getComponent().addChild(entity.getComponent()); + + auto menuEntity = m_scene.createEntity(); + menuEntity.addComponent(); + rootNode.getComponent().addChild(menuEntity.getComponent()); + + auto confirmEntity = m_scene.createEntity(); + confirmEntity.addComponent().setPosition(glm::vec2(-10000.f)); + rootNode.getComponent().addChild(confirmEntity.getComponent()); + + auto& font = m_sharedData.sharedResources->fonts.get(FontID::UI); + auto& uiSystem = *m_scene.getSystem(); + + auto selectedID = uiSystem.addCallback( + [](cro::Entity e) mutable + { + e.getComponent().setFillColour(TextGoldColour); + e.getComponent().play(); + e.getComponent().active = true; + }); + auto unselectedID = uiSystem.addCallback( + [](cro::Entity e) + { + e.getComponent().setFillColour(TextNormalColour); + }); + + auto createItem = [&](glm::vec2 position, const std::string& label, cro::Entity parent) + { + auto e = m_scene.createEntity(); + e.addComponent().setPosition(position); + e.addComponent() = m_menuSounds.getEmitter("switch"); + e.addComponent(); + e.addComponent(font).setCharacterSize(UITextSize); + e.getComponent().setString(label); + e.getComponent().setFillColour(TextNormalColour); + centreText(e); + e.addComponent().area = cro::Text::getLocalBounds(e); + e.getComponent().callbacks[cro::UIInput::Selected] = selectedID; + e.getComponent().callbacks[cro::UIInput::Unselected] = unselectedID; + + e.addComponent().function = MenuTextCallback(); + + parent.getComponent().addChild(e.getComponent()); + return e; + }; + + //options button + entity = createItem(glm::vec2(0.f, 16.f), "Options", menuEntity); + entity.getComponent().setFillColour(TextGoldColour); + entity.getComponent().setGroup(MenuID::Main); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&](cro::Entity e, cro::ButtonEvent evt) + { + if (activated(evt)) + { + requestStackPush(StateID::Options); + } + }); + + //return to game + entity = createItem(glm::vec2(0.f, 6.f), "Return", menuEntity); + entity.getComponent().setGroup(MenuID::Main); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&](cro::Entity e, cro::ButtonEvent evt) + { + if (activated(evt)) + { + quitState(); + } + }); + + + //restart button + entity = createItem(glm::vec2(0.f, -4.f), "Restart Round", menuEntity); + entity.getComponent().setGroup(MenuID::Main); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable + { + if (activated(evt)) + { + confirmEntity.getComponent().setPosition(glm::vec2(0.f)); + menuEntity.getComponent().setPosition(glm::vec2(-10000.f)); + + m_scene.getSystem()->setActiveGroup(MenuID::Confirm); + m_confirmationType = ConfirmType::Restart; + } + }); + entity.getComponent().enabled = (m_sharedData.baseState == StateID::DrivingRange); + entity.getComponent().setScale(glm::vec2(0.f)); + m_restartButton = entity; + + + //quit button + entity = createItem(glm::vec2(0.f, -14.f), "Quit To Menu", menuEntity); + entity.getComponent().setGroup(MenuID::Main); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable + { + if (activated(evt)) + { + confirmEntity.getComponent().setPosition(glm::vec2(0.f)); + menuEntity.getComponent().setPosition(glm::vec2(-10000.f)); + + m_scene.getSystem()->setActiveGroup(MenuID::Confirm); + m_confirmationType = ConfirmType::Quit; + } + }); + + + //confirmation buttons + entity = createItem(glm::vec2(-20.f, -12.f), "No", confirmEntity); + entity.getComponent().setGroup(MenuID::Confirm); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&,menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable + { + if (activated(evt)) + { + menuEntity.getComponent().setPosition(glm::vec2(0.f)); + confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); + + m_scene.getSystem()->setActiveGroup(MenuID::Main); + + m_audioEnts[AudioID::Back].getComponent().play(); + } + }); + + + entity = createItem(glm::vec2(20.f, -12.f), "Yes", confirmEntity); + entity.getComponent().setGroup(MenuID::Confirm); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable + { + if (activated(evt)) + { + if (m_confirmationType == ConfirmType::Quit) + { + //this is a kludge which tells the + //menu state to remove any existing connection/server instance + //rather than disconnecting here which would raise an error message + m_sharedData.tutorial = true; + + requestStackClear(); + //requestStackPush(StateID::Menu); + if (m_sharedData.baseState != StateID::Clubhouse) + { + requestStackPush(StateID::Menu); + } + else + { + requestStackPush(StateID::Clubhouse); + } + } + else + { + m_requestRestart = true; + + //this is done by quitState() + /*menuEntity.getComponent().setPosition(glm::vec2(0.f)); + confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); + + m_scene.getSystem()->setActiveGroup(MenuID::Main);*/ + + quitState(); + } + m_audioEnts[AudioID::Accept].getComponent().play(); + } + }); + + entity = m_scene.createEntity(); + entity.addComponent().setPosition(glm::vec2(0.f, 12.f)); + entity.addComponent(); + entity.addComponent(font).setCharacterSize(UITextSize); + entity.getComponent().setString("Are You Sure?"); + entity.getComponent().setFillColour(TextNormalColour); + centreText(entity); + confirmEntity.getComponent().addChild(entity.getComponent()); + + + resetConfirmation = [&, menuEntity, confirmEntity]() mutable + { + menuEntity.getComponent().setPosition(glm::vec2(0.f)); + confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); + + m_scene.getSystem()->setActiveGroup(MenuID::Main); + }; + + + + if (m_sharedData.hosting + && !m_sharedData.tutorial) + { + auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); + entity = m_scene.createEntity(); + entity.addComponent().setPosition(glm::vec2(0.f, 2.f)); + entity.addComponent(); + entity.addComponent(smallFont).setCharacterSize(InfoTextSize); + entity.getComponent().setString("This Will Kick All Players"); + entity.getComponent().setFillColour(TextNormalColour); + centreText(entity); + confirmEntity.getComponent().addChild(entity.getComponent()); + } + + + auto updateView = [&, rootNode](cro::Camera& cam) mutable + { + glm::vec2 size(GolfGame::getActiveTarget()->getSize()); + + cam.setOrthographic(0.f, size.x, 0.f, size.y, -2.f, 10.f); + cam.viewport = { 0.f, 0.f, 1.f, 1.f }; + + m_viewScale = glm::vec2(getViewScale()); + rootNode.getComponent().setScale(m_viewScale); + rootNode.getComponent().setPosition(size / 2.f); + + //updates any text objects / buttons with a relative position + cro::Command cmd; + cmd.targetFlags = CommandID::Menu::UIElement; + cmd.action = + [&, size](cro::Entity e, float) + { + const auto& element = e.getComponent(); + auto pos = element.absolutePosition; + pos += element.relativePosition * size / m_viewScale; + + pos.x = std::floor(pos.x); + pos.y = std::floor(pos.y); + + e.getComponent().setPosition(glm::vec3(pos, element.depth)); + }; + m_scene.getSystem()->sendCommand(cmd); + }; + + entity = m_scene.createEntity(); + entity.addComponent(); + entity.addComponent().resizeCallback = updateView; + m_scene.setActiveCamera(entity); + updateView(entity.getComponent()); + + m_scene.simulate(0.f); +} + +void LeaderboardState::quitState() +{ + m_scene.setSystemActive(false); + m_rootNode.getComponent().active = true; + m_audioEnts[AudioID::Back].getComponent().play(); +} \ No newline at end of file diff --git a/samples/golf/src/golf/LeaderboardState.hpp b/samples/golf/src/golf/LeaderboardState.hpp new file mode 100644 index 000000000..ee065d360 --- /dev/null +++ b/samples/golf/src/golf/LeaderboardState.hpp @@ -0,0 +1,88 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#pragma once + +#include "../StateIDs.hpp" + +#include +#include +#include + +struct SharedStateData; + +class LeaderboardState final : public cro::State +{ +public: + LeaderboardState(cro::StateStack&, cro::State::Context, SharedStateData&); + + bool handleEvent(const cro::Event&) override; + + void handleMessage(const cro::Message&) override; + + bool simulate(float) override; + + void render() override; + + cro::StateID getStateID() const override { return StateID::Leaderboard; } + +private: + + cro::Scene m_scene; + SharedStateData& m_sharedData; + + cro::AudioScape m_menuSounds; + struct AudioID final + { + enum + { + Accept, Back, + + Count + }; + }; + std::array m_audioEnts = {}; + + glm::vec2 m_viewScale; + cro::Entity m_rootNode; + cro::Entity m_restartButton; + bool m_requestRestart; + + struct ConfirmType final + { + enum + { + Restart, Quit + }; + }; + std::int32_t m_confirmationType; //used to decide which action the confirmation menu should take + + void buildScene(); + void quitState(); +}; \ No newline at end of file From 314a8a82d14052ab14a693031d11670612d73ae0 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 13 Jun 2023 15:54:26 +0100 Subject: [PATCH 040/233] add initial tables for fast CPU stats --- samples/golf/golf.vcxproj | 1 + samples/golf/golf.vcxproj.filters | 3 + samples/golf/src/golf/server/CPUStats.hpp | 111 ++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 samples/golf/src/golf/server/CPUStats.hpp diff --git a/samples/golf/golf.vcxproj b/samples/golf/golf.vcxproj index 09e87e84c..241295f51 100644 --- a/samples/golf/golf.vcxproj +++ b/samples/golf/golf.vcxproj @@ -617,6 +617,7 @@ + diff --git a/samples/golf/golf.vcxproj.filters b/samples/golf/golf.vcxproj.filters index b5825f9e4..d955b5d69 100644 --- a/samples/golf/golf.vcxproj.filters +++ b/samples/golf/golf.vcxproj.filters @@ -728,6 +728,9 @@ Header Files\golf\client\states + + Header Files\golf\server + diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp new file mode 100644 index 000000000..a8f817c50 --- /dev/null +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -0,0 +1,111 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2023 +http://trederia.blogspot.com + +Super Video Golf - zlib licence. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#pragma once + +#include + +#include + +struct CPUStat final +{ + enum + { + Skill, //0 - 2 for club range + WindAccuracy, //rand values +/- this in lookup table + PowerAccuracy, + StrokeAccuracy, + MistakeLikelyhood, //this in 10 chance of a mistake + MistakeAccuracy, //+/- rand this in lookup table + PerfectionLikelyhood, //this in 100 chance of possible perfection + + Count + }; +}; + +static constexpr std::array, 28> CPUStats = +{ + { + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0}, + {2, 0, 0, 0, 0, 0, 0}, + {2, 0, 0, 0, 0, 0, 0}, + {2, 0, 0, 0, 0, 0, 0}, + {2, 0, 0, 0, 0, 0, 0}, + } +}; + +//when applying wind multiply its effect by this +static constexpr std::array WindOffsets = +{ + -0.5f, -0.4f, -0.3f, -0.2f, -0.1f, 0.01f, 0.1f , 0.2f, 0.3f, 0.4f, 0.5f +}; + +//when calculating power offset, multiply this by club power +static constexpr std::array PowerOffsets = +{ + -3.f, -2.f, -1.f, -0.04f, 1.f, 2.f, 3.f +}; + +static constexpr std::array AccuracyOffsets = +{ + -8.f, -4.f, -2.f, -1.f, 0.f, 1.f, 2.f, 4.f, 8.f +}; + +template +float getOffset(const std::array& vals, std::int32_t accuracy) +{ + CRO_ASSERT(accuracy != 0, ""); + auto idx = (S / 2) + cro::Util::Random(-accuracy, accuracy); + + CRO_ASSERT(idx < S, ""); + return vals[idx]; +} \ No newline at end of file From df6fbfcd6da34e6ce3fdd1bbe162e335ede1eece Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 13 Jun 2023 18:51:12 +0100 Subject: [PATCH 041/233] prepartory refactoring --- .../golf/src/golf/server/ServerGolfRules.cpp | 187 +++++++++--------- .../golf/src/golf/server/ServerGolfState.hpp | 2 +- 2 files changed, 97 insertions(+), 92 deletions(-) diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 5fed257a0..01541520a 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -27,6 +27,25 @@ namespace return { v.x, 0.f, -v.y }; //return { 1.f, 0.f, 0.f }; } + + float getClub(float dist) + { + if (dist > 115.f) //forces a cut-off for pitch n putt + { + dist = 1000.f; + } + + std::int32_t clubID = ClubID::SandWedge; + while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist + && clubID != ClubID::Driver) + { + do + { + clubID--; + } while (clubID != ClubID::Driver); + } + return clubID; + } } using namespace sv; @@ -50,7 +69,79 @@ void GolfState::makeCPUMove() if (currTime < 0) { - calcCPUPosition(); + //TODO check if we're putting and use other calc if needed + auto& ball = m_playerInfo[0].ballEntity.getComponent(); + auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; + + auto pos = calcCPUPosition(); //TODO just use this for putting + + + //test terrain height and correct final position + auto result = m_scene.getSystem()->getTerrain(pos); + + //technically this means CPU players never make really bad shots + //but otherwise they just get stuck in a loop + switch (result.terrain) + { + case TerrainID::Water: + case TerrainID::Stone: + case TerrainID::Scrub: + { + std::int32_t tries = 300; + auto dir = glm::normalize(pos - m_playerInfo[0].position); + do + { + pos -= dir; + result = m_scene.getSystem()->getTerrain(pos); + } while (tries-- + && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) + && glm::length2(pos) > 1); + } + break; + default: break; + } + + pos.y = result.intersection.y; + + CRO_ASSERT(!std::isnan(pos.x), ""); + CRO_ASSERT(!std::isnan(pos.y), ""); + CRO_ASSERT(!std::isnan(pos.z), ""); + + m_playerInfo[0].ballEntity.getComponent().setPosition(pos); + m_playerInfo[0].holeScore[m_currentHole]++; + + const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; + + ball.terrain = result.terrain; + switch (result.terrain) + { + default: + ball.state = Ball::State::Paused; + break; + case TerrainID::Bunker: + case TerrainID::Rough: + animID = AnimationID::Disappoint; + [[fallthrough]]; + case TerrainID::Fairway: + ball.state = Ball::State::Flight; + ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem + break; + case TerrainID::Green: + case TerrainID::Hole: + ball.state = Ball::State::Putt; + ball.velocity = velOffset; + break; + case TerrainID::Scrub: + case TerrainID::Stone: + case TerrainID::Water: + ball.state = Ball::State::Reset; + animID = AnimationID::Disappoint; + break; + } + + m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); + + m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); e.getComponent().active = false; @@ -63,18 +154,13 @@ void GolfState::makeCPUMove() } } -void GolfState::calcCPUPosition() +glm::vec3 GolfState::calcCPUPosition() { auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; glm::vec3 pos = glm::vec3(0.f); std::int32_t skill = m_skillIndex; - //std::int32_t offset = m_playerInfo[0].player % 2; - //if (m_skillIndex > 2) - //{ - // offset *= -1; - //} std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; skill = std::clamp((skill - offset), 0, 6); @@ -89,28 +175,13 @@ void GolfState::calcCPUPosition() //std::int32_t skill = m_playerInfo[0].player * 3; auto& ball = m_playerInfo[0].ballEntity.getComponent(); - auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; - std::int32_t clubID = ClubID::Putter; //get longest range available if (ball.terrain != TerrainID::Green) { auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); - if (dist > 115.f) //forces a cut-off for pitch n putt - { - dist = 1000.f; - } - - clubID = ClubID::SandWedge; - while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist - && clubID != ClubID::Driver) - { - do - { - clubID--; - } while (clubID != ClubID::Driver); - } + clubID = getClub(dist); } const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); @@ -303,77 +374,11 @@ void GolfState::calcCPUPosition() } } - - - + pos.x = std::clamp(pos.x, 0.f, 320.f); pos.z = std::clamp(pos.z, -200.f, 0.f); - //test terrain height and correct final position - auto result = m_scene.getSystem()->getTerrain(pos); - - //technically this means CPU players never make really bad shots - //but otherwise they just get stuck in a loop - switch (result.terrain) - { - case TerrainID::Water: - case TerrainID::Stone: - case TerrainID::Scrub: - { - std::int32_t tries = 300; - auto dir = glm::normalize(pos - m_playerInfo[0].position); - do - { - pos -= dir; - result = m_scene.getSystem()->getTerrain(pos); - } while (tries-- - && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) - && glm::length2(pos) > 1); - } - break; - default: break; - } - - - pos.y = result.intersection.y; - - CRO_ASSERT(!std::isnan(pos.x), ""); - CRO_ASSERT(!std::isnan(pos.y), ""); - CRO_ASSERT(!std::isnan(pos.z), ""); - - m_playerInfo[0].ballEntity.getComponent().setPosition(pos); - m_playerInfo[0].holeScore[m_currentHole]++; - - const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; - - ball.terrain = result.terrain; - switch (result.terrain) - { - default: - ball.state = Ball::State::Paused; - break; - case TerrainID::Bunker: - case TerrainID::Rough: - animID = AnimationID::Disappoint; - [[fallthrough]]; - case TerrainID::Fairway: - ball.state = Ball::State::Flight; - ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem - break; - case TerrainID::Green: - case TerrainID::Hole: - ball.state = Ball::State::Putt; - ball.velocity = velOffset; - break; - case TerrainID::Scrub: - case TerrainID::Stone: - case TerrainID::Water: - ball.state = Ball::State::Reset; - animID = AnimationID::Disappoint; - break; - } - - m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); + return pos; } void GolfState::handleDefaultRules(const GolfBallEvent& data) diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index c58ac7bf6..30c23cc23 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -87,7 +87,7 @@ namespace sv void buildWorld(); void makeCPUMove(); - void calcCPUPosition(); + glm::vec3 calcCPUPosition(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From c6e5a61d5efa8357dbacc3f3d4867bb8820752ed Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 14 Jun 2023 09:57:05 +0100 Subject: [PATCH 042/233] add leaderboard button to lobby prevent LB/RB shortcut on lobby when not on course selection pane don't do interball collision until move is complete. --- samples/golf/src/golf/BallSystem.cpp | 10 +++++-- samples/golf/src/golf/MenuCreation.cpp | 29 +++++++++++++++++++ samples/golf/src/golf/MenuState.cpp | 6 ++-- .../golf/src/golf/server/ServerGolfRules.cpp | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/BallSystem.cpp b/samples/golf/src/golf/BallSystem.cpp index c0397b01f..3daf66064 100644 --- a/samples/golf/src/golf/BallSystem.cpp +++ b/samples/golf/src/golf/BallSystem.cpp @@ -341,7 +341,7 @@ void BallSystem::processEntity(cro::Entity entity, float dt) //test collision doCollision(entity); - doBallCollision(entity); + //doBallCollision(entity); CRO_ASSERT(!std::isnan(tx.getPosition().x), ""); CRO_ASSERT(!std::isnan(ball.velocity.x), ""); @@ -352,7 +352,7 @@ void BallSystem::processEntity(cro::Entity entity, float dt) { ball.delay -= dt; - doBallCollision(entity); + //doBallCollision(entity); auto& tx = entity.getComponent(); auto position = tx.getPosition(); @@ -462,7 +462,7 @@ void BallSystem::processEntity(cro::Entity entity, float dt) if (ball.delay < 0) { - doBallCollision(entity); + //doBallCollision(entity); auto& tx = entity.getComponent(); auto position = tx.getPosition(); @@ -784,6 +784,10 @@ void BallSystem::processEntity(cro::Entity entity, float dt) ball.delay -= dt; if (ball.delay < 0) { + //do the ball collision here to separate any balls which were overlapping when they came to rest + doBallCollision(entity); + + ball.spin = { 0.f, 0.f }; ball.initialForwardVector = { 0.f, 0.f, 0.f }; ball.initialSideVector = { 0.f, 0.f, 0.f }; diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index a2987bbde..246865ba8 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1897,6 +1897,35 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.getComponent().function = infoButtonEnable; infoBgEnt.getComponent().addChild(entity.getComponent()); + //button to show leaderboards + entity = m_uiScene.createEntity(); + entity.addComponent(); + entity.addComponent() = m_menuSounds.getEmitter("switch"); + entity.addComponent(); + entity.addComponent() = spriteSheetV2.getSprite("leaderboard_highlight"); + entity.getComponent().setColour(cro::Colour::Transparent); + entity.addComponent().absolutePosition = { 80.f, 17.f }; + entity.getComponent().depth = 0.1f; + entity.addComponent().area = entity.getComponent().getTextureBounds(); + entity.getComponent().setGroup(MenuID::Lobby); + entity.getComponent().setSelectionIndex(5); + entity.getComponent().callbacks[cro::UIInput::Selected] = m_courseSelectCallbacks.selectHighlight; + entity.getComponent().callbacks[cro::UIInput::Unselected] = m_courseSelectCallbacks.unselectHighlight; + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = m_uiScene.getSystem()->addCallback( + [&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + requestStackPush(StateID::Leaderboard); + m_audioEnts[AudioID::Accept].getComponent().play(); + } + } + ); + entity.addComponent().ID = CommandID::Menu::UIElement; + entity.addComponent().active = true; + entity.getComponent().function = infoButtonEnable; + infoBgEnt.getComponent().addChild(entity.getComponent()); + //banner entity = m_uiScene.createEntity(); entity.addComponent().setPosition({ 0.f, BannerPosition, -0.1f }); diff --git a/samples/golf/src/golf/MenuState.cpp b/samples/golf/src/golf/MenuState.cpp index 43bf3ae7b..bceb6009a 100644 --- a/samples/golf/src/golf/MenuState.cpp +++ b/samples/golf/src/golf/MenuState.cpp @@ -630,14 +630,16 @@ bool MenuState::handleEvent(const cro::Event& evt) break; case cro::GameController::ButtonRightShoulder: if (m_sharedData.hosting - && m_currentMenu == MenuID::Lobby) + && m_currentMenu == MenuID::Lobby + && m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().x != 0) { nextCourse(); } break; case cro::GameController::ButtonLeftShoulder: if (m_sharedData.hosting - && m_currentMenu == MenuID::Lobby) + && m_currentMenu == MenuID::Lobby + && m_lobbyWindowEntities[LobbyEntityID::HoleSelection].getComponent().getScale().x != 0) { prevCourse(); } diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 01541520a..b9fe6b1cd 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -28,7 +28,7 @@ namespace //return { 1.f, 0.f, 0.f }; } - float getClub(float dist) + std::int32_t getClub(float dist) { if (dist > 115.f) //forces a cut-off for pitch n putt { From a93fd13aee44ffc424e818429777481004291fd2 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 14 Jun 2023 12:03:51 +0100 Subject: [PATCH 043/233] refactor fast CPU target picking --- samples/golf/src/golf/Clubs.cpp | 31 ++-- samples/golf/src/golf/Clubs.hpp | 2 + samples/golf/src/golf/LeaderboardState.cpp | 141 +----------------- samples/golf/src/golf/server/CPUStats.hpp | 66 ++++---- .../golf/src/golf/server/ServerGolfRules.cpp | 70 ++++++++- .../golf/src/golf/server/ServerGolfState.cpp | 53 ++++++- .../golf/src/golf/server/ServerGolfState.hpp | 5 +- 7 files changed, 180 insertions(+), 188 deletions(-) diff --git a/samples/golf/src/golf/Clubs.cpp b/samples/golf/src/golf/Clubs.cpp index bc0dc2b5e..59e7b487b 100644 --- a/samples/golf/src/golf/Clubs.cpp +++ b/samples/golf/src/golf/Clubs.cpp @@ -162,30 +162,37 @@ float Club::getTarget(float distanceToPin) const } float Club::getBaseTarget() const +{ + return ClubStats[m_id].stats[getClubLevel()].target; +} + +float Club::getTargetAtLevel(std::int32_t level) const +{ + return ClubStats[m_id].stats[level].target; +} + +float Club::getDefaultTarget() const +{ + return ClubStats[m_id].stats[0].target; +} + +std::int32_t Club::getClubLevel() { //check player level and return increased distance auto level = Social::getLevel(); //auto level = DebugLevel; + if (level > 29) { - return ClubStats[m_id].stats[2].target; + return 2; } if (level > 14) { - return ClubStats[m_id].stats[1].target; + return 1; } - return ClubStats[m_id].stats[0].target; -} - -float Club::getTargetAtLevel(std::int32_t level) const -{ - return ClubStats[m_id].stats[level].target; -} -float Club::getDefaultTarget() const -{ - return ClubStats[m_id].stats[0].target; + return 0; } //private diff --git a/samples/golf/src/golf/Clubs.hpp b/samples/golf/src/golf/Clubs.hpp index 38bcf004a..b96015aff 100644 --- a/samples/golf/src/golf/Clubs.hpp +++ b/samples/golf/src/golf/Clubs.hpp @@ -93,6 +93,8 @@ class Club final float getSideSpinMultiplier() const { return m_sidespin; } float getTopSpinMultiplier() const { return m_topspin; } + static std::int32_t getClubLevel(); //0-2 for range + private: const std::int32_t m_id = -1; std::string m_name; //displayed in UI diff --git a/samples/golf/src/golf/LeaderboardState.cpp b/samples/golf/src/golf/LeaderboardState.cpp index c19766064..f4019e75f 100644 --- a/samples/golf/src/golf/LeaderboardState.cpp +++ b/samples/golf/src/golf/LeaderboardState.cpp @@ -226,12 +226,6 @@ void LeaderboardState::buildScene() currTime = std::min(1.f, currTime + (dt * 2.f)); e.getComponent().setScale(m_viewScale * cro::Util::Easing::easeOutQuint(currTime)); - { - auto reset = (m_sharedData.baseState == StateID::DrivingRange); - m_restartButton.getComponent().enabled = reset; - m_restartButton.getComponent().setScale(glm::vec2(reset ? 1.f : 0.f)); - } - if (currTime == 1) { state = RootCallbackData::FadeOut; @@ -354,7 +348,7 @@ void LeaderboardState::buildScene() }; //options button - entity = createItem(glm::vec2(0.f, 16.f), "Options", menuEntity); + entity = createItem(glm::vec2(0.f, 16.f), "Hello", menuEntity); entity.getComponent().setFillColour(TextGoldColour); entity.getComponent().setGroup(MenuID::Main); entity.getComponent().callbacks[cro::UIInput::ButtonDown] = @@ -362,7 +356,7 @@ void LeaderboardState::buildScene() { if (activated(evt)) { - requestStackPush(StateID::Options); + } }); @@ -379,137 +373,6 @@ void LeaderboardState::buildScene() }); - //restart button - entity = createItem(glm::vec2(0.f, -4.f), "Restart Round", menuEntity); - entity.getComponent().setGroup(MenuID::Main); - entity.getComponent().callbacks[cro::UIInput::ButtonDown] = - uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable - { - if (activated(evt)) - { - confirmEntity.getComponent().setPosition(glm::vec2(0.f)); - menuEntity.getComponent().setPosition(glm::vec2(-10000.f)); - - m_scene.getSystem()->setActiveGroup(MenuID::Confirm); - m_confirmationType = ConfirmType::Restart; - } - }); - entity.getComponent().enabled = (m_sharedData.baseState == StateID::DrivingRange); - entity.getComponent().setScale(glm::vec2(0.f)); - m_restartButton = entity; - - - //quit button - entity = createItem(glm::vec2(0.f, -14.f), "Quit To Menu", menuEntity); - entity.getComponent().setGroup(MenuID::Main); - entity.getComponent().callbacks[cro::UIInput::ButtonDown] = - uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable - { - if (activated(evt)) - { - confirmEntity.getComponent().setPosition(glm::vec2(0.f)); - menuEntity.getComponent().setPosition(glm::vec2(-10000.f)); - - m_scene.getSystem()->setActiveGroup(MenuID::Confirm); - m_confirmationType = ConfirmType::Quit; - } - }); - - - //confirmation buttons - entity = createItem(glm::vec2(-20.f, -12.f), "No", confirmEntity); - entity.getComponent().setGroup(MenuID::Confirm); - entity.getComponent().callbacks[cro::UIInput::ButtonDown] = - uiSystem.addCallback([&,menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable - { - if (activated(evt)) - { - menuEntity.getComponent().setPosition(glm::vec2(0.f)); - confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); - - m_scene.getSystem()->setActiveGroup(MenuID::Main); - - m_audioEnts[AudioID::Back].getComponent().play(); - } - }); - - - entity = createItem(glm::vec2(20.f, -12.f), "Yes", confirmEntity); - entity.getComponent().setGroup(MenuID::Confirm); - entity.getComponent().callbacks[cro::UIInput::ButtonDown] = - uiSystem.addCallback([&, menuEntity, confirmEntity](cro::Entity e, cro::ButtonEvent evt) mutable - { - if (activated(evt)) - { - if (m_confirmationType == ConfirmType::Quit) - { - //this is a kludge which tells the - //menu state to remove any existing connection/server instance - //rather than disconnecting here which would raise an error message - m_sharedData.tutorial = true; - - requestStackClear(); - //requestStackPush(StateID::Menu); - if (m_sharedData.baseState != StateID::Clubhouse) - { - requestStackPush(StateID::Menu); - } - else - { - requestStackPush(StateID::Clubhouse); - } - } - else - { - m_requestRestart = true; - - //this is done by quitState() - /*menuEntity.getComponent().setPosition(glm::vec2(0.f)); - confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); - - m_scene.getSystem()->setActiveGroup(MenuID::Main);*/ - - quitState(); - } - m_audioEnts[AudioID::Accept].getComponent().play(); - } - }); - - entity = m_scene.createEntity(); - entity.addComponent().setPosition(glm::vec2(0.f, 12.f)); - entity.addComponent(); - entity.addComponent(font).setCharacterSize(UITextSize); - entity.getComponent().setString("Are You Sure?"); - entity.getComponent().setFillColour(TextNormalColour); - centreText(entity); - confirmEntity.getComponent().addChild(entity.getComponent()); - - - resetConfirmation = [&, menuEntity, confirmEntity]() mutable - { - menuEntity.getComponent().setPosition(glm::vec2(0.f)); - confirmEntity.getComponent().setPosition(glm::vec2(-10000.f)); - - m_scene.getSystem()->setActiveGroup(MenuID::Main); - }; - - - - if (m_sharedData.hosting - && !m_sharedData.tutorial) - { - auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); - entity = m_scene.createEntity(); - entity.addComponent().setPosition(glm::vec2(0.f, 2.f)); - entity.addComponent(); - entity.addComponent(smallFont).setCharacterSize(InfoTextSize); - entity.getComponent().setString("This Will Kick All Players"); - entity.getComponent().setFillColour(TextNormalColour); - centreText(entity); - confirmEntity.getComponent().addChild(entity.getComponent()); - } - - auto updateView = [&, rootNode](cro::Camera& cam) mutable { glm::vec2 size(GolfGame::getActiveTarget()->getSize()); diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index a8f817c50..92cc6cd84 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -38,11 +38,11 @@ struct CPUStat final enum { Skill, //0 - 2 for club range - WindAccuracy, //rand values +/- this in lookup table - PowerAccuracy, - StrokeAccuracy, + WindAccuracy, //rand values +/- this in lookup table max 5 + PowerAccuracy, //max 3 + StrokeAccuracy, //max 4 MistakeLikelyhood, //this in 10 chance of a mistake - MistakeAccuracy, //+/- rand this in lookup table + MistakeAccuracy, //+/- rand this in lookup table max 3 (could be power, stroke or both) PerfectionLikelyhood, //this in 100 chance of possible perfection Count @@ -52,34 +52,36 @@ struct CPUStat final static constexpr std::array, 28> CPUStats = { { - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {1, 0, 0, 0, 0, 0, 0}, - {2, 0, 0, 0, 0, 0, 0}, - {2, 0, 0, 0, 0, 0, 0}, - {2, 0, 0, 0, 0, 0, 0}, - {2, 0, 0, 0, 0, 0, 0}, + {2, 1, 1, 2, 2, 3, 82}, + {2, 2, 2, 1, 3, 2, 76}, + {2, 3, 1, 2, 4, 2, 64}, + {2, 4, 3, 3, 3, 3, 70}, + + {1, 2, 3, 2, 2, 2, 71}, + {1, 1, 2, 1, 2, 2, 46}, + {1, 2, 1, 2, 2, 2, 63}, + {1, 3, 2, 2, 3, 3, 52}, + {1, 2, 2, 3, 5, 3, 50}, + {1, 3, 1, 3, 4, 3, 67}, + {1, 4, 3, 3, 5, 2, 41}, + {1, 3, 3, 4, 6, 3, 39}, + + {0, 3, 2, 1, 3, 2, 40}, + {0, 2, 1, 4, 6, 2, 26}, + {0, 2, 2, 2, 3, 2, 29}, + {0, 1, 3, 5, 2, 2, 32}, + {0, 2, 1, 2, 2, 3, 44}, + {0, 2, 1, 2, 2, 2, 27}, + {0, 3, 2, 1, 3, 2, 30}, + {0, 3, 2, 2, 3, 3, 35}, + {0, 2, 1, 3, 4, 3, 23}, + {0, 3, 2, 2, 5, 3, 20}, + {0, 5, 2, 3, 7, 3, 19}, + {0, 4, 3, 3, 6, 2, 12}, + {0, 4, 1, 4, 6, 3, 24}, + {0, 5, 3, 3, 6, 3, 16}, + {0, 4, 3, 4, 7, 3, 13}, + {0, 5, 3, 4, 8, 3, 9}, } }; diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index b9fe6b1cd..3d2c08019 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -3,6 +3,7 @@ #include "../Clubs.hpp" #include "../GameConsts.hpp" +#include "CPUStats.hpp" #include "ServerMessages.hpp" #include "ServerGolfState.hpp" @@ -69,11 +70,11 @@ void GolfState::makeCPUMove() if (currTime < 0) { - //TODO check if we're putting and use other calc if needed auto& ball = m_playerInfo[0].ballEntity.getComponent(); auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; - - auto pos = calcCPUPosition(); //TODO just use this for putting + + //this is just separated out so we can swap calcs more easily + auto pos = calcCPUPosition(); //test terrain height and correct final position @@ -155,6 +156,69 @@ void GolfState::makeCPUMove() } glm::vec3 GolfState::calcCPUPosition() +{ + const auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; + const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; + auto pos = m_holeData[m_currentHole].pin; //always prefer the pin as the target unless blocked for some reason + + const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; + CRO_ASSERT(cpuID != -1, ""); + + const std::int32_t skill = CPUStats[cpuID][CPUStat::Skill]; + + auto& ball = m_playerInfo[0].ballEntity.getComponent(); + std::int32_t clubID = ClubID::Putter; + + //get longest range available + if (ball.terrain != TerrainID::Green) + { + auto dist = glm::length(pinDir); + clubID = getClub(dist); + + //check to see if the club range can hit the ball into a valid area, + //by reducing pos to max range + const float clubDist = Clubs[clubID].getTargetAtLevel(skill); + if (auto len2 = glm::length2(pinDir); + len2 > (clubDist * clubDist)) + { + const float reduction = clubDist / std::sqrt(len2); + pos = (pinDir * reduction) + m_playerInfo[0].position; + } + + auto result = m_scene.getSystem()->getTerrain(pos); + switch (result.terrain) + { + default: break; + case TerrainID::Water: + case TerrainID::Stone: + case TerrainID::Scrub: + //else use the target point instead of the pin + pos = m_holeData[m_currentHole].target; + break; + } + + } + //else if we're on a mini-putt course see if there's a dog-leg + else if (m_scene.getSystem()->getPuttFromTee()) + { + if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); + dp > 0.4 && dp < 0.97f) //target in front, but not the same dir as pin + { + //don't use if too close + if (glm::length2(targetDir) > (3.f * 3.f)) + { + pos = m_holeData[m_currentHole].target; + } + } + } + + + + + return pos; +} + +glm::vec3 GolfState::calcCPUPositionOld() { auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 3953540df..632dcf673 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -32,6 +32,8 @@ source distribution. #include "../GameConsts.hpp" #include "../ClientPacketData.hpp" #include "../BallSystem.hpp" +#include "../Clubs.hpp" +#include "CPUStats.hpp" #include "ServerGolfState.hpp" #include "ServerMessages.hpp" @@ -74,6 +76,7 @@ GolfState::GolfState(SharedData& sd) m_currentBest (MaxStrokes), m_skillIndex (0) { + std::fill(m_cpuProfileIndices.begin(), m_cpuProfileIndices.end(), -1); if (m_mapDataValid = validateMap(); m_mapDataValid) { initScene(); @@ -983,6 +986,7 @@ void GolfState::initScene() m_mapDataValid = m_scene.addSystem(mb)->setHoleData(m_holeData[0]); m_scene.getSystem()->setGimmeRadius(m_sharedData.gimmeRadius); + std::int32_t cpuCount = 0; for (auto i = 0u; i < m_sharedData.clients.size(); ++i) { if (m_sharedData.clients[i].connected) @@ -994,11 +998,58 @@ void GolfState::initScene() player.player = j; player.position = m_holeData[0].tee; player.distanceToHole = glm::length(m_holeData[0].tee - m_holeData[0].pin); + + //do this regardless of fastCPU setting - it'll just get ignored if not used. + if (m_sharedData.clients[i].playerData[j].isCPU) + { + cpuCount++; + } } } } - std::shuffle(m_playerInfo.begin(), m_playerInfo.end(), cro::Util::Random::rndEngine); + + + //assign indices into the CPU profile array + //based on host's club set and CPU count + std::int32_t baseCPUIndex = 0; + std::int32_t stride = 1; + switch (Club::getClubLevel()) + { + default: break; + case 0: + baseCPUIndex = 12; + stride = 16 / cpuCount; //even distribution throu 16x level 0 + break; + case 1: + baseCPUIndex = 8; + stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 + break; + case 2: + stride = cpuCount < 5 ? 1 : + cpuCount < 13 ? 2 : + 27 / cpuCount; + break; + } + CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); + + for (auto i = 0u; i < m_sharedData.clients.size(); ++i) + { + if (m_sharedData.clients[i].connected) + { + for (auto j = 0u; j < m_sharedData.clients[i].playerCount; ++j) + { + if (m_sharedData.clients[i].playerData[j].isCPU) + { + CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); + + auto cpuIndex = i * ConstVal::MaxPlayers + j; + m_cpuProfileIndices[cpuIndex] = baseCPUIndex; + baseCPUIndex += stride; + } + } + } + } } void GolfState::buildWorld() diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 30c23cc23..8e43481bd 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -72,7 +72,9 @@ namespace sv std::uint8_t m_currentBest; //current best score for hole, non-stroke games end if no-one can beat it cro::Clock m_turnTimer; - std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level + std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level TODO remove this + + std::array m_cpuProfileIndices = {}; void sendInitialGameState(std::uint8_t); void handlePlayerInput(const net::NetEvent::Packet&, bool predict); @@ -88,6 +90,7 @@ namespace sv void makeCPUMove(); glm::vec3 calcCPUPosition(); + glm::vec3 calcCPUPositionOld(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From f8be00a32c2f97b3dfcf5365c44cf8baeb84c29e Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 14 Jun 2023 12:20:29 +0100 Subject: [PATCH 044/233] fix inter-ball collision when balls share exactly the same position --- samples/golf/src/golf/BallSystem.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/BallSystem.cpp b/samples/golf/src/golf/BallSystem.cpp index 3daf66064..aebfbaf6a 100644 --- a/samples/golf/src/golf/BallSystem.cpp +++ b/samples/golf/src/golf/BallSystem.cpp @@ -781,13 +781,13 @@ void BallSystem::processEntity(cro::Entity entity, float dt) break; case Ball::State::Paused: { + //do the ball collision here to separate any balls which were overlapping when they came to rest + //we do this outside the time out, else the server won't send the updated position until the next turn + doBallCollision(entity); + ball.delay -= dt; if (ball.delay < 0) { - //do the ball collision here to separate any balls which were overlapping when they came to rest - doBallCollision(entity); - - ball.spin = { 0.f, 0.f }; ball.initialForwardVector = { 0.f, 0.f, 0.f }; ball.initialSideVector = { 0.f, 0.f, 0.f }; @@ -989,13 +989,12 @@ void BallSystem::doBallCollision(cro::Entity entity) //don't collide until we moved from our start position if (ball.terrain != TerrainID::Hole && - glm::length2(ball.startPoint - tx.getPosition()) > MinDist && + /*glm::length2(ball.startPoint - tx.getPosition()) > MinDist &&*/ glm::length2(m_holeData->pin - tx.getPosition()) > MinAttractRadius) { //ball centre is actually pos.y + radius glm::vec3 ballPos = entity.getComponent().getPosition(); ballPos.y += Ball::Radius; - const auto& others = getEntities(); for (auto other : others) { @@ -1018,6 +1017,13 @@ void BallSystem::doBallCollision(cro::Entity entity) CRO_ASSERT(!std::isnan(tx.getPosition().x), ""); CRO_ASSERT(!std::isnan(ball.velocity.x), ""); } + else if (testDist == 0) + { + //by some chance we're directly on top of each other + //(this might happen in fast CPU play) + tx.move({ Ball::Radius, 0.f, 0.f }); + ball.velocity = glm::vec3(0.f); + } } } } From 225cc2115eafeae25873f0db35c687cfd8b5fc13 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 14 Jun 2023 16:00:01 +0100 Subject: [PATCH 045/233] first pass at second attempt for fast CPU --- samples/golf/src/golf/server/CPUStats.hpp | 10 +- .../golf/src/golf/server/ServerGolfRules.cpp | 184 ++++++++++++++++-- 2 files changed, 175 insertions(+), 19 deletions(-) diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index 92cc6cd84..2a6bf3ac1 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -41,7 +41,7 @@ struct CPUStat final WindAccuracy, //rand values +/- this in lookup table max 5 PowerAccuracy, //max 3 StrokeAccuracy, //max 4 - MistakeLikelyhood, //this in 10 chance of a mistake + MistakeLikelyhood, //this in 10 chance of a mistake - set to no more than 8 so we can add up to 2 for putting distance MistakeAccuracy, //+/- rand this in lookup table max 3 (could be power, stroke or both) PerfectionLikelyhood, //this in 100 chance of possible perfection @@ -88,25 +88,25 @@ static constexpr std::array, 28> CPUSta //when applying wind multiply its effect by this static constexpr std::array WindOffsets = { - -0.5f, -0.4f, -0.3f, -0.2f, -0.1f, 0.01f, 0.1f , 0.2f, 0.3f, 0.4f, 0.5f + 30.f, 22.5f, 15.f, 10.75f, 7.5f, 0.1f, 7.5f , 10.75f, 15.f, 22.5f, 30.f }; //when calculating power offset, multiply this by club power static constexpr std::array PowerOffsets = { - -3.f, -2.f, -1.f, -0.04f, 1.f, 2.f, 3.f + -10.f, -5.f, -1.f, -0.04f, 5.f, 10.f, 30.f }; static constexpr std::array AccuracyOffsets = { - -8.f, -4.f, -2.f, -1.f, 0.f, 1.f, 2.f, 4.f, 8.f + -16.f, -8.f, -2.f, -0.5f, 0.001f, 0.5f, 2.f, 8.f, 16.f }; template float getOffset(const std::array& vals, std::int32_t accuracy) { CRO_ASSERT(accuracy != 0, ""); - auto idx = (S / 2) + cro::Util::Random(-accuracy, accuracy); + auto idx = (static_cast(S) / 2) + cro::Util::Random::value(-accuracy, accuracy); CRO_ASSERT(idx < S, ""); return vals[idx]; diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 3d2c08019..3f8330fae 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -40,10 +40,7 @@ namespace while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist && clubID != ClubID::Driver) { - do - { - clubID--; - } while (clubID != ClubID::Driver); + clubID--; } return clubID; } @@ -111,8 +108,17 @@ void GolfState::makeCPUMove() m_playerInfo[0].ballEntity.getComponent().setPosition(pos); m_playerInfo[0].holeScore[m_currentHole]++; - const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; + //TODO this case should never happen... + auto velOffset = pos - m_playerInfo[0].position; + if (glm::length2(velOffset) == 0) + { + velOffset.x = 0.0001f; + } + velOffset = glm::normalize(velOffset) * 0.001f; + //const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; + + //LogI << velOffset << std::endl; ball.terrain = result.terrain; switch (result.terrain) { @@ -172,8 +178,15 @@ glm::vec3 GolfState::calcCPUPosition() //get longest range available if (ball.terrain != TerrainID::Green) { - auto dist = glm::length(pinDir); - clubID = getClub(dist); + if (ball.terrain == TerrainID::Bunker) + { + clubID = ClubID::PitchWedge; + } + else + { + auto dist = glm::length(pinDir); + clubID = getClub(dist); + } //check to see if the club range can hit the ball into a valid area, //by reducing pos to max range @@ -198,16 +211,27 @@ glm::vec3 GolfState::calcCPUPosition() } } - //else if we're on a mini-putt course see if there's a dog-leg - else if (m_scene.getSystem()->getPuttFromTee()) + else { - if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); - dp > 0.4 && dp < 0.97f) //target in front, but not the same dir as pin + //else if we're on a mini-putt course see if there's a dog-leg + if (m_scene.getSystem()->getPuttFromTee()) + { + if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); + dp > 0.4 && dp < 0.97f) //target in front, but not the same dir as pin + { + //don't use if too close + if (glm::length2(targetDir) > (3.f * 3.f)) + { + pos = m_holeData[m_currentHole].target; + } + } + } + else { - //don't use if too close - if (glm::length2(targetDir) > (3.f * 3.f)) + //regular putting - assume we go in the hole under 20cm + if (glm::length2(pinDir) < (0.2f * 0.2f)) { - pos = m_holeData[m_currentHole].target; + return m_holeData[m_currentHole].pin; } } } @@ -215,6 +239,138 @@ glm::vec3 GolfState::calcCPUPosition() + + auto stepDir = pos - m_playerInfo[0].position; + const auto totalDist = glm::length(stepDir); + std::int32_t stepCount = std::max(1, static_cast(totalDist)); + + //use smaller steps when putting + if (ball.terrain == TerrainID::Green) + { + stepCount *= 2; + } + stepDir /= stepCount; + + //using the CPU stats calculate some sort of offset from the target. + float windEffect = 0.f; + if (clubID < ClubID::SandWedge) + { + windEffect = getOffset(WindOffsets, CPUStats[cpuID][CPUStat::WindAccuracy]); + windEffect *= Clubs[clubID].getBaseTarget() / Clubs[ClubID::Driver].getBaseTarget(); + + LogI << "Wind effect " << windEffect << std::endl; + } + auto wind = m_scene.getSystem()->getWindDirection(); + wind = (glm::vec3(wind.x, 0.f, wind.z) * wind.y * windEffect); + wind /= stepCount; + + //TODO this should use CPU skill level not the default player level + const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTarget(totalDist)); + auto dirNorm = glm::normalize(stepDir); + + const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); + auto overShootDir = dirNorm * overShoot * clubMultiplier; + overShootDir /= stepCount; + + dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular + const float strokeAccuracy = getOffset(AccuracyOffsets, CPUStats[cpuID][CPUStat::StrokeAccuracy]); + auto accuracyDir = dirNorm * strokeAccuracy * clubMultiplier; + accuracyDir /= stepCount; + + //TODO include offset for rough or bunker terrain - this should probably be another stat + //for how well CPU compensates + + + //calculate mistake odds - increase this with distance when putting + bool perfect = false; + std::int32_t puttingOdds = 0; + if (ball.terrain == TerrainID::Green) + { + float odds = std::min(1.f, totalDist / 12.f) * 2.f; + puttingOdds = static_cast(std::round(odds)); + } + + if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) + { + //TODO check these values so we don't accidentally + //undo existing power/accuracy and improve them... + LogI << "Made a mistake!" << std::endl; + switch (cro::Util::Random::value(0, 2)) + { + default: + case 0: + LogI << "Fluffed power" << std::endl; + overShootDir *= 1.001f; + break; + case 1: + LogI << "Fluffed accuracy" << std::endl; + accuracyDir *= 1.001f; + break; + case 2: + LogI << "Fluffed power and accuracy" << std::endl; + accuracyDir *= 1.002f; + overShootDir *= 0.9999f; + break; + } + } + + //calculate perfection odds only if we didn't make a mistake + else if (cro::Util::Random::value(0, 99) < CPUStats[cpuID][CPUStat::PerfectionLikelyhood]) + { + if (totalDist < 20.f) + { + perfect = cro::Util::Random::value(1, 10) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 10; + } + else if (totalDist < 100.f) + { + perfect = cro::Util::Random::value(1, 50) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 2; + } + else + { + perfect = cro::Util::Random::value(1, 100) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood]; + } + + + + + if(perfect && + totalDist < Clubs[clubID].getBaseTarget()) + { + LogI << "Got a PERFECT shot" << std::endl; + pos = m_holeData[m_currentHole].pin; + } + else + { + perfect = false; //need to do the calc below if we're not in range + } + } + + CRO_ASSERT(glm::length2(stepDir), ""); + //CRO_ASSERT(glm::length2(wind), ""); + CRO_ASSERT(glm::length2(overShootDir), ""); + CRO_ASSERT(glm::length2(accuracyDir), ""); + CRO_ASSERT(stepCount != 0, ""); + + //if not perfect + if (!perfect) + { + pos = m_playerInfo[0].position; + for (auto i = 0; i < stepCount; ++i) + { + pos += stepDir; + pos += wind; + pos += overShootDir; + pos += accuracyDir; + } + + //if we're really close to the hole plop it in based on stroke accuracy + if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && + cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 20) + { + pos = m_holeData[m_currentHole].pin; + } + } + return pos; } From 59fd72ebc612b5aac17f731a69a0db38fdd79c5e Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 14 Jun 2023 21:21:41 +0100 Subject: [PATCH 046/233] tweak fast CPU rules tweak golfer animation transition --- samples/golf/src/golf/ClubhouseState.cpp | 4 ++++ samples/golf/src/golf/ClubhouseState.hpp | 1 + samples/golf/src/golf/GolfState.cpp | 15 ++++++++++++--- samples/golf/src/golf/LeaderboardState.cpp | 9 --------- samples/golf/src/golf/MenuCreation.cpp | 2 +- samples/golf/src/golf/server/ServerGolfRules.cpp | 5 +++-- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index c60d222af..f5c3749e4 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -366,6 +366,8 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) { m_arcadeEnt.getComponent().setScale(glm::vec2(1.f)); m_arcadeEnt.getComponent().enabled = true; + + m_audioEnts[AudioID::Win].getComponent().play(); } } } @@ -766,6 +768,8 @@ void ClubhouseState::loadResources() m_audioEnts[AudioID::Accept].addComponent() = m_menuSounds.getEmitter("accept"); m_audioEnts[AudioID::Back] = m_uiScene.createEntity(); m_audioEnts[AudioID::Back].addComponent() = m_menuSounds.getEmitter("back"); + m_audioEnts[AudioID::Win] = m_uiScene.createEntity(); + m_audioEnts[AudioID::Win].addComponent() = m_menuSounds.getEmitter("star"); validateTables(); diff --git a/samples/golf/src/golf/ClubhouseState.hpp b/samples/golf/src/golf/ClubhouseState.hpp index 5ce8cf0de..b17401528 100644 --- a/samples/golf/src/golf/ClubhouseState.hpp +++ b/samples/golf/src/golf/ClubhouseState.hpp @@ -132,6 +132,7 @@ class ClubhouseState final : public cro::State, public cro::GuiClient enum { Accept, Back, + Win, Count }; diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 121386af4..f5b237d91 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -5533,6 +5533,16 @@ void GolfState::handleNetEvent(const net::NetEvent& evt) if (m_activeAvatar) { auto animID = evt.packet.as(); + if (animID == AnimationID::Celebrate) + { + m_clubModels[ClubModel::Wood].getComponent().setScale(glm::vec3(0.f)); + m_clubModels[ClubModel::Iron].getComponent().setScale(glm::vec3(0.f)); + } + else + { + m_clubModels[ClubModel::Wood].getComponent().setScale(glm::vec3(1.f)); + m_clubModels[ClubModel::Iron].getComponent().setScale(glm::vec3(1.f)); + } /*if (animID == AnimationID::Swing) { @@ -5542,14 +5552,13 @@ void GolfState::handleNetEvent(const net::NetEvent& evt) } }*/ - /*bool isCPU = m_sharedData.connectionData[m_currentPlayer.client].playerData[m_currentPlayer.player].isCPU - && m_sharedData.fastCPU;*/ + //TODO scale club model to zero if not idle or swing auto anim = m_activeAvatar->animationIDs[animID]; auto& skel = m_activeAvatar->model.getComponent(); if (skel.getState() == cro::Skeleton::Stopped - || skel.getActiveAnimations().second != anim) + || (skel.getActiveAnimations().first != anim && skel.getActiveAnimations().second != anim)) { skel.play(anim, /*isCPU ? 2.f :*/ 1.f, 0.4f); } diff --git a/samples/golf/src/golf/LeaderboardState.cpp b/samples/golf/src/golf/LeaderboardState.cpp index f4019e75f..4203c7092 100644 --- a/samples/golf/src/golf/LeaderboardState.cpp +++ b/samples/golf/src/golf/LeaderboardState.cpp @@ -240,18 +240,9 @@ void LeaderboardState::buildScene() e.getComponent().setScale(m_viewScale * cro::Util::Easing::easeOutQuint(currTime)); if (currTime == 0) { - resetConfirmation(); requestStackPop(); state = RootCallbackData::FadeIn; - - if (m_requestRestart) - { - auto* msg = postMessage(MessageID::SystemMessage); - msg->type = SystemEvent::RestartActiveMode; - - m_requestRestart = false; - } } break; } diff --git a/samples/golf/src/golf/MenuCreation.cpp b/samples/golf/src/golf/MenuCreation.cpp index 246865ba8..4b2136859 100644 --- a/samples/golf/src/golf/MenuCreation.cpp +++ b/samples/golf/src/golf/MenuCreation.cpp @@ -1904,7 +1904,7 @@ void MenuState::createLobbyMenu(cro::Entity parent, std::uint32_t mouseEnter, st entity.addComponent(); entity.addComponent() = spriteSheetV2.getSprite("leaderboard_highlight"); entity.getComponent().setColour(cro::Colour::Transparent); - entity.addComponent().absolutePosition = { 80.f, 17.f }; + entity.addComponent().absolutePosition = { 79.f, 17.f }; entity.getComponent().depth = 0.1f; entity.addComponent().area = entity.getComponent().getTextureBounds(); entity.getComponent().setGroup(MenuID::Lobby); diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 3f8330fae..88b476997 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -220,7 +220,8 @@ glm::vec3 GolfState::calcCPUPosition() dp > 0.4 && dp < 0.97f) //target in front, but not the same dir as pin { //don't use if too close - if (glm::length2(targetDir) > (3.f * 3.f)) + if (glm::length2(targetDir) > (3.f * 3.f) + && glm::length2(pinDir) > glm::length2(targetDir)) { pos = m_holeData[m_currentHole].target; } @@ -365,7 +366,7 @@ glm::vec3 GolfState::calcCPUPosition() //if we're really close to the hole plop it in based on stroke accuracy if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && - cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 20) + cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 10) { pos = m_holeData[m_currentHole].pin; } From 63eafd3b4f2fabd45fb8a18bec81c3d987cdab5b Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 15 Jun 2023 10:02:54 +0100 Subject: [PATCH 047/233] tidy up --- samples/golf/src/golf/MenuAvatars.cpp | 2 +- .../golf/src/golf/server/ServerGolfRules.cpp | 452 +++++++++--------- .../golf/src/golf/server/ServerGolfState.cpp | 13 +- .../golf/src/golf/server/ServerGolfState.hpp | 4 +- 4 files changed, 237 insertions(+), 234 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index f3bdff0fb..b25bbdc98 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -1845,7 +1845,7 @@ void MenuState::updateLobbyAvatars() //rank text entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 100.f, (RowSpacing * clientCount) + 198.f, 0.3f }); + entity.addComponent().setPosition({ 100.f, (RowSpacing * clientCount) + 180.f, 0.3f }); entity.addComponent(); entity.addComponent(smallFont).setString("Level"); entity.getComponent().setCharacterSize(InfoTextSize); diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 88b476997..cafb21d90 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -375,232 +375,232 @@ glm::vec3 GolfState::calcCPUPosition() return pos; } -glm::vec3 GolfState::calcCPUPositionOld() -{ - auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; - auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; - - glm::vec3 pos = glm::vec3(0.f); - std::int32_t skill = m_skillIndex; - - std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; - skill = std::clamp((skill - offset), 0, 6); - - //randomly invert skill - if (cro::Util::Random::value(0, 49) == 25) - { - skill = (skill + 3) % 6; - } - - - //std::int32_t skill = m_playerInfo[0].player * 3; - - auto& ball = m_playerInfo[0].ballEntity.getComponent(); - std::int32_t clubID = ClubID::Putter; - - //get longest range available - if (ball.terrain != TerrainID::Green) - { - auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); - clubID = getClub(dist); - } - const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); - - const auto pinDist = glm::length2(pinDir); - const auto targetDist = glm::length2(targetDir); - const float MinDist = m_scene.getSystem()->getPuttFromTee() ? 9.f : 2500.f; - - const auto pickTarget = [&](float dp) - { - if (m_scene.getSystem()->getPuttFromTee()) - { - if (dp > 0.97f) - { - return m_holeData[m_currentHole].pin; - } - else - { - if (targetDist < MinDist - || pinDist < targetDist) - { - return m_holeData[m_currentHole].pin; - } - - return m_holeData[m_currentHole].target; - } - } - else - { - return m_holeData[m_currentHole].pin; - } - }; - - //if we're less than 20cm from the hole we'll assume - //it's pretty much a dead cert going in - if (pinDist > (0.2f * 0.2f)) - { - //if both the pin and the target are in front of the player - if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) - { - //set the target depending on how close it is - if (pinDist < targetDist) - { - //always target pin if its closer - pos = pickTarget(dp); - } - - //target the pin if its in range of our longest club - //and CPU skill > something - else if (skill > 2) - { - pos = pickTarget(dp); - } - - else - { - //target the pin if the target is too close - if (targetDist < MinDist) //remember this is len2 - { - pos = m_holeData[m_currentHole].pin; - } - else - { - pos = m_holeData[m_currentHole].target; - } - } - } - else - { - //else set the pin as the target - pos = pickTarget(dp); - } - - //reduce the target distance so that it's in range of our longest club - if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > - (clubDist * clubDist)) - { - const float reduction = clubDist / std::sqrt(len2); - pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; - } - - //make sure there's only a slim chance of getting it in the hole - //if club is not a putter, and VERY slim chance if not a wedge - if (ball.terrain == TerrainID::Green) - { - if (cro::Util::Random::value(0, 3 + (skill / 2)) == (m_playerInfo[0].player % 4)) - { - //add target offset - pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); - CRO_ASSERT(!std::isnan(pos.x), ""); - } - - //add offset based on length of putt - const auto len2 = glm::length2(pos - m_playerInfo[0].position); - const auto length = std::min(1.f, len2 / (10.f * 10.f)); - auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); - accuracy /= 4.f; - - //lower skill has more chance of going off - if (cro::Util::Random::value(0, 3 + skill) == 0) - { - accuracy *= 1.6f + (3 - (std::min(skill, 3))); - } - - //higher skill has more chance of being accurate - if (cro::Util::Random::value(0, 3 + skill) > 3) - { - accuracy /= 2.f; - } - - pos += randomNormal() * accuracy; - //LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; - } - else - { - //we're probably chipping if < 90m - if (auto l2 = glm::length2(pos - m_playerInfo[0].position); l2 < (90.f * 90.f)) - { - //if len > 20 almost always add offset so we don't hole the ball - if (l2 > (20.f * 20.f)) - { - if (cro::Util::Random::value(0, 100 - (skill * 10)) != m_playerInfo[0].player) - { - pos += randomNormal() * static_cast(cro::Util::Random::value(3, 10)) / 10.f; - } - } - - if (cro::Util::Random::value(0, 4 + skill) < 9) - { - //add offset based on how close we are to the pin/target - pos += randomNormal() * (static_cast(cro::Util::Random::value(6, 70)) / 10.f) * std::min(0.2f, std::max(1.f, std::sqrt(l2) / 100.f)); - CRO_ASSERT(!std::isnan(pos.x), ""); - } - else - { - //even if we have the skill reduce the chance of it going in - pos += randomNormal() * static_cast(cro::Util::Random::value(1, 30)) / 10.f; - } - - //more likely to overshoot with distance and lower skill - if (cro::Util::Random::value(0, 4 + skill) < 4) - { - auto dir = pos - m_playerInfo[0].position; - dir *= 1.f + (0.065f * std::min(1.f, std::sqrt(l2) / 80.f)); - pos = m_playerInfo[0].position + dir; - } - } - else - { - //iron or driver so add some arbitrary offset - //based on bounce and wind strength/dir and distance to pos - - //TODO we need a good bounce as a percentage value... - constexpr float BouncePercent = 1.05f; - pos = ((pos - m_playerInfo[0].position) * (BouncePercent - (static_cast((m_playerInfo[0].player % 3) * 2) / 100.f))); - - auto step = pos / 5.f; - pos = m_playerInfo[0].position + step; - - for (auto i = 0; i < 4; ++i) - { - const auto wind = m_scene.getSystem()->getWindDirection(); - step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.15f * (7 - skill)); - pos += step; - } - - CRO_ASSERT(!std::isnan(pos.x), ""); - } - - //reduce the distance if we're in the rough based on skill - if (ball.terrain == TerrainID::Rough - || ball.terrain == TerrainID::Bunker) - { - auto dir = pos - m_playerInfo[0].position; - - float pc = 1.f - (static_cast(std::max(0, 6 - skill)) / 100.f); - dir *= pc; - - pos = m_playerInfo[0].position + dir; - } - } - } - else - { - pos = m_holeData[m_currentHole].pin; - - //if we're really daft maybe we'll hit it short - if (cro::Util::Random::value(0, 2 + (skill * 2)) == 0) - { - pos = m_playerInfo[0].position + (pinDir / 2.f); - } - } - - - pos.x = std::clamp(pos.x, 0.f, 320.f); - pos.z = std::clamp(pos.z, -200.f, 0.f); - - return pos; -} +//glm::vec3 GolfState::calcCPUPositionOld() +//{ +// auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; +// auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; +// +// glm::vec3 pos = glm::vec3(0.f); +// std::int32_t skill = m_skillIndex; +// +// std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; +// skill = std::clamp((skill - offset), 0, 6); +// +// //randomly invert skill +// if (cro::Util::Random::value(0, 49) == 25) +// { +// skill = (skill + 3) % 6; +// } +// +// +// //std::int32_t skill = m_playerInfo[0].player * 3; +// +// auto& ball = m_playerInfo[0].ballEntity.getComponent(); +// std::int32_t clubID = ClubID::Putter; +// +// //get longest range available +// if (ball.terrain != TerrainID::Green) +// { +// auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); +// clubID = getClub(dist); +// } +// const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); +// +// const auto pinDist = glm::length2(pinDir); +// const auto targetDist = glm::length2(targetDir); +// const float MinDist = m_scene.getSystem()->getPuttFromTee() ? 9.f : 2500.f; +// +// const auto pickTarget = [&](float dp) +// { +// if (m_scene.getSystem()->getPuttFromTee()) +// { +// if (dp > 0.97f) +// { +// return m_holeData[m_currentHole].pin; +// } +// else +// { +// if (targetDist < MinDist +// || pinDist < targetDist) +// { +// return m_holeData[m_currentHole].pin; +// } +// +// return m_holeData[m_currentHole].target; +// } +// } +// else +// { +// return m_holeData[m_currentHole].pin; +// } +// }; +// +// //if we're less than 20cm from the hole we'll assume +// //it's pretty much a dead cert going in +// if (pinDist > (0.2f * 0.2f)) +// { +// //if both the pin and the target are in front of the player +// if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) +// { +// //set the target depending on how close it is +// if (pinDist < targetDist) +// { +// //always target pin if its closer +// pos = pickTarget(dp); +// } +// +// //target the pin if its in range of our longest club +// //and CPU skill > something +// else if (skill > 2) +// { +// pos = pickTarget(dp); +// } +// +// else +// { +// //target the pin if the target is too close +// if (targetDist < MinDist) //remember this is len2 +// { +// pos = m_holeData[m_currentHole].pin; +// } +// else +// { +// pos = m_holeData[m_currentHole].target; +// } +// } +// } +// else +// { +// //else set the pin as the target +// pos = pickTarget(dp); +// } +// +// //reduce the target distance so that it's in range of our longest club +// if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > +// (clubDist * clubDist)) +// { +// const float reduction = clubDist / std::sqrt(len2); +// pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; +// } +// +// //make sure there's only a slim chance of getting it in the hole +// //if club is not a putter, and VERY slim chance if not a wedge +// if (ball.terrain == TerrainID::Green) +// { +// if (cro::Util::Random::value(0, 3 + (skill / 2)) == (m_playerInfo[0].player % 4)) +// { +// //add target offset +// pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); +// CRO_ASSERT(!std::isnan(pos.x), ""); +// } +// +// //add offset based on length of putt +// const auto len2 = glm::length2(pos - m_playerInfo[0].position); +// const auto length = std::min(1.f, len2 / (10.f * 10.f)); +// auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); +// accuracy /= 4.f; +// +// //lower skill has more chance of going off +// if (cro::Util::Random::value(0, 3 + skill) == 0) +// { +// accuracy *= 1.6f + (3 - (std::min(skill, 3))); +// } +// +// //higher skill has more chance of being accurate +// if (cro::Util::Random::value(0, 3 + skill) > 3) +// { +// accuracy /= 2.f; +// } +// +// pos += randomNormal() * accuracy; +// //LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; +// } +// else +// { +// //we're probably chipping if < 90m +// if (auto l2 = glm::length2(pos - m_playerInfo[0].position); l2 < (90.f * 90.f)) +// { +// //if len > 20 almost always add offset so we don't hole the ball +// if (l2 > (20.f * 20.f)) +// { +// if (cro::Util::Random::value(0, 100 - (skill * 10)) != m_playerInfo[0].player) +// { +// pos += randomNormal() * static_cast(cro::Util::Random::value(3, 10)) / 10.f; +// } +// } +// +// if (cro::Util::Random::value(0, 4 + skill) < 9) +// { +// //add offset based on how close we are to the pin/target +// pos += randomNormal() * (static_cast(cro::Util::Random::value(6, 70)) / 10.f) * std::min(0.2f, std::max(1.f, std::sqrt(l2) / 100.f)); +// CRO_ASSERT(!std::isnan(pos.x), ""); +// } +// else +// { +// //even if we have the skill reduce the chance of it going in +// pos += randomNormal() * static_cast(cro::Util::Random::value(1, 30)) / 10.f; +// } +// +// //more likely to overshoot with distance and lower skill +// if (cro::Util::Random::value(0, 4 + skill) < 4) +// { +// auto dir = pos - m_playerInfo[0].position; +// dir *= 1.f + (0.065f * std::min(1.f, std::sqrt(l2) / 80.f)); +// pos = m_playerInfo[0].position + dir; +// } +// } +// else +// { +// //iron or driver so add some arbitrary offset +// //based on bounce and wind strength/dir and distance to pos +// +// //TODO we need a good bounce as a percentage value... +// constexpr float BouncePercent = 1.05f; +// pos = ((pos - m_playerInfo[0].position) * (BouncePercent - (static_cast((m_playerInfo[0].player % 3) * 2) / 100.f))); +// +// auto step = pos / 5.f; +// pos = m_playerInfo[0].position + step; +// +// for (auto i = 0; i < 4; ++i) +// { +// const auto wind = m_scene.getSystem()->getWindDirection(); +// step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.15f * (7 - skill)); +// pos += step; +// } +// +// CRO_ASSERT(!std::isnan(pos.x), ""); +// } +// +// //reduce the distance if we're in the rough based on skill +// if (ball.terrain == TerrainID::Rough +// || ball.terrain == TerrainID::Bunker) +// { +// auto dir = pos - m_playerInfo[0].position; +// +// float pc = 1.f - (static_cast(std::max(0, 6 - skill)) / 100.f); +// dir *= pc; +// +// pos = m_playerInfo[0].position + dir; +// } +// } +// } +// else +// { +// pos = m_holeData[m_currentHole].pin; +// +// //if we're really daft maybe we'll hit it short +// if (cro::Util::Random::value(0, 2 + (skill * 2)) == 0) +// { +// pos = m_playerInfo[0].position + (pinDir / 2.f); +// } +// } +// +// +// pos.x = std::clamp(pos.x, 0.f, 320.f); +// pos.z = std::clamp(pos.z, -200.f, 0.f); +// +// return pos; +//} void GolfState::handleDefaultRules(const GolfBallEvent& data) { diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 632dcf673..97c4eab49 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -73,8 +73,8 @@ GolfState::GolfState(SharedData& sd) m_allMapsLoaded (false), m_currentHole (0), m_skinsPot (1), - m_currentBest (MaxStrokes), - m_skillIndex (0) + m_currentBest (MaxStrokes)/*, + m_skillIndex (0)*/ { std::fill(m_cpuProfileIndices.begin(), m_cpuProfileIndices.end(), -1); if (m_mapDataValid = validateMap(); m_mapDataValid) @@ -82,8 +82,8 @@ GolfState::GolfState(SharedData& sd) initScene(); buildWorld(); - auto level = std::min(Social::getLevel(), 24) + 2; - m_skillIndex = level / 4; + /*auto level = std::min(Social::getLevel(), 24) + 2; + m_skillIndex = level / 4;*/ } LOG("Entered Server Golf State", cro::Logger::Type::Info); @@ -1014,12 +1014,13 @@ void GolfState::initScene() //based on host's club set and CPU count std::int32_t baseCPUIndex = 0; std::int32_t stride = 1; + switch (Club::getClubLevel()) { default: break; case 0: baseCPUIndex = 12; - stride = 16 / cpuCount; //even distribution throu 16x level 0 + stride = 16 / cpuCount; //even distribution through 16x level 0 break; case 1: baseCPUIndex = 8; @@ -1031,6 +1032,8 @@ void GolfState::initScene() 27 / cpuCount; break; } + + CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); for (auto i = 0u; i < m_sharedData.clients.size(); ++i) diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 8e43481bd..73687a8d7 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -72,7 +72,7 @@ namespace sv std::uint8_t m_currentBest; //current best score for hole, non-stroke games end if no-one can beat it cro::Clock m_turnTimer; - std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level TODO remove this + //std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level TODO remove this std::array m_cpuProfileIndices = {}; @@ -90,7 +90,7 @@ namespace sv void makeCPUMove(); glm::vec3 calcCPUPosition(); - glm::vec3 calcCPUPositionOld(); + //glm::vec3 calcCPUPositionOld(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From 2f10a93be4ba993ddaa166558126cec31e10844e Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 15 Jun 2023 11:58:21 +0100 Subject: [PATCH 048/233] make full-swing CPU more aggressively chase the pin as a target use CPU stats to affect full-swing CPU behaviour --- samples/golf/src/golf/CPUGolfer.cpp | 78 +++++++++++++++---- samples/golf/src/golf/CPUGolfer.hpp | 2 + samples/golf/src/golf/server/CPUStats.hpp | 10 +-- .../golf/src/golf/server/ServerGolfRules.cpp | 15 ++-- .../golf/src/golf/server/ServerGolfState.hpp | 2 +- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 7b2cec662..1a322cdcc 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -33,6 +33,7 @@ source distribution. #include "Clubs.hpp" #include "CollisionMesh.hpp" #include "server/ServerPacketData.hpp" +#include "server/CPUStats.hpp" #include @@ -205,6 +206,9 @@ void CPUGolfer::handleMessage(const cro::Message& msg) void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) { + target += getRandomOffset(target - m_activePlayer.position); + fallback += getRandomOffset(fallback - m_activePlayer.position); + if (!m_fastCPU && m_state == State::Inactive) { @@ -212,7 +216,8 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) m_fallbackTarget = fallback; m_baseTarget = m_target = target; - //TODO also use fallback if target is out of max range of driver + //previous fail is set to the last ID of the player which went OOB + //so if it matches our current then the last shot was bad if (previousFail == (m_activePlayer.client * 100) + m_activePlayer.player) { auto fwd = m_baseTarget - m_activePlayer.position; @@ -222,9 +227,28 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) m_baseTarget = m_target = fallback; } } - else if (glm::length(target - m_activePlayer.position) > Clubs[ClubID::Driver].getTarget(0.f)) + //otherwise check if the target is in max range - if not shorten + //the target (so we prefer the pin still, just fall short) unless + //the shortened target is out of bounds, in which case use the fallback + else if (auto len = glm::length(target - m_activePlayer.position); + len > Clubs[ClubID::Driver].getTarget(0.f)) { - m_baseTarget = m_target = fallback; + const float reduction = (Clubs[ClubID::Driver].getTarget(0.f) / len) * 0.95f; + auto newDir = (target - m_activePlayer.position) * reduction; + newDir += m_activePlayer.position; + + const auto terrain = m_collisionMesh.getTerrain(newDir); + switch (terrain.terrain) + { + default: + m_baseTarget = m_target = newDir; + break; + case TerrainID::Water: + case TerrainID::Scrub: + case TerrainID::Stone: + m_baseTarget = m_target = fallback; + break; + } } previousFail = -1; @@ -389,17 +413,8 @@ void CPUGolfer::setPuttingPower(float power) std::size_t CPUGolfer::getSkillIndex() const { - //std::int32_t offset = m_activePlayer.player % 2; - //if (m_skillIndex > 2) - //{ - // offset *= -1; - //} - std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; - return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); - - //return std::min(static_cast(m_skills.size() - 1), static_cast(m_skillIndex) + offset); } //private @@ -1172,4 +1187,41 @@ void CPUGolfer::sendKeystroke(std::int32_t key, bool autoRelease) evt.key.state = SDL_RELEASED; m_popEvents.push_back(evt); } -}; \ No newline at end of file +}; + +glm::vec3 CPUGolfer::getRandomOffset(glm::vec3 baseDir) const +{ + std::int32_t indexOffset = 0; + switch (Club::getClubLevel()) + { + default: break; + case 0: + indexOffset = 12; + break; + case 1: + indexOffset = 4; + break; + case 2: + + break; + } + + indexOffset += ((m_activePlayer.player / 4) + 1) * m_activePlayer.player; + CRO_ASSERT(indexOffset < CPUStats.size(), ""); + + const auto baseLength = glm::length(baseDir); + auto normDir = baseDir / baseLength; + + const auto& cpuStat = CPUStats[indexOffset]; + const float maxDist = Clubs[ClubID::Driver].getTargetAtLevel(cpuStat[CPUStat::Skill]); + const float resultMultiplier = std::max(1.f, baseLength / maxDist); + + const float powerOffset = getOffset(PowerOffsets, cpuStat[CPUStat::PowerAccuracy]); + auto retVal = normDir * powerOffset * (resultMultiplier * resultMultiplier); + + normDir = { -normDir.z, normDir.y, normDir.x }; + const float accuracyOffset = getOffset(AccuracyOffsets, cpuStat[CPUStat::StrokeAccuracy]); + retVal += normDir * accuracyOffset * resultMultiplier; + + return retVal * (static_cast(indexOffset) / CPUStats.size()) * 0.05f; +} \ No newline at end of file diff --git a/samples/golf/src/golf/CPUGolfer.hpp b/samples/golf/src/golf/CPUGolfer.hpp index 1f5f887bf..ebb52e206 100644 --- a/samples/golf/src/golf/CPUGolfer.hpp +++ b/samples/golf/src/golf/CPUGolfer.hpp @@ -155,4 +155,6 @@ class CPUGolfer final : public cro::GuiClient //for each pressed event we need a release event the next frame std::vector m_popEvents; void sendKeystroke(std::int32_t, bool autoRelease = true); + + glm::vec3 getRandomOffset(glm::vec3) const; }; diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index 2a6bf3ac1..d003a06e2 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -53,9 +53,9 @@ static constexpr std::array, 28> CPUSta { { {2, 1, 1, 2, 2, 3, 82}, - {2, 2, 2, 1, 3, 2, 76}, - {2, 3, 1, 2, 4, 2, 64}, - {2, 4, 3, 3, 3, 3, 70}, + {2, 2, 2, 1, 1, 2, 76}, + {2, 1, 1, 2, 2, 2, 64}, + {2, 2, 2, 1, 3, 1, 70}, {1, 2, 3, 2, 2, 2, 71}, {1, 1, 2, 1, 2, 2, 46}, @@ -66,8 +66,8 @@ static constexpr std::array, 28> CPUSta {1, 4, 3, 3, 5, 2, 41}, {1, 3, 3, 4, 6, 3, 39}, - {0, 3, 2, 1, 3, 2, 40}, - {0, 2, 1, 4, 6, 2, 26}, + {0, 3, 2, 3, 3, 2, 40}, + {0, 2, 1, 4, 6, 3, 26}, {0, 2, 2, 2, 3, 2, 29}, {0, 1, 3, 5, 2, 2, 32}, {0, 2, 1, 2, 2, 3, 44}, diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index cafb21d90..e6a4d6be5 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -161,7 +161,7 @@ void GolfState::makeCPUMove() } } -glm::vec3 GolfState::calcCPUPosition() +glm::vec3 GolfState::calcCPUPosition() const { const auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; @@ -259,14 +259,13 @@ glm::vec3 GolfState::calcCPUPosition() windEffect = getOffset(WindOffsets, CPUStats[cpuID][CPUStat::WindAccuracy]); windEffect *= Clubs[clubID].getBaseTarget() / Clubs[ClubID::Driver].getBaseTarget(); - LogI << "Wind effect " << windEffect << std::endl; + //LogI << "Wind effect " << windEffect << std::endl; } auto wind = m_scene.getSystem()->getWindDirection(); wind = (glm::vec3(wind.x, 0.f, wind.z) * wind.y * windEffect); wind /= stepCount; - //TODO this should use CPU skill level not the default player level - const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTarget(totalDist)); + const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTargetAtLevel(CPUStats[cpuID][CPUStat::Skill])); auto dirNorm = glm::normalize(stepDir); const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); @@ -295,20 +294,20 @@ glm::vec3 GolfState::calcCPUPosition() { //TODO check these values so we don't accidentally //undo existing power/accuracy and improve them... - LogI << "Made a mistake!" << std::endl; + //LogI << "Made a mistake!" << std::endl; switch (cro::Util::Random::value(0, 2)) { default: case 0: - LogI << "Fluffed power" << std::endl; + //LogI << "Fluffed power" << std::endl; overShootDir *= 1.001f; break; case 1: - LogI << "Fluffed accuracy" << std::endl; + //LogI << "Fluffed accuracy" << std::endl; accuracyDir *= 1.001f; break; case 2: - LogI << "Fluffed power and accuracy" << std::endl; + //LogI << "Fluffed power and accuracy" << std::endl; accuracyDir *= 1.002f; overShootDir *= 0.9999f; break; diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 73687a8d7..7d6792649 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -89,7 +89,7 @@ namespace sv void buildWorld(); void makeCPUMove(); - glm::vec3 calcCPUPosition(); + glm::vec3 calcCPUPosition() const; //glm::vec3 calcCPUPositionOld(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From 5f3771f5ba1ef39b099f256b2a299e33fee63ec3 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 15 Jun 2023 15:06:15 +0100 Subject: [PATCH 049/233] begin init hall of fame --- samples/golf/src/golf/ClubhouseState.cpp | 2 +- samples/golf/src/golf/LeaderboardState.cpp | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index f5c3749e4..82ba5e7a9 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -132,7 +132,7 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha { //if we were returning from arcade this tidies up, else does nothing gg.unloadPlugin(); - + std::fill(m_readyState.begin(), m_readyState.end(), false); ctx.mainWindow.loadResources([this]() { diff --git a/samples/golf/src/golf/LeaderboardState.cpp b/samples/golf/src/golf/LeaderboardState.cpp index 4203c7092..f5791e9f2 100644 --- a/samples/golf/src/golf/LeaderboardState.cpp +++ b/samples/golf/src/golf/LeaderboardState.cpp @@ -89,7 +89,7 @@ LeaderboardState::LeaderboardState(cro::StateStack& ss, cro::State::Context ctx, m_requestRestart (false), m_confirmationType (ConfirmType::Quit) { - ctx.mainWindow.setMouseCaptured(false); + Social::updateHallOfFame(); buildScene(); } @@ -164,6 +164,15 @@ bool LeaderboardState::handleEvent(const cro::Event& evt) void LeaderboardState::handleMessage(const cro::Message& msg) { + if (msg.id == Social::MessageID::StatsMessage) + { + const auto& data = msg.getData(); + if (data.type == Social::StatEvent::HOFReceived) + { + LogI << data.index << ", " << data.page << ", " << data.holeCount << std::endl; + } + } + m_scene.forwardMessage(msg); } From f512ea1445250453e053af25c6d6170e9700b8ed Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 16 Jun 2023 11:46:42 +0100 Subject: [PATCH 050/233] use CPUStat odds in full-swing CPU mode --- samples/golf/src/golf/CPUGolfer.cpp | 130 +++++---- samples/golf/src/golf/server/CPUStats.hpp | 44 +-- .../golf/src/golf/server/ServerGolfRules.cpp | 269 ++---------------- .../golf/src/golf/server/ServerGolfState.cpp | 7 +- .../golf/src/golf/server/ServerGolfState.hpp | 2 - 5 files changed, 136 insertions(+), 316 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 1a322cdcc..ef48e2f7e 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -44,6 +44,30 @@ source distribution. namespace { + std::int32_t getCPUID(const ActivePlayer& player) + { + std::int32_t indexOffset = 0; + switch (Club::getClubLevel()) + { + default: break; + case 0: + indexOffset = 12; + break; + case 1: + indexOffset = 4; + break; + case 2: + + break; + } + + indexOffset += ((player.player / 4) + 1) * player.player; + CRO_ASSERT(indexOffset < CPUStats.size(), ""); + + return indexOffset; + } + + const std::array StateStrings = { std::string("Inactive"), @@ -97,6 +121,7 @@ result tolerance result tolerance putting stroke accuracy mistake odds +TODO this is more or less deprecated in favour of sharing stats with fast CPU */ const std::array CPUGolfer::m_skills = { @@ -206,8 +231,8 @@ void CPUGolfer::handleMessage(const cro::Message& msg) void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) { - target += getRandomOffset(target - m_activePlayer.position); - fallback += getRandomOffset(fallback - m_activePlayer.position); + //target += getRandomOffset(target - m_activePlayer.position); + //fallback += getRandomOffset(fallback - m_activePlayer.position); if (!m_fastCPU && m_state == State::Inactive) @@ -413,8 +438,11 @@ void CPUGolfer::setPuttingPower(float power) std::size_t CPUGolfer::getSkillIndex() const { - std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; - return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); + //std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; + //return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); + + auto id = getCPUID(m_activePlayer); + return std::clamp((static_cast(CPUStats.size()) - id) / 5, 0, 5); } //private @@ -831,17 +859,6 @@ void CPUGolfer::aim(float dt, glm::vec3 windVector) m_targetPower = std::min(m_targetPower, m_puttingPower); } - ////due to input lag 0.08 is actually ~0 ie perfectly accurate - ////so this range lies ~0.04 either side of perfect - //m_targetAccuracy = static_cast(cro::Util::Random::value(4, 12)) / 100.f; - - ////occasionally make really inaccurate - ////... or maybe even perfect? :) - //if (cro::Util::Random::value(0, 8) == 0) - //{ - // m_targetAccuracy += static_cast(cro::Util::Random::value(-8, 4)) / 100.f; - //} - calcAccuracy(); m_state = State::Stroke; @@ -938,7 +955,8 @@ void CPUGolfer::updatePrediction(float dt) if (m_activePlayer.terrain == TerrainID::Green) { //see if the flag indicator has a better suggestion :) - m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast(getSkillIndex()) * 0.01f))); + //m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast(getSkillIndex()) * 0.01f))); + m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast((CPUStats.size() - getCPUID(m_activePlayer)) / 2) * 0.01f))); } m_targetAccuracy -= (Deviance[devianceOffset] * 0.05f) * devianceMultiplier; @@ -1113,33 +1131,53 @@ void CPUGolfer::stroke(float dt) void CPUGolfer::calcAccuracy() { //due to input lag 0.08 is actually ~0 ie perfectly accurate - //so this range lies ~0.04 either side of perfect - //m_targetAccuracy = static_cast(cro::Util::Random::value(4, 12)) / 100.f; m_targetAccuracy = 0.08f; - if (m_skills[getSkillIndex()].strokeAccuracy != 0) - { - m_targetAccuracy += static_cast(-m_skills[getSkillIndex()].strokeAccuracy/*, m_skills[m_skillIndex].strokeAccuracy*/) / 100.f; - } + + //old ver + //if (m_skills[getSkillIndex()].strokeAccuracy != 0) + //{ + // m_targetAccuracy += static_cast(-m_skills[getSkillIndex()].strokeAccuracy) / 100.f; + //} + + const auto& Stat = CPUStats[getCPUID(m_activePlayer)]; + m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 + //occasionally make really inaccurate //... or maybe even perfect? :) - if (m_skills[getSkillIndex()].mistakeOdds != 0) + //old version + //if (m_skills[getSkillIndex()].mistakeOdds != 0) + //{ + // if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) + // { + // m_targetAccuracy += static_cast(cro::Util::Random::value(-16, 16)) / 100.f; + // } + //} + + std::int32_t puttingOdds = 0; + if (m_activePlayer.terrain == TerrainID::Green) { - if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) - { - m_targetAccuracy += static_cast(cro::Util::Random::value(-16, 16)) / 100.f; - } + float odds = std::min(1.f, glm::length(m_target - m_activePlayer.position) / 12.f) * 2.f; + puttingOdds = static_cast(std::round(odds)); } + if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood] + puttingOdds) + { + m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) / 100.f; + } + + + //to prevent multiple players making the same decision offset the accuracy a small amount //based on their client and player number - auto offset = (m_offsetRotation % 4) * 10; - m_targetAccuracy += (static_cast(cro::Util::Random::value(-(offset / 2), (offset / 2) + 1)) / 500.f) * getOffsetValue(); + //old version + //auto offset = (m_offsetRotation % 4) * 10; + //m_targetAccuracy += (static_cast(cro::Util::Random::value(-(offset / 2), (offset / 2) + 1)) / 500.f) * getOffsetValue(); if (m_clubID != ClubID::Putter) { - m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); + /*m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); if (m_skills[getSkillIndex()].mistakeOdds != 0) { @@ -1147,13 +1185,21 @@ void CPUGolfer::calcAccuracy() { m_targetPower += static_cast(cro::Util::Random::value(-6, 6)) / 1000.f; } + }*/ + + if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood]) + { + m_targetPower += cstat::getOffset(cstat::PowerOffsets, Stat[CPUStat::PowerAccuracy]) * 0.00002f; } + m_targetPower = std::min(1.f, m_targetPower); } else { //hack to make the ball putt a little further m_targetPower = std::min(1.f, m_targetPower * 0.98f /*+ (static_cast(m_skillIndex) * 0.01f)*/); + + //uhhh this actually shortens the power?? leaving this here though because it apparently works... } } @@ -1162,7 +1208,7 @@ float CPUGolfer::getOffsetValue() const float multiplier = m_activePlayer.terrain == TerrainID::Green ? smoothstep(0.2f, 0.95f, glm::length(m_target - m_activePlayer.position) / Clubs[ClubID::Putter].getTarget(m_distanceToPin)) : 1.f; return static_cast(1 - ((m_offsetRotation % 2) * 2)) - * static_cast((m_offsetRotation % (m_skills.size() - getSkillIndex()))) + //* static_cast((m_offsetRotation % (m_skills.size() - getSkillIndex()))) * multiplier; } @@ -1191,23 +1237,7 @@ void CPUGolfer::sendKeystroke(std::int32_t key, bool autoRelease) glm::vec3 CPUGolfer::getRandomOffset(glm::vec3 baseDir) const { - std::int32_t indexOffset = 0; - switch (Club::getClubLevel()) - { - default: break; - case 0: - indexOffset = 12; - break; - case 1: - indexOffset = 4; - break; - case 2: - - break; - } - - indexOffset += ((m_activePlayer.player / 4) + 1) * m_activePlayer.player; - CRO_ASSERT(indexOffset < CPUStats.size(), ""); + auto indexOffset = getCPUID(m_activePlayer); const auto baseLength = glm::length(baseDir); auto normDir = baseDir / baseLength; @@ -1216,11 +1246,11 @@ glm::vec3 CPUGolfer::getRandomOffset(glm::vec3 baseDir) const const float maxDist = Clubs[ClubID::Driver].getTargetAtLevel(cpuStat[CPUStat::Skill]); const float resultMultiplier = std::max(1.f, baseLength / maxDist); - const float powerOffset = getOffset(PowerOffsets, cpuStat[CPUStat::PowerAccuracy]); + const float powerOffset = cstat::getOffset(cstat::PowerOffsets, cpuStat[CPUStat::PowerAccuracy]); auto retVal = normDir * powerOffset * (resultMultiplier * resultMultiplier); normDir = { -normDir.z, normDir.y, normDir.x }; - const float accuracyOffset = getOffset(AccuracyOffsets, cpuStat[CPUStat::StrokeAccuracy]); + const float accuracyOffset = cstat::getOffset(cstat::AccuracyOffsets, cpuStat[CPUStat::StrokeAccuracy]); retVal += normDir * accuracyOffset * resultMultiplier; return retVal * (static_cast(indexOffset) / CPUStats.size()) * 0.05f; diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index d003a06e2..62897a065 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -33,6 +33,7 @@ source distribution. #include + struct CPUStat final { enum @@ -85,29 +86,32 @@ static constexpr std::array, 28> CPUSta } }; -//when applying wind multiply its effect by this -static constexpr std::array WindOffsets = +namespace cstat { - 30.f, 22.5f, 15.f, 10.75f, 7.5f, 0.1f, 7.5f , 10.75f, 15.f, 22.5f, 30.f -}; + //when applying wind multiply its effect by this + static constexpr std::array WindOffsets = + { + 30.f, 22.5f, 15.f, 10.75f, 7.5f, 0.1f, 7.5f , 10.75f, 15.f, 22.5f, 30.f + }; -//when calculating power offset, multiply this by club power -static constexpr std::array PowerOffsets = -{ - -10.f, -5.f, -1.f, -0.04f, 5.f, 10.f, 30.f -}; + //when calculating power offset, multiply this by club power + static constexpr std::array PowerOffsets = + { + -10.f, -5.f, -1.f, -0.5f, 5.f, 10.f, 30.f + }; -static constexpr std::array AccuracyOffsets = -{ - -16.f, -8.f, -2.f, -0.5f, 0.001f, 0.5f, 2.f, 8.f, 16.f -}; + static constexpr std::array AccuracyOffsets = + { + -16.f, -8.f, -2.f, -0.5f, 0.01f, 0.5f, 2.f, 8.f, 16.f + }; -template -float getOffset(const std::array& vals, std::int32_t accuracy) -{ - CRO_ASSERT(accuracy != 0, ""); - auto idx = (static_cast(S) / 2) + cro::Util::Random::value(-accuracy, accuracy); + template + float getOffset(const std::array& vals, std::int32_t accuracy) + { + CRO_ASSERT(accuracy != 0, ""); + auto idx = (static_cast(S) / 2) + cro::Util::Random::value(-accuracy, accuracy); - CRO_ASSERT(idx < S, ""); - return vals[idx]; + CRO_ASSERT(idx < S, ""); + return vals[idx]; + } } \ No newline at end of file diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index e6a4d6be5..35f9dbdd5 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -47,6 +47,7 @@ namespace } using namespace sv; +using namespace cstat; void GolfState::makeCPUMove() { if(m_sharedData.fastCPU @@ -82,6 +83,15 @@ void GolfState::makeCPUMove() switch (result.terrain) { case TerrainID::Water: + //use mistake odds to occasionally hit the water + { + const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; + if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) + { + break; + } + } + [[fallthrough]]; case TerrainID::Stone: case TerrainID::Scrub: { @@ -203,13 +213,18 @@ glm::vec3 GolfState::calcCPUPosition() const { default: break; case TerrainID::Water: + //use the mistake odds to sometimes accept this target + if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) + { + break; + } + [[fallthrough]]; case TerrainID::Stone: case TerrainID::Scrub: //else use the target point instead of the pin pos = m_holeData[m_currentHole].target; break; } - } else { @@ -238,7 +253,10 @@ glm::vec3 GolfState::calcCPUPosition() const } - + auto playerName = [&]() + { + return m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].name.toAnsiString(); + }; auto stepDir = pos - m_playerInfo[0].position; @@ -269,7 +287,7 @@ glm::vec3 GolfState::calcCPUPosition() const auto dirNorm = glm::normalize(stepDir); const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); - auto overShootDir = dirNorm * overShoot * clubMultiplier; + auto overShootDir = dirNorm * overShoot * (clubMultiplier * clubMultiplier); overShootDir /= stepCount; dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular @@ -294,22 +312,22 @@ glm::vec3 GolfState::calcCPUPosition() const { //TODO check these values so we don't accidentally //undo existing power/accuracy and improve them... - //LogI << "Made a mistake!" << std::endl; + LogI << " Made a mistake!" << std::endl; switch (cro::Util::Random::value(0, 2)) { default: case 0: - //LogI << "Fluffed power" << std::endl; - overShootDir *= 1.001f; + LogI << playerName() << " Fluffed power" << std::endl; + overShootDir *= 1.01f; break; case 1: - //LogI << "Fluffed accuracy" << std::endl; - accuracyDir *= 1.001f; + LogI << playerName() << " Fluffed accuracy" << std::endl; + accuracyDir *= 1.01f; break; case 2: - //LogI << "Fluffed power and accuracy" << std::endl; - accuracyDir *= 1.002f; - overShootDir *= 0.9999f; + LogI << playerName() << " Fluffed power and accuracy" << std::endl; + accuracyDir *= 1.02f; + overShootDir *= 0.999f; break; } } @@ -336,7 +354,7 @@ glm::vec3 GolfState::calcCPUPosition() const if(perfect && totalDist < Clubs[clubID].getBaseTarget()) { - LogI << "Got a PERFECT shot" << std::endl; + LogI << playerName() << " Got a PERFECT shot" << std::endl; pos = m_holeData[m_currentHole].pin; } else @@ -374,233 +392,6 @@ glm::vec3 GolfState::calcCPUPosition() const return pos; } -//glm::vec3 GolfState::calcCPUPositionOld() -//{ -// auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; -// auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; -// -// glm::vec3 pos = glm::vec3(0.f); -// std::int32_t skill = m_skillIndex; -// -// std::int32_t offset = ((m_playerInfo[0].player + 2) % 3) * 2; -// skill = std::clamp((skill - offset), 0, 6); -// -// //randomly invert skill -// if (cro::Util::Random::value(0, 49) == 25) -// { -// skill = (skill + 3) % 6; -// } -// -// -// //std::int32_t skill = m_playerInfo[0].player * 3; -// -// auto& ball = m_playerInfo[0].ballEntity.getComponent(); -// std::int32_t clubID = ClubID::Putter; -// -// //get longest range available -// if (ball.terrain != TerrainID::Green) -// { -// auto dist = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); -// clubID = getClub(dist); -// } -// const float clubDist = Clubs[clubID].getTargetAtLevel(std::min(2, skill / 3)); -// -// const auto pinDist = glm::length2(pinDir); -// const auto targetDist = glm::length2(targetDir); -// const float MinDist = m_scene.getSystem()->getPuttFromTee() ? 9.f : 2500.f; -// -// const auto pickTarget = [&](float dp) -// { -// if (m_scene.getSystem()->getPuttFromTee()) -// { -// if (dp > 0.97f) -// { -// return m_holeData[m_currentHole].pin; -// } -// else -// { -// if (targetDist < MinDist -// || pinDist < targetDist) -// { -// return m_holeData[m_currentHole].pin; -// } -// -// return m_holeData[m_currentHole].target; -// } -// } -// else -// { -// return m_holeData[m_currentHole].pin; -// } -// }; -// -// //if we're less than 20cm from the hole we'll assume -// //it's pretty much a dead cert going in -// if (pinDist > (0.2f * 0.2f)) -// { -// //if both the pin and the target are in front of the player -// if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4) -// { -// //set the target depending on how close it is -// if (pinDist < targetDist) -// { -// //always target pin if its closer -// pos = pickTarget(dp); -// } -// -// //target the pin if its in range of our longest club -// //and CPU skill > something -// else if (skill > 2) -// { -// pos = pickTarget(dp); -// } -// -// else -// { -// //target the pin if the target is too close -// if (targetDist < MinDist) //remember this is len2 -// { -// pos = m_holeData[m_currentHole].pin; -// } -// else -// { -// pos = m_holeData[m_currentHole].target; -// } -// } -// } -// else -// { -// //else set the pin as the target -// pos = pickTarget(dp); -// } -// -// //reduce the target distance so that it's in range of our longest club -// if (auto len2 = glm::length2(pos - m_playerInfo[0].position); len2 > -// (clubDist * clubDist)) -// { -// const float reduction = clubDist / std::sqrt(len2); -// pos = ((pos - m_playerInfo[0].position) * reduction) + m_playerInfo[0].position; -// } -// -// //make sure there's only a slim chance of getting it in the hole -// //if club is not a putter, and VERY slim chance if not a wedge -// if (ball.terrain == TerrainID::Green) -// { -// if (cro::Util::Random::value(0, 3 + (skill / 2)) == (m_playerInfo[0].player % 4)) -// { -// //add target offset -// pos += randomNormal() * cro::Util::Random::value(0.05f, 0.2f); -// CRO_ASSERT(!std::isnan(pos.x), ""); -// } -// -// //add offset based on length of putt -// const auto len2 = glm::length2(pos - m_playerInfo[0].position); -// const auto length = std::min(1.f, len2 / (10.f * 10.f)); -// auto accuracy = length * (1.f - (static_cast(skill) / 10.f)); -// accuracy /= 4.f; -// -// //lower skill has more chance of going off -// if (cro::Util::Random::value(0, 3 + skill) == 0) -// { -// accuracy *= 1.6f + (3 - (std::min(skill, 3))); -// } -// -// //higher skill has more chance of being accurate -// if (cro::Util::Random::value(0, 3 + skill) > 3) -// { -// accuracy /= 2.f; -// } -// -// pos += randomNormal() * accuracy; -// //LogI << "accuracy is " << accuracy << ", distance " << glm::length(pos - m_playerInfo[0].position) << std::endl; -// } -// else -// { -// //we're probably chipping if < 90m -// if (auto l2 = glm::length2(pos - m_playerInfo[0].position); l2 < (90.f * 90.f)) -// { -// //if len > 20 almost always add offset so we don't hole the ball -// if (l2 > (20.f * 20.f)) -// { -// if (cro::Util::Random::value(0, 100 - (skill * 10)) != m_playerInfo[0].player) -// { -// pos += randomNormal() * static_cast(cro::Util::Random::value(3, 10)) / 10.f; -// } -// } -// -// if (cro::Util::Random::value(0, 4 + skill) < 9) -// { -// //add offset based on how close we are to the pin/target -// pos += randomNormal() * (static_cast(cro::Util::Random::value(6, 70)) / 10.f) * std::min(0.2f, std::max(1.f, std::sqrt(l2) / 100.f)); -// CRO_ASSERT(!std::isnan(pos.x), ""); -// } -// else -// { -// //even if we have the skill reduce the chance of it going in -// pos += randomNormal() * static_cast(cro::Util::Random::value(1, 30)) / 10.f; -// } -// -// //more likely to overshoot with distance and lower skill -// if (cro::Util::Random::value(0, 4 + skill) < 4) -// { -// auto dir = pos - m_playerInfo[0].position; -// dir *= 1.f + (0.065f * std::min(1.f, std::sqrt(l2) / 80.f)); -// pos = m_playerInfo[0].position + dir; -// } -// } -// else -// { -// //iron or driver so add some arbitrary offset -// //based on bounce and wind strength/dir and distance to pos -// -// //TODO we need a good bounce as a percentage value... -// constexpr float BouncePercent = 1.05f; -// pos = ((pos - m_playerInfo[0].position) * (BouncePercent - (static_cast((m_playerInfo[0].player % 3) * 2) / 100.f))); -// -// auto step = pos / 5.f; -// pos = m_playerInfo[0].position + step; -// -// for (auto i = 0; i < 4; ++i) -// { -// const auto wind = m_scene.getSystem()->getWindDirection(); -// step += glm::vec3(wind.x, 0.f, wind.z) * wind.y * (0.15f * (7 - skill)); -// pos += step; -// } -// -// CRO_ASSERT(!std::isnan(pos.x), ""); -// } -// -// //reduce the distance if we're in the rough based on skill -// if (ball.terrain == TerrainID::Rough -// || ball.terrain == TerrainID::Bunker) -// { -// auto dir = pos - m_playerInfo[0].position; -// -// float pc = 1.f - (static_cast(std::max(0, 6 - skill)) / 100.f); -// dir *= pc; -// -// pos = m_playerInfo[0].position + dir; -// } -// } -// } -// else -// { -// pos = m_holeData[m_currentHole].pin; -// -// //if we're really daft maybe we'll hit it short -// if (cro::Util::Random::value(0, 2 + (skill * 2)) == 0) -// { -// pos = m_playerInfo[0].position + (pinDir / 2.f); -// } -// } -// -// -// pos.x = std::clamp(pos.x, 0.f, 320.f); -// pos.z = std::clamp(pos.z, -200.f, 0.f); -// -// return pos; -//} - void GolfState::handleDefaultRules(const GolfBallEvent& data) { if (data.type == GolfBallEvent::TurnEnded) diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 97c4eab49..b6f47ceb6 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -81,9 +81,6 @@ GolfState::GolfState(SharedData& sd) { initScene(); buildWorld(); - - /*auto level = std::min(Social::getLevel(), 24) + 2; - m_skillIndex = level / 4;*/ } LOG("Entered Server Golf State", cro::Logger::Type::Info); @@ -1027,8 +1024,8 @@ void GolfState::initScene() stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 break; case 2: - stride = cpuCount < 5 ? 1 : - cpuCount < 13 ? 2 : + stride = cpuCount < 3 ? 1 : + cpuCount < 8 ? 2 : 27 / cpuCount; break; } diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 7d6792649..6dd6e3fb8 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -72,7 +72,6 @@ namespace sv std::uint8_t m_currentBest; //current best score for hole, non-stroke games end if no-one can beat it cro::Clock m_turnTimer; - //std::int32_t m_skillIndex; //< base skill for CPU players based on host's current level TODO remove this std::array m_cpuProfileIndices = {}; @@ -90,7 +89,6 @@ namespace sv void makeCPUMove(); glm::vec3 calcCPUPosition() const; - //glm::vec3 calcCPUPositionOld(); void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From 32a9746df0d04161ec5c1a78bdd4c740d6d11312 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 16 Jun 2023 15:34:00 +0100 Subject: [PATCH 051/233] align full-swing cpu indices with fast mode cpu implement mistake correction factor for fast cpu --- samples/golf/src/golf/CPUGolfer.cpp | 81 ++++++++++++------- samples/golf/src/golf/CPUGolfer.hpp | 6 ++ samples/golf/src/golf/GolfState.cpp | 26 ++++-- samples/golf/src/golf/server/CPUStats.hpp | 6 ++ .../golf/src/golf/server/ServerGolfRules.cpp | 31 +++++-- 5 files changed, 107 insertions(+), 43 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index ef48e2f7e..fd86c9193 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -44,30 +44,6 @@ source distribution. namespace { - std::int32_t getCPUID(const ActivePlayer& player) - { - std::int32_t indexOffset = 0; - switch (Club::getClubLevel()) - { - default: break; - case 0: - indexOffset = 12; - break; - case 1: - indexOffset = 4; - break; - case 2: - - break; - } - - indexOffset += ((player.player / 4) + 1) * player.player; - CRO_ASSERT(indexOffset < CPUStats.size(), ""); - - return indexOffset; - } - - const std::array StateStrings = { std::string("Inactive"), @@ -441,10 +417,54 @@ std::size_t CPUGolfer::getSkillIndex() const //std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; //return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); - auto id = getCPUID(m_activePlayer); + auto id = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; return std::clamp((static_cast(CPUStats.size()) - id) / 5, 0, 5); } +void CPUGolfer::setCPUCount(std::int32_t cpuCount, const SharedStateData& sharedData) +{ + std::fill(m_cpuProfileIndices.begin(), m_cpuProfileIndices.end(), -1); + + std::int32_t baseCPUIndex = 0; + std::int32_t stride = 1; + + switch (Club::getClubLevel()) + { + default: break; + case 0: + baseCPUIndex = 12; + stride = 16 / cpuCount; //even distribution through 16x level 0 + break; + case 1: + baseCPUIndex = 8; + stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 + break; + case 2: + stride = cpuCount < 3 ? 1 : + cpuCount < 8 ? 2 : + 27 / cpuCount; + break; + } + + + CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); + + for (auto i = 0u; i < sharedData.connectionData.size(); ++i) + { + for (auto j = 0u; j < sharedData.connectionData[i].playerCount; ++j) + { + if (sharedData.connectionData[i].playerData[j].isCPU) + { + CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); + + auto cpuIndex = i * ConstVal::MaxPlayers + j; + m_cpuProfileIndices[cpuIndex] = baseCPUIndex; + baseCPUIndex += stride; + } + } + } +} + //private void CPUGolfer::startThinking(float duration) { @@ -956,7 +976,8 @@ void CPUGolfer::updatePrediction(float dt) { //see if the flag indicator has a better suggestion :) //m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast(getSkillIndex()) * 0.01f))); - m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast((CPUStats.size() - getCPUID(m_activePlayer)) / 2) * 0.01f))); + auto cpuID = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; + m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast((CPUStats.size() - cpuID) / 2) * 0.01f))); } m_targetAccuracy -= (Deviance[devianceOffset] * 0.05f) * devianceMultiplier; @@ -1140,7 +1161,7 @@ void CPUGolfer::calcAccuracy() // m_targetAccuracy += static_cast(-m_skills[getSkillIndex()].strokeAccuracy) / 100.f; //} - const auto& Stat = CPUStats[getCPUID(m_activePlayer)]; + const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 @@ -1177,9 +1198,9 @@ void CPUGolfer::calcAccuracy() if (m_clubID != ClubID::Putter) { - /*m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); + m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); - if (m_skills[getSkillIndex()].mistakeOdds != 0) + /*if (m_skills[getSkillIndex()].mistakeOdds != 0) { if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) { @@ -1237,7 +1258,7 @@ void CPUGolfer::sendKeystroke(std::int32_t key, bool autoRelease) glm::vec3 CPUGolfer::getRandomOffset(glm::vec3 baseDir) const { - auto indexOffset = getCPUID(m_activePlayer); + auto indexOffset = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; const auto baseLength = glm::length(baseDir); auto normDir = baseDir / baseLength; diff --git a/samples/golf/src/golf/CPUGolfer.hpp b/samples/golf/src/golf/CPUGolfer.hpp index ebb52e206..2d5c1b67d 100644 --- a/samples/golf/src/golf/CPUGolfer.hpp +++ b/samples/golf/src/golf/CPUGolfer.hpp @@ -29,12 +29,15 @@ source distribution. #pragma once +#include "CommonConsts.hpp" + #include #include #include #include #include +#include namespace cro { @@ -61,6 +64,7 @@ class CPUGolfer final : public cro::GuiClient std::size_t getSkillIndex() const; void setFastCPU(bool fast) { m_fastCPU = fast; } + void setCPUCount(std::int32_t, const struct SharedStateData&); private: const InputParser& m_inputParser; //only reads the state - actual commands are send by raising events. @@ -80,6 +84,8 @@ class CPUGolfer final : public cro::GuiClient std::int32_t m_predictionCount; float m_puttingPower; //how much power is predicted by the power bar flag + std::array m_cpuProfileIndices = {}; + enum class State { Inactive, diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index f5b237d91..06bff504e 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -178,6 +178,12 @@ namespace bool recordCam = false; + bool isFastCPU(const SharedStateData& sd, const ActivePlayer& activePlayer) + { + return sd.connectionData[activePlayer.client].playerData[activePlayer.player].isCPU + && sd.fastCPU; + } + const std::array CourseNames = { "course_01", @@ -337,6 +343,7 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared //do this first so scores are reset before scoreboard //is first created. std::int32_t clientCount = 0; + std::int32_t cpuCount = 0; for (auto& c : sd.connectionData) { if (c.playerCount != 0) @@ -347,6 +354,11 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared { c.playerData[i].matchScore = 0; c.playerData[i].skinScore = 0; + + if (c.playerData[i].isCPU) + { + cpuCount++; + } } } } @@ -354,7 +366,7 @@ GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, Shared { Achievements::awardAchievement(AchievementStrings[AchievementID::BetterWithFriends]); } - + m_cpuGolfer.setCPUCount(cpuCount, sd); context.mainWindow.loadResources([this]() { addSystems(); @@ -1166,7 +1178,8 @@ void GolfState::handleMessage(const cro::Message& msg) case TerrainID::Bunker: case TerrainID::Scrub: case TerrainID::Water: - if (data.travelDistance > 100.f) + if (data.travelDistance > 100.f + && !isFastCPU(m_sharedData, m_currentPlayer)) { m_activeAvatar->model.getComponent().play(m_activeAvatar->animationIDs[AnimationID::Disappoint], 1.f, 0.2f); } @@ -1174,8 +1187,9 @@ void GolfState::handleMessage(const cro::Message& msg) case TerrainID::Green: if (getClub() != ClubID::Putter) { - if (data.pinDistance < 1.f + if ((data.pinDistance < 1.f || data.travelDistance > 10000.f) + && !isFastCPU(m_sharedData, m_currentPlayer)) { m_activeAvatar->model.getComponent().play(m_activeAvatar->animationIDs[AnimationID::Celebrate], 1.f, 1.2f); @@ -1184,7 +1198,8 @@ void GolfState::handleMessage(const cro::Message& msg) Social::awardXP(XPValues[XPID::Special] / 2, XPStringID::NiceChip); } } - else if (data.travelDistance < 9.f) + else if (data.travelDistance < 9.f + && !isFastCPU(m_sharedData, m_currentPlayer)) { m_activeAvatar->model.getComponent().play(m_activeAvatar->animationIDs[AnimationID::Disappoint], 1.f, 0.4f); } @@ -1202,7 +1217,8 @@ void GolfState::handleMessage(const cro::Message& msg) } break; case TerrainID::Fairway: - if (data.travelDistance < 16.f) + if (data.travelDistance < 16.f + && !isFastCPU(m_sharedData, m_currentPlayer)) { m_activeAvatar->model.getComponent().play(m_activeAvatar->animationIDs[AnimationID::Disappoint], 1.f, 0.4f); } diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index 62897a065..f14d455d0 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -105,6 +105,12 @@ namespace cstat -16.f, -8.f, -2.f, -0.5f, 0.01f, 0.5f, 2.f, 8.f, 16.f }; + //how much of the missing 'mistake vector' is added back + static constexpr std::array MistakeCorrection = + { + 0.24f, 0.43f, 0.65f, 0.95f, 0.8f, 0.45f, 0.25f + }; + template float getOffset(const std::array& vals, std::int32_t accuracy) { diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 35f9dbdd5..ceb3b9612 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -232,7 +232,7 @@ glm::vec3 GolfState::calcCPUPosition() const if (m_scene.getSystem()->getPuttFromTee()) { if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); - dp > 0.4 && dp < 0.97f) //target in front, but not the same dir as pin + dp > 0.4 && dp < 0.98f) //target in front, but not the same dir as pin { //don't use if too close if (glm::length2(targetDir) > (3.f * 3.f) @@ -253,7 +253,7 @@ glm::vec3 GolfState::calcCPUPosition() const } - auto playerName = [&]() + const auto playerName = [&]() { return m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].name.toAnsiString(); }; @@ -304,11 +304,15 @@ glm::vec3 GolfState::calcCPUPosition() const std::int32_t puttingOdds = 0; if (ball.terrain == TerrainID::Green) { - float odds = std::min(1.f, totalDist / 12.f) * 2.f; + float odds = std::pow(std::min(1.f, totalDist / 5.f), 2.f); + + accuracyDir *= (1.f + odds); + odds *= 2.f; + puttingOdds = static_cast(std::round(odds)); } - if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) + if (cro::Util::Random::value(0, 14) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) { //TODO check these values so we don't accidentally //undo existing power/accuracy and improve them... @@ -318,18 +322,26 @@ glm::vec3 GolfState::calcCPUPosition() const default: case 0: LogI << playerName() << " Fluffed power" << std::endl; - overShootDir *= 1.01f; + overShootDir *= 2.56f; break; case 1: LogI << playerName() << " Fluffed accuracy" << std::endl; - accuracyDir *= 1.01f; + accuracyDir *= 1.78f; break; case 2: LogI << playerName() << " Fluffed power and accuracy" << std::endl; - accuracyDir *= 1.02f; - overShootDir *= 0.999f; + accuracyDir *= 1.92f; + overShootDir *= 1.98f; break; } + + if (cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) + { + LogI << playerName() << " made uber mistake" << std::endl; + stepDir /= 2.f; + + stepDir += (stepDir * getOffset(MistakeCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy])); + } } //calculate perfection odds only if we didn't make a mistake @@ -379,6 +391,9 @@ glm::vec3 GolfState::calcCPUPosition() const pos += wind; pos += overShootDir; pos += accuracyDir; + + //TODO check each step for terrain to see if we + //accidentally cut corners } //if we're really close to the hole plop it in based on stroke accuracy From f361bfcdff4bb2e90b39657fa23a71eb9aa8a1e4 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 17 Jun 2023 12:40:57 +0100 Subject: [PATCH 052/233] test terrain when putting from tee during CPU turn putt from tee courses reset ball to target instead of edge --- samples/golf/src/golf/BallSystem.cpp | 110 +++++++++++------- samples/golf/src/golf/ClubhouseState.cpp | 18 +++ samples/golf/src/golf/server/CPUStats.hpp | 8 +- .../golf/src/golf/server/ServerGolfRules.cpp | 69 +++++++++-- 4 files changed, 151 insertions(+), 54 deletions(-) diff --git a/samples/golf/src/golf/BallSystem.cpp b/samples/golf/src/golf/BallSystem.cpp index aebfbaf6a..6ddb89c75 100644 --- a/samples/golf/src/golf/BallSystem.cpp +++ b/samples/golf/src/golf/BallSystem.cpp @@ -707,64 +707,88 @@ void BallSystem::processEntity(cro::Entity entity, float dt) { ball.spin = { 0.f,0.f }; - //move towards player until we find non-water + std::uint8_t terrain = TerrainID::Water; + if (m_puttFromTee) + { + //just place at the nearest tee or target - //make sure ball height is level with target - //else moving it may cause the collision test - //to miss if the terrain is much higher than - //the water level. + terrain = TerrainID::Green; + if (glm::length2(tx.getPosition() - m_holeData->tee) < + glm::length2(tx.getPosition() - m_holeData->target)) + { + tx.setPosition(m_holeData->tee); + } + else + { + tx.setPosition(m_holeData->target); + } + } + else + { - glm::vec3 dir = ball.startPoint - ballPos; - ballPos.y = ball.startPoint.y; + //move towards player until we find non-water - auto length = glm::length(dir); - dir /= length; - std::int32_t maxDist = static_cast(length /*- 10.f*/); + //make sure ball height is level with target + //else moving it may cause the collision test + //to miss if the terrain is much higher than + //the water level. - //if we're on a putting course take smaller steps for better accuracy - if (m_puttFromTee) - { - dir /= 4.f; - maxDist *= 4; - } + glm::vec3 dir = ball.startPoint - ballPos; + ballPos.y = ball.startPoint.y; - for (auto i = 0; i < maxDist; ++i) - { - ballPos += dir; - auto res = getTerrain(ballPos); - terrain = res.terrain; + auto length = glm::length(dir); + dir /= length; + std::int32_t maxDist = static_cast(length /*- 10.f*/); - const auto slope = dot(res.normal, glm::vec3(0.f, 1.f, 0.f)); + //if we're on a putting course take smaller steps for better accuracy + if (m_puttFromTee) + { + dir /= 4.f; + maxDist *= 4; + } - if (terrain != TerrainID::Water - && terrain != TerrainID::Scrub - && terrain != TerrainID::Stone - && slope > 0.996f) + for (auto i = 0; i < maxDist; ++i) { - //move the ball a bit closer so we're not balancing on the edge - //but only if we're not on the green else we might get placed in the hole :) - if (res.terrain != TerrainID::Green) + ballPos += dir; + auto res = getTerrain(ballPos); + terrain = res.terrain; + + const auto slope = dot(res.normal, glm::vec3(0.f, 1.f, 0.f)); + + if (terrain != TerrainID::Water + && terrain != TerrainID::Scrub + && terrain != TerrainID::Stone + && slope > 0.996f) { - ballPos += dir * 1.5f; - res = getTerrain(ballPos); + //move the ball a bit closer so we're not balancing on the edge + //but only if we're not on the green else we might get placed in the hole :) + if (res.terrain != TerrainID::Green) + { + ballPos += dir * 1.5f; + res = getTerrain(ballPos); + } + + ballPos = res.intersection; + tx.setPosition(ballPos); + break; } + } - ballPos = res.intersection; - tx.setPosition(ballPos); - break; + //if for some reason we never got out the water, put the ball back at the start + if (terrain == TerrainID::Water + || terrain == TerrainID::Scrub) + { + //this is important else we'll end up trying to drive + //down a putting course :facepalm: + terrain = m_puttFromTee ? TerrainID::Green : TerrainID::Fairway; + tx.setPosition(m_holeData->tee); } } - //if for some reason we never got out the water, put the ball back at the start - if (terrain == TerrainID::Water - || terrain == TerrainID::Scrub) - { - //this is important else we'll end up trying to drive - //down a putting course :facepalm: - terrain = m_puttFromTee ? TerrainID::Green : TerrainID::Fairway; - tx.setPosition(m_holeData->tee); - } + + + //raise message to say player should be penalised auto* msg = postEvent(); diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index 82ba5e7a9..584f1689b 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -1198,6 +1198,24 @@ void ClubhouseState::buildScene() } }; + //unlock + entity = m_backgroundScene.createEntity(); + entity.addComponent().setPosition({ 14.4f, 0.f, -3.f }); + entity.addComponent() = m_menuSounds.getEmitter("unlock"); + entity.addComponent().active = true; + entity.getComponent().setUserData(45.f); + entity.getComponent().function = + [](cro::Entity e, float dt) + { + auto& f = e.getComponent().getUserData(); + f -= dt; + if (f < 0.f) + { + f += 65.f; + e.getComponent().play(); + } + }; + //update the 3D view auto updateView = [&](cro::Camera& cam) diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index f14d455d0..4d6bdaae1 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -70,7 +70,7 @@ static constexpr std::array, 28> CPUSta {0, 3, 2, 3, 3, 2, 40}, {0, 2, 1, 4, 6, 3, 26}, {0, 2, 2, 2, 3, 2, 29}, - {0, 1, 3, 5, 2, 2, 32}, + {0, 1, 3, 4, 2, 2, 32}, {0, 2, 1, 2, 2, 3, 44}, {0, 2, 1, 2, 2, 2, 27}, {0, 3, 2, 1, 3, 2, 30}, @@ -111,6 +111,12 @@ namespace cstat 0.24f, 0.43f, 0.65f, 0.95f, 0.8f, 0.45f, 0.25f }; + //how much to move away from the hole if we're too close after a long shot + static constexpr std::array HoleCorrection = + { + 0.5f, 0.4f, 0.25f, 0.1f, 0.2f, 0.35f, 0.48f + }; + template float getOffset(const std::array& vals, std::int32_t accuracy) { diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index ceb3b9612..96f9087f6 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -95,7 +95,7 @@ void GolfState::makeCPUMove() case TerrainID::Stone: case TerrainID::Scrub: { - std::int32_t tries = 300; + /*std::int32_t tries = 300; auto dir = glm::normalize(pos - m_playerInfo[0].position); do { @@ -103,7 +103,7 @@ void GolfState::makeCPUMove() result = m_scene.getSystem()->getTerrain(pos); } while (tries-- && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) - && glm::length2(pos) > 1); + && glm::length2(pos) > 1);*/ } break; default: break; @@ -177,6 +177,8 @@ glm::vec3 GolfState::calcCPUPosition() const const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; auto pos = m_holeData[m_currentHole].pin; //always prefer the pin as the target unless blocked for some reason + const auto puttFromTee = m_scene.getSystem()->getPuttFromTee(); + const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; CRO_ASSERT(cpuID != -1, ""); @@ -229,7 +231,7 @@ glm::vec3 GolfState::calcCPUPosition() const else { //else if we're on a mini-putt course see if there's a dog-leg - if (m_scene.getSystem()->getPuttFromTee()) + if (puttFromTee) { if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); dp > 0.4 && dp < 0.98f) //target in front, but not the same dir as pin @@ -316,26 +318,27 @@ glm::vec3 GolfState::calcCPUPosition() const { //TODO check these values so we don't accidentally //undo existing power/accuracy and improve them... - LogI << " Made a mistake!" << std::endl; + LogI << playerName() << " Made a mistake!" << std::endl; switch (cro::Util::Random::value(0, 2)) { default: case 0: LogI << playerName() << " Fluffed power" << std::endl; - overShootDir *= 2.56f; + overShootDir *= puttFromTee ? 1.3f : 2.56f; break; case 1: LogI << playerName() << " Fluffed accuracy" << std::endl; - accuracyDir *= 1.78f; + accuracyDir *= puttFromTee ? 1.01f : 1.78f; break; case 2: LogI << playerName() << " Fluffed power and accuracy" << std::endl; - accuracyDir *= 1.92f; - overShootDir *= 1.98f; + accuracyDir *= puttFromTee ? 1.02f : 1.92f; + overShootDir *= puttFromTee ? 1.25f : 1.98f; break; } - if (cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) + if (glm::length2(m_holeData[m_currentHole].pin - m_playerInfo[0].position) > (2.f * 2.f) && + cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) { LogI << playerName() << " made uber mistake" << std::endl; stepDir /= 2.f; @@ -366,6 +369,27 @@ glm::vec3 GolfState::calcCPUPosition() const if(perfect && totalDist < Clubs[clubID].getBaseTarget()) { + if (puttFromTee) + { + //check for corner cutting + auto dir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; + dir /= stepCount; + + pos = m_playerInfo[0].position; + + for (auto i = 0; i < stepCount; ++i) + { + pos += dir; + auto terrain = m_scene.getSystem()->getTerrain(pos); + if (terrain.terrain != TerrainID::Green) + { + LogI << "Perfect shot would cut corner" << std::endl; + perfect = false; + break; + } + } + } + LogI << playerName() << " Got a PERFECT shot" << std::endl; pos = m_holeData[m_currentHole].pin; } @@ -392,10 +416,35 @@ glm::vec3 GolfState::calcCPUPosition() const pos += overShootDir; pos += accuracyDir; - //TODO check each step for terrain to see if we + //check each step for terrain to see if we //accidentally cut corners + if (puttFromTee) + { + auto terrain = m_scene.getSystem()->getTerrain(pos); + if (terrain.terrain != TerrainID::Green) + { + break; + } + } + } + + //one final check that we're not toooo close to the hole after + //a long shot else we get too many chip-ins without the 'perfect' requirement + if (glm::length2(pos - m_playerInfo[0].position) > (5.f * 5.f)) + { + if (!puttFromTee || cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) + { + if (auto l2 = glm::length2(pos - m_holeData[m_currentHole].pin); l2 < (0.15f * 0.15f)) + { + auto dir = (pos - m_holeData[m_currentHole].pin) / std::sqrt(l2); + dir *= getOffset(HoleCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy]); + pos += dir; + LogI << playerName() << " Corrected for too much perfection." << std::endl; + } + } } + //if we're really close to the hole plop it in based on stroke accuracy if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 10) From a9482adca86f5de28a67fb006aac3514c49920e4 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 17 Jun 2023 13:57:27 +0100 Subject: [PATCH 053/233] when reducing CPU target to club range, move towards backup target instead of player. --- samples/golf/src/golf/CPUGolfer.cpp | 17 ++++++++++++++--- .../golf/src/golf/server/ServerGolfRules.cpp | 11 +++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index fd86c9193..8f9f80254 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -234,9 +234,20 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) else if (auto len = glm::length(target - m_activePlayer.position); len > Clubs[ClubID::Driver].getTarget(0.f)) { - const float reduction = (Clubs[ClubID::Driver].getTarget(0.f) / len) * 0.95f; - auto newDir = (target - m_activePlayer.position) * reduction; - newDir += m_activePlayer.position; + glm::vec3 newDir(0.f); + if (target != fallback) + { + len -= Clubs[ClubID::Driver].getTarget(0.f); + const auto correctionDir = glm::normalize(fallback - target) * (len * 1.05f); + newDir = target + correctionDir; + } + else + { + //just move back towards the player + const float reduction = (Clubs[ClubID::Driver].getTarget(0.f) / len) * 0.95f; + newDir = (target - m_activePlayer.position) * reduction; + newDir += m_activePlayer.position; + } const auto terrain = m_collisionMesh.getTerrain(newDir); switch (terrain.terrain) diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 96f9087f6..b1de8e247 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -206,8 +206,14 @@ glm::vec3 GolfState::calcCPUPosition() const if (auto len2 = glm::length2(pinDir); len2 > (clubDist * clubDist)) { - const float reduction = clubDist / std::sqrt(len2); - pos = (pinDir * reduction) + m_playerInfo[0].position; + //rather than move back toward the player (which might put us in the rough) + //head towards the target point which in theory should aim us towards the fairway + const auto len = std::sqrt(len2) - clubDist; + const auto correctionDir = glm::normalize(m_holeData[m_currentHole].target - m_holeData[m_currentHole].pin) * len; + pos = m_holeData[m_currentHole].pin + correctionDir; + + //const float reduction = clubDist / std::sqrt(len2); + //pos = (pinDir * reduction) + m_playerInfo[0].position; } auto result = m_scene.getSystem()->getTerrain(pos); @@ -295,6 +301,7 @@ glm::vec3 GolfState::calcCPUPosition() const dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular const float strokeAccuracy = getOffset(AccuracyOffsets, CPUStats[cpuID][CPUStat::StrokeAccuracy]); auto accuracyDir = dirNorm * strokeAccuracy * clubMultiplier; + if (puttFromTee) accuracyDir *= 0.5f; accuracyDir /= stepCount; //TODO include offset for rough or bunker terrain - this should probably be another stat From 1fd6b282fbcda16ab0d7c44b965768a13d302744 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 17 Jun 2023 15:22:08 +0100 Subject: [PATCH 054/233] update profile edit buttons --- samples/golf/src/golf/MenuAvatars.cpp | 18 ++++++++++-- samples/golf/src/golf/ProfileState.cpp | 39 +++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index b25bbdc98..39cda9c27 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -797,13 +797,17 @@ void MenuState::createAvatarMenu(cro::Entity parent) //create profile entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 265.f, 52.f, 0.1f }); + entity.addComponent().setPosition({ 265.f, 53.f, 0.1f }); entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("profile_edit_select"); entity.getComponent().setColour(cro::Colour::Transparent); entity.addComponent().function = MenuTextCallback(); bounds = entity.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; entity.addComponent().area = bounds; entity.getComponent().setGroup(MenuID::Avatar); entity.getComponent().callbacks[cro::UIInput::Selected] = selectionCallback; @@ -856,13 +860,17 @@ void MenuState::createAvatarMenu(cro::Entity parent) //edit profile entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 265.f, 37.f, 0.1f }); + entity.addComponent().setPosition({ 265.f, 36.f, 0.1f }); entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("profile_edit_select"); entity.getComponent().setColour(cro::Colour::Transparent); entity.addComponent().function = MenuTextCallback(); bounds = entity.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; entity.addComponent().area = bounds; entity.getComponent().setGroup(MenuID::Avatar); entity.getComponent().callbacks[cro::UIInput::Selected] = selectionCallback; @@ -883,13 +891,17 @@ void MenuState::createAvatarMenu(cro::Entity parent) //delete profile entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 265.f, 22.f, 0.1f }); + entity.addComponent().setPosition({ 265.f, 19.f, 0.1f }); entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("profile_edit_select"); entity.getComponent().setColour(cro::Colour::Transparent); entity.addComponent().function = MenuTextCallback(); bounds = entity.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; entity.addComponent().area = bounds; entity.getComponent().setGroup(MenuID::Avatar); entity.getComponent().callbacks[cro::UIInput::Selected] = selectionCallback; diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index d7db53d7c..6aa8827b0 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -982,8 +982,33 @@ void ProfileState::buildScene() } }); + + //updates the profile icon + auto profileIcon = createButton("button_highlight", glm::vec2(269.f, 55.f)); + bounds = profileIcon.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; + profileIcon.getComponent().area = bounds; + profileIcon.getComponent().callbacks[cro::UIInput::ButtonUp] = + uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + + } + }); + + //save/quit buttons - auto saveQuit = createButton("button_highlight", glm::vec2(269.f, 48.f)); + auto saveQuit = createButton("button_highlight", glm::vec2(269.f, 38.f)); + bounds = saveQuit.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; + saveQuit.getComponent().area = bounds; saveQuit.getComponent().callbacks[cro::UIInput::ButtonUp] = uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) { @@ -994,7 +1019,14 @@ void ProfileState::buildScene() quitState(); } }); - auto quit = createButton("button_highlight", glm::vec2(269.f, 24.f)); + + auto quit = createButton("button_highlight", glm::vec2(269.f, 21.f)); + bounds = quit.getComponent().getTextureBounds(); + bounds.left += 2.f; + bounds.bottom += 2.f; + bounds.width -= 4.f; + bounds.height -= 4.f; + quit.getComponent().area = bounds; quit.getComponent().callbacks[cro::UIInput::ButtonUp] = uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) { @@ -1005,8 +1037,7 @@ void ProfileState::buildScene() }); - //TODO check for steamdeck and add mugshot button - //TODO will this also break big picture mode? + auto addCorners = [&](cro::Entity p, cro::Entity q) { From 0136275fce9906d6df87da618b1a6cf1a3a86fb3 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sat, 17 Jun 2023 15:28:54 +0100 Subject: [PATCH 055/233] enable dpad magic --- samples/golf/src/golf/ClubhouseState.cpp | 42 +++++++++++++++++++----- samples/golf/src/golf/ClubhouseState.hpp | 16 +++++++-- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index 584f1689b..9595f049a 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -128,7 +128,8 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha m_currentMenu (MenuID::Main), m_prevMenu (MenuID::Main), m_gameCreationIndex (0), - m_arcadeIndex (0) + m_arcadeIndexKey (0), + m_arcadeIndexJoy (0) { //if we were returning from arcade this tidies up, else does nothing gg.unloadPlugin(); @@ -354,12 +355,12 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) switch (evt.key.keysym.sym) { default: - if (m_arcadeIndex < Arcade.size()) + if (m_arcadeIndexKey < ArcadeKey.size()) { - if (evt.key.keysym.sym == Arcade[m_arcadeIndex]) + if (evt.key.keysym.sym == ArcadeKey[m_arcadeIndexKey]) { - m_arcadeIndex++; - if (m_arcadeIndex == Arcade.size()) + m_arcadeIndexKey++; + if (m_arcadeIndexKey == ArcadeKey.size()) { //MAGIC if (m_arcadeEnt.isValid()) @@ -373,7 +374,7 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) } else { - m_arcadeIndex = 0; + m_arcadeIndexKey = 0; } } @@ -381,7 +382,7 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) case SDLK_RETURN: case SDLK_RETURN2: case SDLK_KP_ENTER: - m_arcadeIndex = 0; + m_arcadeIndexKey = 0; applyTextEdit(); break; case SDLK_ESCAPE: @@ -422,7 +423,32 @@ bool ClubhouseState::handleEvent(const cro::Event& evt) cro::App::getWindow().setMouseCaptured(true); switch (evt.cbutton.button) { - default: break; + default: + if (m_arcadeIndexJoy < ArcadeJoy.size()) + { + if (evt.cbutton.button == ArcadeJoy[m_arcadeIndexJoy]) + { + m_arcadeIndexJoy++; + if (m_arcadeIndexJoy == ArcadeJoy.size()) + { + //MAGIC + if (m_arcadeEnt.isValid()) + { + m_arcadeEnt.getComponent().setScale(glm::vec2(1.f)); + m_arcadeEnt.getComponent().enabled = true; + + m_audioEnts[AudioID::Win].getComponent().play(); + } + } + } + else + { + m_arcadeIndexJoy = 0; + } + } + + + break; //leave the current menu when B is pressed. case cro::GameController::ButtonB: quitMenu(); diff --git a/samples/golf/src/golf/ClubhouseState.hpp b/samples/golf/src/golf/ClubhouseState.hpp index b17401528..af7f3e9c4 100644 --- a/samples/golf/src/golf/ClubhouseState.hpp +++ b/samples/golf/src/golf/ClubhouseState.hpp @@ -204,7 +204,7 @@ class ClubhouseState final : public cro::State, public cro::GuiClient LobbyPager m_lobbyPager; - static constexpr std::array Arcade = + static constexpr std::array ArcadeKey = { SDLK_UP, SDLK_UP, @@ -215,7 +215,19 @@ class ClubhouseState final : public cro::State, public cro::GuiClient SDLK_LEFT, SDLK_RIGHT, }; - std::size_t m_arcadeIndex; + static constexpr std::array ArcadeJoy = + { + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + }; + std::size_t m_arcadeIndexKey; + std::size_t m_arcadeIndexJoy; cro::Entity m_arcadeEnt; void addSystems(); From 38919a73e413a84e82a7f2c4a396638cc67f18f5 Mon Sep 17 00:00:00 2001 From: fallahn Date: Sun, 18 Jun 2023 15:04:17 +0100 Subject: [PATCH 056/233] tweak profile edit layout tweak fast cpu stats --- samples/golf/src/golf/ProfileState.cpp | 8 ++++---- samples/golf/src/golf/server/CPUStats.hpp | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index 6aa8827b0..66499509f 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -93,7 +93,7 @@ namespace }; }; - constexpr glm::uvec2 BallTexSize(110u, 110u); + constexpr glm::uvec2 BallTexSize(96u, 110u); constexpr glm::uvec2 AvatarTexSize(130u, 202u); constexpr glm::vec3 CameraBasePosition({ -0.867f, 1.325f, -1.68f }); @@ -961,7 +961,7 @@ void ProfileState::buildScene() // { // } // }); - auto ballLeft = createButton("arrow_left", glm::vec2(311.f, 134.f)); //+24Y + auto ballLeft = createButton("arrow_left", glm::vec2(267.f, 134.f)); //+24Y ballLeft.getComponent().callbacks[cro::UIInput::ButtonUp] = uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) { @@ -971,7 +971,7 @@ void ProfileState::buildScene() m_audioEnts[AudioID::Back].getComponent().play(); } }); - auto ballRight = createButton("arrow_right", glm::vec2(440.f, 134.f)); + auto ballRight = createButton("arrow_right", glm::vec2(382.f, 134.f)); ballRight.getComponent().callbacks[cro::UIInput::ButtonUp] = uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) { @@ -1091,7 +1091,7 @@ void ProfileState::buildScene() //ball preview entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 323.f, 83.f, 0.1f }); + entity.addComponent().setPosition({ 279.f, 83.f, 0.1f }); if (!m_sharedData.pixelScale) { entity.getComponent().setScale(glm::vec2(1.f / getViewScale())); diff --git a/samples/golf/src/golf/server/CPUStats.hpp b/samples/golf/src/golf/server/CPUStats.hpp index 4d6bdaae1..953fe7906 100644 --- a/samples/golf/src/golf/server/CPUStats.hpp +++ b/samples/golf/src/golf/server/CPUStats.hpp @@ -53,16 +53,16 @@ struct CPUStat final static constexpr std::array, 28> CPUStats = { { - {2, 1, 1, 2, 2, 3, 82}, - {2, 2, 2, 1, 1, 2, 76}, - {2, 1, 1, 2, 2, 2, 64}, - {2, 2, 2, 1, 3, 1, 70}, + {2, 1, 2, 2, 2, 3, 82}, + {2, 2, 2, 2, 1, 2, 76}, + {2, 2, 3, 2, 2, 2, 64}, + {2, 3, 2, 3, 3, 1, 70}, {1, 2, 3, 2, 2, 2, 71}, - {1, 1, 2, 1, 2, 2, 46}, + {1, 3, 2, 1, 2, 2, 46}, {1, 2, 1, 2, 2, 2, 63}, {1, 3, 2, 2, 3, 3, 52}, - {1, 2, 2, 3, 5, 3, 50}, + {1, 4, 2, 3, 5, 3, 50}, {1, 3, 1, 3, 4, 3, 67}, {1, 4, 3, 3, 5, 2, 41}, {1, 3, 3, 4, 6, 3, 39}, @@ -73,7 +73,7 @@ static constexpr std::array, 28> CPUSta {0, 1, 3, 4, 2, 2, 32}, {0, 2, 1, 2, 2, 3, 44}, {0, 2, 1, 2, 2, 2, 27}, - {0, 3, 2, 1, 3, 2, 30}, + {0, 4, 2, 1, 3, 2, 30}, {0, 3, 2, 2, 3, 3, 35}, {0, 2, 1, 3, 4, 3, 23}, {0, 3, 2, 2, 5, 3, 20}, From 657d140216621cd3d1e4f9a0d0a178d65b5178f7 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 19 Jun 2023 14:03:43 +0100 Subject: [PATCH 057/233] auto swing mode CPU for fast skipping --- samples/golf/src/golf/CPUGolfer.cpp | 156 ++++++++++-------- samples/golf/src/golf/CPUGolfer.hpp | 4 +- samples/golf/src/golf/GolfState.cpp | 14 +- samples/golf/src/golf/InputParser.cpp | 13 ++ samples/golf/src/golf/InputParser.hpp | 3 + .../golf/src/golf/server/ServerGolfState.cpp | 4 +- 6 files changed, 117 insertions(+), 77 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 8f9f80254..f745fdb7f 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -97,7 +97,7 @@ result tolerance result tolerance putting stroke accuracy mistake odds -TODO this is more or less deprecated in favour of sharing stats with fast CPU + */ const std::array CPUGolfer::m_skills = { @@ -109,7 +109,7 @@ const std::array CPUGolfer::m_skills = {CPUGolfer::Skill::Dynamic, 2.f, 0.06f, 0, 50} }; -CPUGolfer::CPUGolfer(const InputParser& ip, const ActivePlayer& ap, const CollisionMesh& cm) +CPUGolfer::CPUGolfer(InputParser& ip, const ActivePlayer& ap, const CollisionMesh& cm) : m_inputParser (ip), m_activePlayer (ap), m_collisionMesh (cm), @@ -210,7 +210,7 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) //target += getRandomOffset(target - m_activePlayer.position); //fallback += getRandomOffset(fallback - m_activePlayer.position); - if (!m_fastCPU && + if (/*!m_fastCPU &&*/ m_state == State::Inactive) { m_puttFromTee = puttFromTee; @@ -425,8 +425,8 @@ void CPUGolfer::setPuttingPower(float power) std::size_t CPUGolfer::getSkillIndex() const { - //std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; - //return std::clamp((static_cast(m_skillIndex) - offset), 0, 5); + /*std::int32_t offset = ((m_activePlayer.player + 2) % 3) * 2; + return std::clamp((static_cast(m_skillIndex) - offset), 0, 5);*/ auto id = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; return std::clamp((static_cast(CPUStats.size()) - id) / 5, 0, 5); @@ -986,9 +986,9 @@ void CPUGolfer::updatePrediction(float dt) if (m_activePlayer.terrain == TerrainID::Green) { //see if the flag indicator has a better suggestion :) - //m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast(getSkillIndex()) * 0.01f))); - auto cpuID = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; - m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast((CPUStats.size() - cpuID) / 2) * 0.01f))); + m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast(getSkillIndex()) * 0.01f))); + //auto cpuID = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; + //m_targetPower = std::min(m_targetPower, m_puttingPower * (1.f + (static_cast((CPUStats.size() - cpuID) / 2) * 0.01f))); } m_targetAccuracy -= (Deviance[devianceOffset] * 0.05f) * devianceMultiplier; @@ -1117,44 +1117,53 @@ void CPUGolfer::stroke(float dt) } else { - if (!m_inputParser.inProgress()) + if (m_fastCPU) { - //start the stroke - sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); - - m_strokeState = StrokeState::Power; - m_prevPower = 0.f; - m_prevAccuracy = -1.f; - - devianceOffset = (devianceOffset + 1 + m_activePlayer.player) % Deviance.size(); - //TODO this actually happens twice because of the delay of the - //event queue, how ever doesn't appear to be a problem as long - //as the 'double-tap' prevention works + //just apply our values and raise a stroke message + m_inputParser.doFastStroke(m_targetAccuracy, m_targetPower); + m_state = State::Watching; } else { - if (m_strokeState == StrokeState::Power) + if (!m_inputParser.inProgress()) { - auto power = m_inputParser.getPower(); - if ((m_targetPower - power) < 0.02f - && m_prevPower < power) - { - sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); - m_strokeState = StrokeState::Accuracy; - } - m_prevPower = power; + //start the stroke + sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); + + m_strokeState = StrokeState::Power; + m_prevPower = 0.f; + m_prevAccuracy = -1.f; + + devianceOffset = (devianceOffset + 1 + m_activePlayer.player) % Deviance.size(); + //TODO this actually happens twice because of the delay of the + //event queue, how ever doesn't appear to be a problem as long + //as the 'double-tap' prevention works } else { - auto accuracy = m_inputParser.getHook(); - if (accuracy < m_targetAccuracy - && m_prevAccuracy > accuracy) + if (m_strokeState == StrokeState::Power) { - sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); - m_state = State::Watching; - return; + auto power = m_inputParser.getPower(); + if ((m_targetPower - power) < 0.02f + && m_prevPower < power) + { + sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); + m_strokeState = StrokeState::Accuracy; + } + m_prevPower = power; + } + else + { + auto accuracy = m_inputParser.getHook(); + if (accuracy < m_targetAccuracy + && m_prevAccuracy > accuracy) + { + sendKeystroke(m_inputParser.getInputBinding().keys[InputBinding::Action]); + m_state = State::Watching; + return; + } + m_prevAccuracy = accuracy; } - m_prevAccuracy = accuracy; } } } @@ -1167,27 +1176,29 @@ void CPUGolfer::calcAccuracy() m_targetAccuracy = 0.08f; //old ver - //if (m_skills[getSkillIndex()].strokeAccuracy != 0) - //{ - // m_targetAccuracy += static_cast(-m_skills[getSkillIndex()].strokeAccuracy) / 100.f; - //} + if (m_skills[getSkillIndex()].strokeAccuracy != 0) + { + m_targetAccuracy += static_cast(-m_skills[getSkillIndex()].strokeAccuracy) / 100.f; + } - const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; - m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 + //new ver + //const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; + //m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 //occasionally make really inaccurate //... or maybe even perfect? :) //old version - //if (m_skills[getSkillIndex()].mistakeOdds != 0) - //{ - // if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) - // { - // m_targetAccuracy += static_cast(cro::Util::Random::value(-16, 16)) / 100.f; - // } - //} - - std::int32_t puttingOdds = 0; + if (m_skills[getSkillIndex()].mistakeOdds != 0) + { + if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) + { + m_targetAccuracy += static_cast(cro::Util::Random::value(-16, 16)) / 100.f; + } + } + + //new version + /*std::int32_t puttingOdds = 0; if (m_activePlayer.terrain == TerrainID::Green) { float odds = std::min(1.f, glm::length(m_target - m_activePlayer.position) / 12.f) * 2.f; @@ -1197,32 +1208,33 @@ void CPUGolfer::calcAccuracy() if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood] + puttingOdds) { m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) / 100.f; - } + }*/ //to prevent multiple players making the same decision offset the accuracy a small amount //based on their client and player number //old version - //auto offset = (m_offsetRotation % 4) * 10; - //m_targetAccuracy += (static_cast(cro::Util::Random::value(-(offset / 2), (offset / 2) + 1)) / 500.f) * getOffsetValue(); + auto offset = (m_offsetRotation % 4) * 10; + m_targetAccuracy += (static_cast(cro::Util::Random::value(-(offset / 2), (offset / 2) + 1)) / 500.f) * getOffsetValue(); if (m_clubID != ClubID::Putter) { m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); - /*if (m_skills[getSkillIndex()].mistakeOdds != 0) + if (m_skills[getSkillIndex()].mistakeOdds != 0) { if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) { m_targetPower += static_cast(cro::Util::Random::value(-6, 6)) / 1000.f; } - }*/ + } - if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood]) + //new ver + /*if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood]) { m_targetPower += cstat::getOffset(cstat::PowerOffsets, Stat[CPUStat::PowerAccuracy]) * 0.00002f; - } + }*/ m_targetPower = std::min(1.f, m_targetPower); } @@ -1240,7 +1252,7 @@ float CPUGolfer::getOffsetValue() const float multiplier = m_activePlayer.terrain == TerrainID::Green ? smoothstep(0.2f, 0.95f, glm::length(m_target - m_activePlayer.position) / Clubs[ClubID::Putter].getTarget(m_distanceToPin)) : 1.f; return static_cast(1 - ((m_offsetRotation % 2) * 2)) - //* static_cast((m_offsetRotation % (m_skills.size() - getSkillIndex()))) + * static_cast((m_offsetRotation % (m_skills.size() - getSkillIndex()))) * multiplier; } @@ -1269,21 +1281,23 @@ void CPUGolfer::sendKeystroke(std::int32_t key, bool autoRelease) glm::vec3 CPUGolfer::getRandomOffset(glm::vec3 baseDir) const { - auto indexOffset = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; + return glm::vec3(0.f); + + //auto indexOffset = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; - const auto baseLength = glm::length(baseDir); - auto normDir = baseDir / baseLength; + //const auto baseLength = glm::length(baseDir); + //auto normDir = baseDir / baseLength; - const auto& cpuStat = CPUStats[indexOffset]; - const float maxDist = Clubs[ClubID::Driver].getTargetAtLevel(cpuStat[CPUStat::Skill]); - const float resultMultiplier = std::max(1.f, baseLength / maxDist); + //const auto& cpuStat = CPUStats[indexOffset]; + //const float maxDist = Clubs[ClubID::Driver].getTargetAtLevel(cpuStat[CPUStat::Skill]); + //const float resultMultiplier = std::max(1.f, baseLength / maxDist); - const float powerOffset = cstat::getOffset(cstat::PowerOffsets, cpuStat[CPUStat::PowerAccuracy]); - auto retVal = normDir * powerOffset * (resultMultiplier * resultMultiplier); + //const float powerOffset = cstat::getOffset(cstat::PowerOffsets, cpuStat[CPUStat::PowerAccuracy]); + //auto retVal = normDir * powerOffset * (resultMultiplier * resultMultiplier); - normDir = { -normDir.z, normDir.y, normDir.x }; - const float accuracyOffset = cstat::getOffset(cstat::AccuracyOffsets, cpuStat[CPUStat::StrokeAccuracy]); - retVal += normDir * accuracyOffset * resultMultiplier; + //normDir = { -normDir.z, normDir.y, normDir.x }; + //const float accuracyOffset = cstat::getOffset(cstat::AccuracyOffsets, cpuStat[CPUStat::StrokeAccuracy]); + //retVal += normDir * accuracyOffset * resultMultiplier; - return retVal * (static_cast(indexOffset) / CPUStats.size()) * 0.05f; + //return retVal * (static_cast(indexOffset) / CPUStats.size()) * 0.05f; } \ No newline at end of file diff --git a/samples/golf/src/golf/CPUGolfer.hpp b/samples/golf/src/golf/CPUGolfer.hpp index 2d5c1b67d..e9abea182 100644 --- a/samples/golf/src/golf/CPUGolfer.hpp +++ b/samples/golf/src/golf/CPUGolfer.hpp @@ -51,7 +51,7 @@ class CPUGolfer final : public cro::GuiClient { public: - CPUGolfer(const InputParser&, const ActivePlayer&, const CollisionMesh&); + CPUGolfer(InputParser&, const ActivePlayer&, const CollisionMesh&); void handleMessage(const cro::Message&); void activate(glm::vec3 target, glm::vec3 fallbackTarget, bool puttFromTee); @@ -67,7 +67,7 @@ class CPUGolfer final : public cro::GuiClient void setCPUCount(std::int32_t, const struct SharedStateData&); private: - const InputParser& m_inputParser; //only reads the state - actual commands are send by raising events. + InputParser& m_inputParser; const ActivePlayer& m_activePlayer; const CollisionMesh& m_collisionMesh; bool m_fastCPU; diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 06bff504e..5deff1211 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -898,6 +898,16 @@ void GolfState::handleMessage(const cro::Message& msg) cro::GameController::rumbleStart(activeControllerID(m_sharedData.inputBinding.playerID), strLow, strHigh, 200); } + //auto skip if fast CPU is on + if (m_sharedData.fastCPU && isCPU) + { + if (m_currentPlayer.client == m_sharedData.localConnectionData.connectionID) + { + m_sharedData.clientConnection.netClient.sendPacket(PacketID::SkipTurn, m_sharedData.localConnectionData.connectionID, net::NetFlag::Reliable); + m_skipState.wasSkipped = true; + } + } + //check if we hooked/sliced if (auto club = getClub(); club != ClubID::Putter) { @@ -6661,10 +6671,10 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) //if we're using fast CPU we need to pre-increment stroke so //that the UI displays correct score while waiting for the //arbiter score from the server. - if (isCPU && m_sharedData.fastCPU) + /*if (isCPU && m_sharedData.fastCPU) { m_sharedData.connectionData[m_currentPlayer.client].playerData[m_currentPlayer.player].holeScores[m_currentHole]++; - } + }*/ //this is just so that the particle director knows if we're on a new hole diff --git a/samples/golf/src/golf/InputParser.cpp b/samples/golf/src/golf/InputParser.cpp index 673500783..71700cbd7 100644 --- a/samples/golf/src/golf/InputParser.cpp +++ b/samples/golf/src/golf/InputParser.cpp @@ -703,6 +703,18 @@ InputParser::StrokeResult InputParser::getStroke(std::int32_t club, std::int32_t return { impulse, spin, hook }; } +void InputParser::doFastStroke(float accuracy, float power) +{ + m_hook = (accuracy + 1.f) / 2.f; + m_power = cro::Util::Easing::easeOutSine(power); //gets eased in on getPower(); + + m_powerbarDirection = 1.f; + m_state = State::Flight; + + auto* msg = cro::App::postMessage(MessageID::GolfMessage); + msg->type = GolfEvent::HitBall; +} + //private void InputParser::updateDistanceEstimation() { @@ -851,6 +863,7 @@ void InputParser::updateStroke(float dt) //move level to 1 and back (returning to 0 is a fluff) float speed = dt * 0.7f; + if (m_terrain == TerrainID::Green && m_sharedData.showPuttingPower) { diff --git a/samples/golf/src/golf/InputParser.hpp b/samples/golf/src/golf/InputParser.hpp index dc7c5d304..e88749186 100644 --- a/samples/golf/src/golf/InputParser.hpp +++ b/samples/golf/src/golf/InputParser.hpp @@ -100,6 +100,9 @@ class InputParser final static constexpr std::uint32_t CPU_ID = 1337u; + void doFastStroke(float hook, float power); //performed by 'fast' CPU + + private: const SharedStateData& m_sharedData; const InputBinding& m_inputBinding; diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index b6f47ceb6..f864a9a8d 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -263,7 +263,7 @@ void GolfState::netEvent(const net::NetEvent& evt) default: break; case PacketID::NewPlayer: //checks if player is CPU and requires fast move - makeCPUMove(); + //makeCPUMove(); break; case PacketID::SkipTurn: skipCurrentTurn(evt.packet.as()); @@ -311,7 +311,7 @@ void GolfState::netEvent(const net::NetEvent& evt) m_sharedData.host.broadcastPacket(PacketID::FastCPU, m_sharedData.fastCPU, net::NetFlag::Reliable, ConstVal::NetChannelReliable); //this checks current player is CPU and launch a move if inactive - makeCPUMove(); + //makeCPUMove(); } break; } From 7024925cc6ea39853aba38325c58c0349361a5ea Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 19 Jun 2023 21:37:41 +0100 Subject: [PATCH 058/233] refine CPU behaviour fix up non-steam build --- libsocial/include/Social.hpp | 9 ++- samples/golf/src/golf/CPUGolfer.cpp | 78 ++++++++++--------- samples/golf/src/golf/ClubhouseState.cpp | 44 +++++------ samples/golf/src/golf/GolfState.cpp | 8 +- .../golf/src/golf/server/ServerGolfState.cpp | 61 ++++++++------- 5 files changed, 109 insertions(+), 91 deletions(-) diff --git a/libsocial/include/Social.hpp b/libsocial/include/Social.hpp index bd37eb91a..66ba8a19d 100644 --- a/libsocial/include/Social.hpp +++ b/libsocial/include/Social.hpp @@ -68,7 +68,7 @@ class Social final enum { SocialMessage = 10000, - StatMessage + StatsMessage }; }; @@ -76,8 +76,12 @@ class Social final { enum { - StatsReceived + StatsReceived, + HOFReceived }type = StatsReceived; + std::int32_t index = -1; + std::int32_t page = -1; + std::int32_t holeCount = -1; }; struct SocialEvent final @@ -136,6 +140,7 @@ class Social final static constexpr std::uint32_t IconSize = 64; static inline const std::string RSSFeed = "https://fallahn.itch.io/super-video-golf/devlog.rss"; static inline const std::string WebURL = "https://fallahn.itch.io/super-video-golf"; + static void updateHallOfFame() {} enum class UnlockType { diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index f745fdb7f..88250d69a 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -436,41 +436,44 @@ void CPUGolfer::setCPUCount(std::int32_t cpuCount, const SharedStateData& shared { std::fill(m_cpuProfileIndices.begin(), m_cpuProfileIndices.end(), -1); - std::int32_t baseCPUIndex = 0; - std::int32_t stride = 1; - - switch (Club::getClubLevel()) + if (cpuCount) { - default: break; - case 0: - baseCPUIndex = 12; - stride = 16 / cpuCount; //even distribution through 16x level 0 - break; - case 1: - baseCPUIndex = 8; - stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 - break; - case 2: - stride = cpuCount < 3 ? 1 : - cpuCount < 8 ? 2 : - 27 / cpuCount; - break; - } + std::int32_t baseCPUIndex = 0; + std::int32_t stride = 1; + switch (Club::getClubLevel()) + { + default: break; + case 0: + baseCPUIndex = 12; + stride = 16 / cpuCount; //even distribution through 16x level 0 + break; + case 1: + baseCPUIndex = 8; + stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 + break; + case 2: + stride = cpuCount < 3 ? 1 : + cpuCount < 8 ? 2 : + 27 / cpuCount; + break; + } - CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); - for (auto i = 0u; i < sharedData.connectionData.size(); ++i) - { - for (auto j = 0u; j < sharedData.connectionData[i].playerCount; ++j) + CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); + + for (auto i = 0u; i < sharedData.connectionData.size(); ++i) { - if (sharedData.connectionData[i].playerData[j].isCPU) + for (auto j = 0u; j < sharedData.connectionData[i].playerCount; ++j) { - CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); + if (sharedData.connectionData[i].playerData[j].isCPU) + { + CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); - auto cpuIndex = i * ConstVal::MaxPlayers + j; - m_cpuProfileIndices[cpuIndex] = baseCPUIndex; - baseCPUIndex += stride; + auto cpuIndex = i * ConstVal::MaxPlayers + j; + m_cpuProfileIndices[cpuIndex] = baseCPUIndex; + baseCPUIndex += stride; + } } } } @@ -1182,9 +1185,11 @@ void CPUGolfer::calcAccuracy() } //new ver - //const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; - //m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 - + const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; + if (m_puttFromTee) + { + m_targetAccuracy += cstat::getOffset(cstat::AccuracyOffsets, Stat[CPUStat::StrokeAccuracy]) * 0.00375f; //scales max value to 0.06 + } //occasionally make really inaccurate //... or maybe even perfect? :) @@ -1222,19 +1227,20 @@ void CPUGolfer::calcAccuracy() { m_targetPower = std::min(1.f, m_targetPower + (1 - (cro::Util::Random::value(0, 1) * 2)) * (static_cast(m_offsetRotation % 4) / 50.f)); - if (m_skills[getSkillIndex()].mistakeOdds != 0) + //old ver + /*if (m_skills[getSkillIndex()].mistakeOdds != 0) { if (cro::Util::Random::value(0, m_skills[getSkillIndex()].mistakeOdds) == 0) { m_targetPower += static_cast(cro::Util::Random::value(-6, 6)) / 1000.f; } - } + }*/ //new ver - /*if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood]) + if (cro::Util::Random::value(0, 9) < Stat[CPUStat::MistakeLikelyhood]) { - m_targetPower += cstat::getOffset(cstat::PowerOffsets, Stat[CPUStat::PowerAccuracy]) * 0.00002f; - }*/ + m_targetPower += cstat::getOffset(cstat::PowerOffsets, Stat[CPUStat::PowerAccuracy]) * 0.00001f; + } m_targetPower = std::min(1.f, m_targetPower); } diff --git a/samples/golf/src/golf/ClubhouseState.cpp b/samples/golf/src/golf/ClubhouseState.cpp index 9595f049a..9395ce655 100644 --- a/samples/golf/src/golf/ClubhouseState.cpp +++ b/samples/golf/src/golf/ClubhouseState.cpp @@ -283,28 +283,28 @@ ClubhouseState::ClubhouseState(cro::StateStack& ss, cro::State::Context ctx, Sha Social::setGroup(0); #ifdef CRO_DEBUG_ - registerWindow([&]() - { - if (ImGui::Begin("Buns")) - { - ImGui::Text("Index %u", m_arcadeIndex); - //float maxDepth = m_backgroundScene.getActiveCamera().getComponent().getMaxShadowDistance(); - //if (ImGui::SliderFloat("Depth", &maxDepth, 1.f, 30.f)) - //{ - // m_backgroundScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDepth); - //} - - //float ext = m_backgroundScene.getActiveCamera().getComponent().getShadowExpansion(); - //if (ImGui::SliderFloat("ext", &ext, 0.f, 30.f)) - //{ - // m_backgroundScene.getActiveCamera().getComponent().setShadowExpansion(ext); - //} - - //auto rot = m_backgroundScene.getSunlight().getComponent().getRotation(); - //ImGui::Text("%3.3f, %3.3f, %3.3f, %3.3f", rot.x, rot.y, rot.z, rot.w); - } - ImGui::End(); - }); + //registerWindow([&]() + // { + // if (ImGui::Begin("Buns")) + // { + // //ImGui::Text("Index %u", m_arcadeIndexJoy); + // //float maxDepth = m_backgroundScene.getActiveCamera().getComponent().getMaxShadowDistance(); + // //if (ImGui::SliderFloat("Depth", &maxDepth, 1.f, 30.f)) + // //{ + // // m_backgroundScene.getActiveCamera().getComponent().setMaxShadowDistance(maxDepth); + // //} + + // //float ext = m_backgroundScene.getActiveCamera().getComponent().getShadowExpansion(); + // //if (ImGui::SliderFloat("ext", &ext, 0.f, 30.f)) + // //{ + // // m_backgroundScene.getActiveCamera().getComponent().setShadowExpansion(ext); + // //} + + // //auto rot = m_backgroundScene.getSunlight().getComponent().getRotation(); + // //ImGui::Text("%3.3f, %3.3f, %3.3f, %3.3f", rot.x, rot.y, rot.z, rot.w); + // } + // ImGui::End(); + // }); #endif } diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 5deff1211..0b88dbc52 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -678,7 +678,10 @@ bool GolfState::handleEvent(const cro::Event& evt) } else if (evt.type == SDL_KEYDOWN) { - resetIdle(); + if (evt.key.keysym.sym != SDLK_F12) //default screenshot key + { + resetIdle(); + } m_skipState.displayControllerMessage = false; switch (evt.key.keysym.sym) @@ -909,7 +912,8 @@ void GolfState::handleMessage(const cro::Message& msg) } //check if we hooked/sliced - if (auto club = getClub(); club != ClubID::Putter) + if (auto club = getClub(); club != ClubID::Putter + && (!isCPU && !m_sharedData.fastCPU)) { //TODO this doesn't include any easing added when making the stroke //we should be using the value returned by getStroke() in hitBall() diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index f864a9a8d..ffa4de706 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -1009,43 +1009,46 @@ void GolfState::initScene() //assign indices into the CPU profile array //based on host's club set and CPU count - std::int32_t baseCPUIndex = 0; - std::int32_t stride = 1; - - switch (Club::getClubLevel()) + if (cpuCount) { - default: break; - case 0: - baseCPUIndex = 12; - stride = 16 / cpuCount; //even distribution through 16x level 0 - break; - case 1: - baseCPUIndex = 8; - stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 - break; - case 2: - stride = cpuCount < 3 ? 1 : - cpuCount < 8 ? 2 : - 27 / cpuCount; - break; - } + std::int32_t baseCPUIndex = 0; + std::int32_t stride = 1; + + switch (Club::getClubLevel()) + { + default: break; + case 0: + baseCPUIndex = 12; + stride = 16 / cpuCount; //even distribution through 16x level 0 + break; + case 1: + baseCPUIndex = 8; + stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 + break; + case 2: + stride = cpuCount < 3 ? 1 : + cpuCount < 8 ? 2 : + 27 / cpuCount; + break; + } - CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); + CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); - for (auto i = 0u; i < m_sharedData.clients.size(); ++i) - { - if (m_sharedData.clients[i].connected) + for (auto i = 0u; i < m_sharedData.clients.size(); ++i) { - for (auto j = 0u; j < m_sharedData.clients[i].playerCount; ++j) + if (m_sharedData.clients[i].connected) { - if (m_sharedData.clients[i].playerData[j].isCPU) + for (auto j = 0u; j < m_sharedData.clients[i].playerCount; ++j) { - CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); + if (m_sharedData.clients[i].playerData[j].isCPU) + { + CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); - auto cpuIndex = i * ConstVal::MaxPlayers + j; - m_cpuProfileIndices[cpuIndex] = baseCPUIndex; - baseCPUIndex += stride; + auto cpuIndex = i * ConstVal::MaxPlayers + j; + m_cpuProfileIndices[cpuIndex] = baseCPUIndex; + baseCPUIndex += stride; + } } } } From 48bb697217e3038027f9e197042037bc10d80765 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 20 Jun 2023 14:07:31 +0100 Subject: [PATCH 059/233] shout at the client until it behaves. --- samples/golf/src/GolfGame.cpp | 4 +-- samples/golf/src/golf/BallSystem.cpp | 5 +++ .../golf/src/golf/server/ServerGolfState.cpp | 36 ++++++++++++++++--- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 53989814a..c7899644f 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -717,9 +717,9 @@ bool GolfGame::initialise() #ifdef CRO_DEBUG_ //m_stateStack.pushState(StateID::DrivingRange); //can't go straight to this because menu needs to parse avatar data //m_stateStack.pushState(StateID::Bush); - m_stateStack.pushState(StateID::Clubhouse); + //m_stateStack.pushState(StateID::Clubhouse); //m_stateStack.pushState(StateID::SplashScreen); - //m_stateStack.pushState(StateID::Menu); + m_stateStack.pushState(StateID::Menu); //m_stateStack.pushState(StateID::SQLite); #else m_stateStack.pushState(StateID::SplashScreen); diff --git a/samples/golf/src/golf/BallSystem.cpp b/samples/golf/src/golf/BallSystem.cpp index 6ddb89c75..80d119c57 100644 --- a/samples/golf/src/golf/BallSystem.cpp +++ b/samples/golf/src/golf/BallSystem.cpp @@ -204,6 +204,11 @@ bool BallSystem::setHoleData(HoleData& holeData, bool rebuildMesh) auto result = rebuildMesh ? updateCollisionMesh(holeData.modelPath) : true; holeData.pin.y = getTerrain(holeData.pin).intersection.y; + for (auto entity : getEntities()) + { + entity.getComponent().setPosition(holeData.tee); + } + return result; } diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index ffa4de706..377e41814 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -707,7 +707,7 @@ void GolfState::setNextHole() } //reset player positions/strokes - for (auto& player : m_playerInfo) + /*for (auto& player : m_playerInfo) { player.position = m_holeData[m_currentHole].tee; player.distanceToHole = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); @@ -716,19 +716,21 @@ void GolfState::setNextHole() auto ball = player.ballEntity; ball.getComponent().terrain = player.terrain; ball.getComponent().velocity = glm::vec3(0.f); - ball.getComponent().setPosition(player.position); + ball.getComponent().setPosition(m_holeData[m_currentHole].tee); auto timestamp = m_serverTime.elapsed().asMilliseconds(); ActorInfo info; info.serverID = static_cast(ball.getIndex()); info.position = ball.getComponent().getPosition(); + info.rotation = cro::Util::Net::compressQuat(ball.getComponent().getRotation()); + info.windEffect = ball.getComponent().windEffect; info.timestamp = timestamp; info.clientID = player.client; info.playerID = player.player; info.state = static_cast(ball.getComponent().state); m_sharedData.host.broadcastPacket(PacketID::ActorUpdate, info, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - } + }*/ //tell clients to set up next hole std::uint16_t newHole = (m_currentHole << 8) | std::uint8_t(m_holeData[m_currentHole].par); @@ -740,7 +742,7 @@ void GolfState::setNextHole() entity.addComponent(); entity.addComponent().active = true; entity.getComponent().function = - [&](cro::Entity e, float dt) + [&, ballSystem](cro::Entity e, float dt) { if (m_allMapsLoaded) { @@ -756,6 +758,32 @@ void GolfState::setNextHole() else { m_scoreboardTime = 0.f; + + //apparently we have to do this here else the client just ignores it + for (auto& player : m_playerInfo) + { + player.position = m_holeData[m_currentHole].tee; + player.distanceToHole = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); + player.terrain = ballSystem->getTerrain(player.position).terrain; + + auto ball = player.ballEntity; + ball.getComponent().terrain = player.terrain; + ball.getComponent().velocity = glm::vec3(0.f); + ball.getComponent().setPosition(m_holeData[m_currentHole].tee); + + auto timestamp = m_serverTime.elapsed().asMilliseconds(); + + ActorInfo info; + info.serverID = static_cast(ball.getIndex()); + info.position = ball.getComponent().getPosition(); + info.rotation = cro::Util::Net::compressQuat(ball.getComponent().getRotation()); + info.windEffect = ball.getComponent().windEffect; + info.timestamp = timestamp; + info.clientID = player.client; + info.playerID = player.player; + info.state = static_cast(ball.getComponent().state); + m_sharedData.host.broadcastPacket(PacketID::ActorUpdate, info, net::NetFlag::Reliable, ConstVal::NetChannelReliable); + } } //make sure to keep resetting this to prevent unfairly From c1f279bae77f12b0c2e79f56473008690a48f98f Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 20 Jun 2023 20:39:57 +0100 Subject: [PATCH 060/233] tweak CPU behaviour --- crogine.sln | 4 ++-- samples/golf/src/golf/CPUGolfer.cpp | 27 ++++++++++++++++++++++++--- samples/golf/src/golf/InputParser.cpp | 11 ++++++++++- samples/scratchpad/src/MenuState.cpp | 22 ++++++++++++++++++++++ samples/scratchpad/src/MyApp.cpp | 4 ++-- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/crogine.sln b/crogine.sln index 8cf326883..36c317be4 100644 --- a/crogine.sln +++ b/crogine.sln @@ -268,6 +268,7 @@ Global {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM64.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.ActiveCfg = Debug|x64 + {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.Build.0 = Debug|x64 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x86.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|ARM.ActiveCfg = DebugASan|Win32 @@ -289,8 +290,7 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|Any CPU.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM64.ActiveCfg = Debug|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.ActiveCfg = DebugGNS|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.Build.0 = DebugGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.ActiveCfg = Debug|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.Build.0 = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 88250d69a..17ffc3cfe 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -207,12 +207,12 @@ void CPUGolfer::handleMessage(const cro::Message& msg) void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) { - //target += getRandomOffset(target - m_activePlayer.position); - //fallback += getRandomOffset(fallback - m_activePlayer.position); if (/*!m_fastCPU &&*/ m_state == State::Inactive) { + const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; + m_puttFromTee = puttFromTee; m_fallbackTarget = fallback; m_baseTarget = m_target = target; @@ -258,7 +258,28 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) case TerrainID::Water: case TerrainID::Scrub: case TerrainID::Stone: - m_baseTarget = m_target = fallback; + if (cro::Util::Random::value(0, 9) > Stat[CPUStat::MistakeLikelyhood]) + { + m_baseTarget = m_target = fallback; + } + break; + } + } + else + { + //just check the target is in bounds + const auto terrain = m_collisionMesh.getTerrain(m_target); + switch (terrain.terrain) + { + default: + break; + case TerrainID::Water: + case TerrainID::Scrub: + case TerrainID::Stone: + if (cro::Util::Random::value(0, 9) > Stat[CPUStat::MistakeLikelyhood]) + { + m_baseTarget = m_target = fallback; + } break; } } diff --git a/samples/golf/src/golf/InputParser.cpp b/samples/golf/src/golf/InputParser.cpp index 71700cbd7..1671990fc 100644 --- a/samples/golf/src/golf/InputParser.cpp +++ b/samples/golf/src/golf/InputParser.cpp @@ -705,8 +705,17 @@ InputParser::StrokeResult InputParser::getStroke(std::int32_t club, std::int32_t void InputParser::doFastStroke(float accuracy, float power) { + //this ONLY works with the CPU because it's been estimating + //the suppplied power value based on getPower() which has been + //appplying easeInSine() - and we need to invert that to get the + //base power value again... + const auto invSine = [](float x) + { + return (std::acos(1.f - x) / cro::Util::Const::PI) * 2.f; + }; + m_hook = (accuracy + 1.f) / 2.f; - m_power = cro::Util::Easing::easeOutSine(power); //gets eased in on getPower(); + m_power = invSine(power); //gets eased in on getPower(); m_powerbarDirection = 1.f; m_state = State::Flight; diff --git a/samples/scratchpad/src/MenuState.cpp b/samples/scratchpad/src/MenuState.cpp index b0a5e7f3f..ba97d3d28 100644 --- a/samples/scratchpad/src/MenuState.cpp +++ b/samples/scratchpad/src/MenuState.cpp @@ -45,6 +45,8 @@ source distribution. #include #include +#include + #include #include @@ -97,6 +99,26 @@ MenuState::MenuState(cro::StateStack& stack, cro::State::Context context, MyApp& //create ImGui stuff createUI(); }); + + float buns = 0.1f; + for (auto i = 0; i < 9; ++i) + { + LogI << "buns " << buns << std::endl; + + auto bunsIn = cro::Util::Easing::easeInSine(buns); + LogI << "buns in " << bunsIn << std::endl; + + //1.f - std::cos((t * Const::PI) / 2.f); + + auto invSine = [](float x) + { + return (std::acos(1.f - x) / cro::Util::Const::PI) * 2.f; + }; + LogI << "buns out " << invSine(bunsIn) << std::endl; + + buns += 0.1f; + } + } //public diff --git a/samples/scratchpad/src/MyApp.cpp b/samples/scratchpad/src/MyApp.cpp index fa5441669..4f65f8608 100644 --- a/samples/scratchpad/src/MyApp.cpp +++ b/samples/scratchpad/src/MyApp.cpp @@ -164,8 +164,8 @@ bool MyApp::initialise() m_stateStack.registerState(States::ScratchPad::Log); #ifdef CRO_DEBUG_ - m_stateStack.pushState(States::ScratchPad::Log); - //m_stateStack.pushState(States::ScratchPad::MainMenu); + //m_stateStack.pushState(States::ScratchPad::Log); + m_stateStack.pushState(States::ScratchPad::MainMenu); #else m_stateStack.pushState(States::ScratchPad::MainMenu); #endif From 8ef98dd2222bd5c36d55313258cb19e3568d1deb Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 21 Jun 2023 11:18:11 +0100 Subject: [PATCH 061/233] hide CPU power bar when local play is above level 10 --- samples/golf/src/golf/GolfState.cpp | 7 +- .../golf/src/golf/server/ServerGolfRules.cpp | 889 +++++++++--------- .../golf/src/golf/server/ServerGolfState.hpp | 4 +- 3 files changed, 451 insertions(+), 449 deletions(-) diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 0b88dbc52..4e7d7053e 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -6420,9 +6420,12 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) //show ui if this is our client cmd.targetFlags = CommandID::UI::Root; - cmd.action = [&,localPlayer](cro::Entity e, float) + cmd.action = [&,localPlayer, isCPU](cro::Entity e, float) { - e.getComponent().getUserData>().first = localPlayer ? 0 : 1; + //only show CPU power to beginners + std::int32_t show = localPlayer && (!isCPU || Social::getLevel() < 10) ? 0 : 1; + + e.getComponent().getUserData>().first = show; e.getComponent().active = true; }; m_uiScene.getSystem()->sendCommand(cmd); diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index b1de8e247..71f576bd5 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -14,454 +14,453 @@ namespace { - glm::vec3 randomNormal() - { - glm::vec2 v( - static_cast(cro::Util::Random::value(1, 10)) / 10.f, - static_cast(cro::Util::Random::value(1, 10)) / 10.f); - - v.x *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; - v.y *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; - v = glm::normalize(v); - - CRO_ASSERT(!std::isnan(v.x), ""); - return { v.x, 0.f, -v.y }; - //return { 1.f, 0.f, 0.f }; - } - - std::int32_t getClub(float dist) - { - if (dist > 115.f) //forces a cut-off for pitch n putt - { - dist = 1000.f; - } - - std::int32_t clubID = ClubID::SandWedge; - while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist - && clubID != ClubID::Driver) - { - clubID--; - } - return clubID; - } + //glm::vec3 randomNormal() + //{ + // glm::vec2 v( + // static_cast(cro::Util::Random::value(1, 10)) / 10.f, + // static_cast(cro::Util::Random::value(1, 10)) / 10.f); + + // v.x *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; + // v.y *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; + // v = glm::normalize(v); + + // CRO_ASSERT(!std::isnan(v.x), ""); + // return { v.x, 0.f, -v.y }; + //} + + //std::int32_t getClub(float dist) + //{ + // if (dist > 115.f) //forces a cut-off for pitch n putt + // { + // dist = 1000.f; + // } + + // std::int32_t clubID = ClubID::SandWedge; + // while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist + // && clubID != ClubID::Driver) + // { + // clubID--; + // } + // return clubID; + //} } using namespace sv; -using namespace cstat; -void GolfState::makeCPUMove() -{ - if(m_sharedData.fastCPU - && m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].isCPU) - { - auto& ball = m_playerInfo[0].ballEntity.getComponent(); - if (ball.state == Ball::State::Idle) - { - //wrap in an ent so we can add a small delay - auto entity = m_scene.createEntity(); - entity.addComponent().active = true; - entity.getComponent().setUserData(1.2f); - entity.getComponent().function = - [&](cro::Entity e, float dt) - { - auto& currTime = e.getComponent().getUserData(); - currTime -= dt; - - if (currTime < 0) - { - auto& ball = m_playerInfo[0].ballEntity.getComponent(); - auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; - - //this is just separated out so we can swap calcs more easily - auto pos = calcCPUPosition(); - - - //test terrain height and correct final position - auto result = m_scene.getSystem()->getTerrain(pos); - - //technically this means CPU players never make really bad shots - //but otherwise they just get stuck in a loop - switch (result.terrain) - { - case TerrainID::Water: - //use mistake odds to occasionally hit the water - { - const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; - if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) - { - break; - } - } - [[fallthrough]]; - case TerrainID::Stone: - case TerrainID::Scrub: - { - /*std::int32_t tries = 300; - auto dir = glm::normalize(pos - m_playerInfo[0].position); - do - { - pos -= dir; - result = m_scene.getSystem()->getTerrain(pos); - } while (tries-- - && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) - && glm::length2(pos) > 1);*/ - } - break; - default: break; - } - - pos.y = result.intersection.y; - - CRO_ASSERT(!std::isnan(pos.x), ""); - CRO_ASSERT(!std::isnan(pos.y), ""); - CRO_ASSERT(!std::isnan(pos.z), ""); - - m_playerInfo[0].ballEntity.getComponent().setPosition(pos); - m_playerInfo[0].holeScore[m_currentHole]++; - - //TODO this case should never happen... - auto velOffset = pos - m_playerInfo[0].position; - if (glm::length2(velOffset) == 0) - { - velOffset.x = 0.0001f; - } - velOffset = glm::normalize(velOffset) * 0.001f; - - //const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; - - //LogI << velOffset << std::endl; - ball.terrain = result.terrain; - switch (result.terrain) - { - default: - ball.state = Ball::State::Paused; - break; - case TerrainID::Bunker: - case TerrainID::Rough: - animID = AnimationID::Disappoint; - [[fallthrough]]; - case TerrainID::Fairway: - ball.state = Ball::State::Flight; - ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem - break; - case TerrainID::Green: - case TerrainID::Hole: - ball.state = Ball::State::Putt; - ball.velocity = velOffset; - break; - case TerrainID::Scrub: - case TerrainID::Stone: - case TerrainID::Water: - ball.state = Ball::State::Reset; - animID = AnimationID::Disappoint; - break; - } - - m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); - - - - m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - e.getComponent().active = false; - m_scene.destroyEntity(e); - } - }; - - m_sharedData.host.broadcastPacket(PacketID::CPUThink, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - } - } -} - -glm::vec3 GolfState::calcCPUPosition() const -{ - const auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; - const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; - auto pos = m_holeData[m_currentHole].pin; //always prefer the pin as the target unless blocked for some reason - - const auto puttFromTee = m_scene.getSystem()->getPuttFromTee(); - - const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; - CRO_ASSERT(cpuID != -1, ""); - - const std::int32_t skill = CPUStats[cpuID][CPUStat::Skill]; - - auto& ball = m_playerInfo[0].ballEntity.getComponent(); - std::int32_t clubID = ClubID::Putter; - - //get longest range available - if (ball.terrain != TerrainID::Green) - { - if (ball.terrain == TerrainID::Bunker) - { - clubID = ClubID::PitchWedge; - } - else - { - auto dist = glm::length(pinDir); - clubID = getClub(dist); - } - - //check to see if the club range can hit the ball into a valid area, - //by reducing pos to max range - const float clubDist = Clubs[clubID].getTargetAtLevel(skill); - if (auto len2 = glm::length2(pinDir); - len2 > (clubDist * clubDist)) - { - //rather than move back toward the player (which might put us in the rough) - //head towards the target point which in theory should aim us towards the fairway - const auto len = std::sqrt(len2) - clubDist; - const auto correctionDir = glm::normalize(m_holeData[m_currentHole].target - m_holeData[m_currentHole].pin) * len; - pos = m_holeData[m_currentHole].pin + correctionDir; - - //const float reduction = clubDist / std::sqrt(len2); - //pos = (pinDir * reduction) + m_playerInfo[0].position; - } - - auto result = m_scene.getSystem()->getTerrain(pos); - switch (result.terrain) - { - default: break; - case TerrainID::Water: - //use the mistake odds to sometimes accept this target - if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) - { - break; - } - [[fallthrough]]; - case TerrainID::Stone: - case TerrainID::Scrub: - //else use the target point instead of the pin - pos = m_holeData[m_currentHole].target; - break; - } - } - else - { - //else if we're on a mini-putt course see if there's a dog-leg - if (puttFromTee) - { - if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); - dp > 0.4 && dp < 0.98f) //target in front, but not the same dir as pin - { - //don't use if too close - if (glm::length2(targetDir) > (3.f * 3.f) - && glm::length2(pinDir) > glm::length2(targetDir)) - { - pos = m_holeData[m_currentHole].target; - } - } - } - else - { - //regular putting - assume we go in the hole under 20cm - if (glm::length2(pinDir) < (0.2f * 0.2f)) - { - return m_holeData[m_currentHole].pin; - } - } - } - - - const auto playerName = [&]() - { - return m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].name.toAnsiString(); - }; - - - auto stepDir = pos - m_playerInfo[0].position; - const auto totalDist = glm::length(stepDir); - std::int32_t stepCount = std::max(1, static_cast(totalDist)); - - //use smaller steps when putting - if (ball.terrain == TerrainID::Green) - { - stepCount *= 2; - } - stepDir /= stepCount; - - //using the CPU stats calculate some sort of offset from the target. - float windEffect = 0.f; - if (clubID < ClubID::SandWedge) - { - windEffect = getOffset(WindOffsets, CPUStats[cpuID][CPUStat::WindAccuracy]); - windEffect *= Clubs[clubID].getBaseTarget() / Clubs[ClubID::Driver].getBaseTarget(); - - //LogI << "Wind effect " << windEffect << std::endl; - } - auto wind = m_scene.getSystem()->getWindDirection(); - wind = (glm::vec3(wind.x, 0.f, wind.z) * wind.y * windEffect); - wind /= stepCount; - - const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTargetAtLevel(CPUStats[cpuID][CPUStat::Skill])); - auto dirNorm = glm::normalize(stepDir); - - const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); - auto overShootDir = dirNorm * overShoot * (clubMultiplier * clubMultiplier); - overShootDir /= stepCount; - - dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular - const float strokeAccuracy = getOffset(AccuracyOffsets, CPUStats[cpuID][CPUStat::StrokeAccuracy]); - auto accuracyDir = dirNorm * strokeAccuracy * clubMultiplier; - if (puttFromTee) accuracyDir *= 0.5f; - accuracyDir /= stepCount; - - //TODO include offset for rough or bunker terrain - this should probably be another stat - //for how well CPU compensates - - - //calculate mistake odds - increase this with distance when putting - bool perfect = false; - std::int32_t puttingOdds = 0; - if (ball.terrain == TerrainID::Green) - { - float odds = std::pow(std::min(1.f, totalDist / 5.f), 2.f); - - accuracyDir *= (1.f + odds); - odds *= 2.f; - - puttingOdds = static_cast(std::round(odds)); - } - - if (cro::Util::Random::value(0, 14) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) - { - //TODO check these values so we don't accidentally - //undo existing power/accuracy and improve them... - LogI << playerName() << " Made a mistake!" << std::endl; - switch (cro::Util::Random::value(0, 2)) - { - default: - case 0: - LogI << playerName() << " Fluffed power" << std::endl; - overShootDir *= puttFromTee ? 1.3f : 2.56f; - break; - case 1: - LogI << playerName() << " Fluffed accuracy" << std::endl; - accuracyDir *= puttFromTee ? 1.01f : 1.78f; - break; - case 2: - LogI << playerName() << " Fluffed power and accuracy" << std::endl; - accuracyDir *= puttFromTee ? 1.02f : 1.92f; - overShootDir *= puttFromTee ? 1.25f : 1.98f; - break; - } - - if (glm::length2(m_holeData[m_currentHole].pin - m_playerInfo[0].position) > (2.f * 2.f) && - cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) - { - LogI << playerName() << " made uber mistake" << std::endl; - stepDir /= 2.f; - - stepDir += (stepDir * getOffset(MistakeCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy])); - } - } - - //calculate perfection odds only if we didn't make a mistake - else if (cro::Util::Random::value(0, 99) < CPUStats[cpuID][CPUStat::PerfectionLikelyhood]) - { - if (totalDist < 20.f) - { - perfect = cro::Util::Random::value(1, 10) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 10; - } - else if (totalDist < 100.f) - { - perfect = cro::Util::Random::value(1, 50) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 2; - } - else - { - perfect = cro::Util::Random::value(1, 100) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood]; - } - - - - - if(perfect && - totalDist < Clubs[clubID].getBaseTarget()) - { - if (puttFromTee) - { - //check for corner cutting - auto dir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; - dir /= stepCount; - - pos = m_playerInfo[0].position; - - for (auto i = 0; i < stepCount; ++i) - { - pos += dir; - auto terrain = m_scene.getSystem()->getTerrain(pos); - if (terrain.terrain != TerrainID::Green) - { - LogI << "Perfect shot would cut corner" << std::endl; - perfect = false; - break; - } - } - } - - LogI << playerName() << " Got a PERFECT shot" << std::endl; - pos = m_holeData[m_currentHole].pin; - } - else - { - perfect = false; //need to do the calc below if we're not in range - } - } - - CRO_ASSERT(glm::length2(stepDir), ""); - //CRO_ASSERT(glm::length2(wind), ""); - CRO_ASSERT(glm::length2(overShootDir), ""); - CRO_ASSERT(glm::length2(accuracyDir), ""); - CRO_ASSERT(stepCount != 0, ""); - - //if not perfect - if (!perfect) - { - pos = m_playerInfo[0].position; - for (auto i = 0; i < stepCount; ++i) - { - pos += stepDir; - pos += wind; - pos += overShootDir; - pos += accuracyDir; - - //check each step for terrain to see if we - //accidentally cut corners - if (puttFromTee) - { - auto terrain = m_scene.getSystem()->getTerrain(pos); - if (terrain.terrain != TerrainID::Green) - { - break; - } - } - } - - //one final check that we're not toooo close to the hole after - //a long shot else we get too many chip-ins without the 'perfect' requirement - if (glm::length2(pos - m_playerInfo[0].position) > (5.f * 5.f)) - { - if (!puttFromTee || cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) - { - if (auto l2 = glm::length2(pos - m_holeData[m_currentHole].pin); l2 < (0.15f * 0.15f)) - { - auto dir = (pos - m_holeData[m_currentHole].pin) / std::sqrt(l2); - dir *= getOffset(HoleCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy]); - pos += dir; - LogI << playerName() << " Corrected for too much perfection." << std::endl; - } - } - } - - - //if we're really close to the hole plop it in based on stroke accuracy - if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && - cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 10) - { - pos = m_holeData[m_currentHole].pin; - } - } - - return pos; -} +//using namespace cstat; +//void GolfState::makeCPUMove() +//{ +// if(m_sharedData.fastCPU +// && m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].isCPU) +// { +// auto& ball = m_playerInfo[0].ballEntity.getComponent(); +// if (ball.state == Ball::State::Idle) +// { +// //wrap in an ent so we can add a small delay +// auto entity = m_scene.createEntity(); +// entity.addComponent().active = true; +// entity.getComponent().setUserData(1.2f); +// entity.getComponent().function = +// [&](cro::Entity e, float dt) +// { +// auto& currTime = e.getComponent().getUserData(); +// currTime -= dt; +// +// if (currTime < 0) +// { +// auto& ball = m_playerInfo[0].ballEntity.getComponent(); +// auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; +// +// //this is just separated out so we can swap calcs more easily +// auto pos = calcCPUPosition(); +// +// +// //test terrain height and correct final position +// auto result = m_scene.getSystem()->getTerrain(pos); +// +// //technically this means CPU players never make really bad shots +// //but otherwise they just get stuck in a loop +// switch (result.terrain) +// { +// case TerrainID::Water: +// //use mistake odds to occasionally hit the water +// { +// const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; +// if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) +// { +// break; +// } +// } +// [[fallthrough]]; +// case TerrainID::Stone: +// case TerrainID::Scrub: +// { +// /*std::int32_t tries = 300; +// auto dir = glm::normalize(pos - m_playerInfo[0].position); +// do +// { +// pos -= dir; +// result = m_scene.getSystem()->getTerrain(pos); +// } while (tries-- +// && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) +// && glm::length2(pos) > 1);*/ +// } +// break; +// default: break; +// } +// +// pos.y = result.intersection.y; +// +// CRO_ASSERT(!std::isnan(pos.x), ""); +// CRO_ASSERT(!std::isnan(pos.y), ""); +// CRO_ASSERT(!std::isnan(pos.z), ""); +// +// m_playerInfo[0].ballEntity.getComponent().setPosition(pos); +// m_playerInfo[0].holeScore[m_currentHole]++; +// +// //TODO this case should never happen... +// auto velOffset = pos - m_playerInfo[0].position; +// if (glm::length2(velOffset) == 0) +// { +// velOffset.x = 0.0001f; +// } +// velOffset = glm::normalize(velOffset) * 0.001f; +// +// //const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; +// +// //LogI << velOffset << std::endl; +// ball.terrain = result.terrain; +// switch (result.terrain) +// { +// default: +// ball.state = Ball::State::Paused; +// break; +// case TerrainID::Bunker: +// case TerrainID::Rough: +// animID = AnimationID::Disappoint; +// [[fallthrough]]; +// case TerrainID::Fairway: +// ball.state = Ball::State::Flight; +// ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem +// break; +// case TerrainID::Green: +// case TerrainID::Hole: +// ball.state = Ball::State::Putt; +// ball.velocity = velOffset; +// break; +// case TerrainID::Scrub: +// case TerrainID::Stone: +// case TerrainID::Water: +// ball.state = Ball::State::Reset; +// animID = AnimationID::Disappoint; +// break; +// } +// +// m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); +// +// +// +// m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); +// e.getComponent().active = false; +// m_scene.destroyEntity(e); +// } +// }; +// +// m_sharedData.host.broadcastPacket(PacketID::CPUThink, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); +// } +// } +//} +// +//glm::vec3 GolfState::calcCPUPosition() const +//{ +// const auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; +// const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; +// auto pos = m_holeData[m_currentHole].pin; //always prefer the pin as the target unless blocked for some reason +// +// const auto puttFromTee = m_scene.getSystem()->getPuttFromTee(); +// +// const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; +// CRO_ASSERT(cpuID != -1, ""); +// +// const std::int32_t skill = CPUStats[cpuID][CPUStat::Skill]; +// +// auto& ball = m_playerInfo[0].ballEntity.getComponent(); +// std::int32_t clubID = ClubID::Putter; +// +// //get longest range available +// if (ball.terrain != TerrainID::Green) +// { +// if (ball.terrain == TerrainID::Bunker) +// { +// clubID = ClubID::PitchWedge; +// } +// else +// { +// auto dist = glm::length(pinDir); +// clubID = getClub(dist); +// } +// +// //check to see if the club range can hit the ball into a valid area, +// //by reducing pos to max range +// const float clubDist = Clubs[clubID].getTargetAtLevel(skill); +// if (auto len2 = glm::length2(pinDir); +// len2 > (clubDist * clubDist)) +// { +// //rather than move back toward the player (which might put us in the rough) +// //head towards the target point which in theory should aim us towards the fairway +// const auto len = std::sqrt(len2) - clubDist; +// const auto correctionDir = glm::normalize(m_holeData[m_currentHole].target - m_holeData[m_currentHole].pin) * len; +// pos = m_holeData[m_currentHole].pin + correctionDir; +// +// //const float reduction = clubDist / std::sqrt(len2); +// //pos = (pinDir * reduction) + m_playerInfo[0].position; +// } +// +// auto result = m_scene.getSystem()->getTerrain(pos); +// switch (result.terrain) +// { +// default: break; +// case TerrainID::Water: +// //use the mistake odds to sometimes accept this target +// if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) +// { +// break; +// } +// [[fallthrough]]; +// case TerrainID::Stone: +// case TerrainID::Scrub: +// //else use the target point instead of the pin +// pos = m_holeData[m_currentHole].target; +// break; +// } +// } +// else +// { +// //else if we're on a mini-putt course see if there's a dog-leg +// if (puttFromTee) +// { +// if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); +// dp > 0.4 && dp < 0.98f) //target in front, but not the same dir as pin +// { +// //don't use if too close +// if (glm::length2(targetDir) > (3.f * 3.f) +// && glm::length2(pinDir) > glm::length2(targetDir)) +// { +// pos = m_holeData[m_currentHole].target; +// } +// } +// } +// else +// { +// //regular putting - assume we go in the hole under 20cm +// if (glm::length2(pinDir) < (0.2f * 0.2f)) +// { +// return m_holeData[m_currentHole].pin; +// } +// } +// } +// +// +// const auto playerName = [&]() +// { +// return m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].name.toAnsiString(); +// }; +// +// +// auto stepDir = pos - m_playerInfo[0].position; +// const auto totalDist = glm::length(stepDir); +// std::int32_t stepCount = std::max(1, static_cast(totalDist)); +// +// //use smaller steps when putting +// if (ball.terrain == TerrainID::Green) +// { +// stepCount *= 2; +// } +// stepDir /= stepCount; +// +// //using the CPU stats calculate some sort of offset from the target. +// float windEffect = 0.f; +// if (clubID < ClubID::SandWedge) +// { +// windEffect = getOffset(WindOffsets, CPUStats[cpuID][CPUStat::WindAccuracy]); +// windEffect *= Clubs[clubID].getBaseTarget() / Clubs[ClubID::Driver].getBaseTarget(); +// +// //LogI << "Wind effect " << windEffect << std::endl; +// } +// auto wind = m_scene.getSystem()->getWindDirection(); +// wind = (glm::vec3(wind.x, 0.f, wind.z) * wind.y * windEffect); +// wind /= stepCount; +// +// const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTargetAtLevel(CPUStats[cpuID][CPUStat::Skill])); +// auto dirNorm = glm::normalize(stepDir); +// +// const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); +// auto overShootDir = dirNorm * overShoot * (clubMultiplier * clubMultiplier); +// overShootDir /= stepCount; +// +// dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular +// const float strokeAccuracy = getOffset(AccuracyOffsets, CPUStats[cpuID][CPUStat::StrokeAccuracy]); +// auto accuracyDir = dirNorm * strokeAccuracy * clubMultiplier; +// if (puttFromTee) accuracyDir *= 0.5f; +// accuracyDir /= stepCount; +// +// //TODO include offset for rough or bunker terrain - this should probably be another stat +// //for how well CPU compensates +// +// +// //calculate mistake odds - increase this with distance when putting +// bool perfect = false; +// std::int32_t puttingOdds = 0; +// if (ball.terrain == TerrainID::Green) +// { +// float odds = std::pow(std::min(1.f, totalDist / 5.f), 2.f); +// +// accuracyDir *= (1.f + odds); +// odds *= 2.f; +// +// puttingOdds = static_cast(std::round(odds)); +// } +// +// if (cro::Util::Random::value(0, 14) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) +// { +// //TODO check these values so we don't accidentally +// //undo existing power/accuracy and improve them... +// LogI << playerName() << " Made a mistake!" << std::endl; +// switch (cro::Util::Random::value(0, 2)) +// { +// default: +// case 0: +// LogI << playerName() << " Fluffed power" << std::endl; +// overShootDir *= puttFromTee ? 1.3f : 2.56f; +// break; +// case 1: +// LogI << playerName() << " Fluffed accuracy" << std::endl; +// accuracyDir *= puttFromTee ? 1.01f : 1.78f; +// break; +// case 2: +// LogI << playerName() << " Fluffed power and accuracy" << std::endl; +// accuracyDir *= puttFromTee ? 1.02f : 1.92f; +// overShootDir *= puttFromTee ? 1.25f : 1.98f; +// break; +// } +// +// if (glm::length2(m_holeData[m_currentHole].pin - m_playerInfo[0].position) > (2.f * 2.f) && +// cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) +// { +// LogI << playerName() << " made uber mistake" << std::endl; +// stepDir /= 2.f; +// +// stepDir += (stepDir * getOffset(MistakeCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy])); +// } +// } +// +// //calculate perfection odds only if we didn't make a mistake +// else if (cro::Util::Random::value(0, 99) < CPUStats[cpuID][CPUStat::PerfectionLikelyhood]) +// { +// if (totalDist < 20.f) +// { +// perfect = cro::Util::Random::value(1, 10) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 10; +// } +// else if (totalDist < 100.f) +// { +// perfect = cro::Util::Random::value(1, 50) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 2; +// } +// else +// { +// perfect = cro::Util::Random::value(1, 100) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood]; +// } +// +// +// +// +// if(perfect && +// totalDist < Clubs[clubID].getBaseTarget()) +// { +// if (puttFromTee) +// { +// //check for corner cutting +// auto dir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; +// dir /= stepCount; +// +// pos = m_playerInfo[0].position; +// +// for (auto i = 0; i < stepCount; ++i) +// { +// pos += dir; +// auto terrain = m_scene.getSystem()->getTerrain(pos); +// if (terrain.terrain != TerrainID::Green) +// { +// LogI << "Perfect shot would cut corner" << std::endl; +// perfect = false; +// break; +// } +// } +// } +// +// LogI << playerName() << " Got a PERFECT shot" << std::endl; +// pos = m_holeData[m_currentHole].pin; +// } +// else +// { +// perfect = false; //need to do the calc below if we're not in range +// } +// } +// +// CRO_ASSERT(glm::length2(stepDir), ""); +// //CRO_ASSERT(glm::length2(wind), ""); +// CRO_ASSERT(glm::length2(overShootDir), ""); +// CRO_ASSERT(glm::length2(accuracyDir), ""); +// CRO_ASSERT(stepCount != 0, ""); +// +// //if not perfect +// if (!perfect) +// { +// pos = m_playerInfo[0].position; +// for (auto i = 0; i < stepCount; ++i) +// { +// pos += stepDir; +// pos += wind; +// pos += overShootDir; +// pos += accuracyDir; +// +// //check each step for terrain to see if we +// //accidentally cut corners +// if (puttFromTee) +// { +// auto terrain = m_scene.getSystem()->getTerrain(pos); +// if (terrain.terrain != TerrainID::Green) +// { +// break; +// } +// } +// } +// +// //one final check that we're not toooo close to the hole after +// //a long shot else we get too many chip-ins without the 'perfect' requirement +// if (glm::length2(pos - m_playerInfo[0].position) > (5.f * 5.f)) +// { +// if (!puttFromTee || cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) +// { +// if (auto l2 = glm::length2(pos - m_holeData[m_currentHole].pin); l2 < (0.15f * 0.15f)) +// { +// auto dir = (pos - m_holeData[m_currentHole].pin) / std::sqrt(l2); +// dir *= getOffset(HoleCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy]); +// pos += dir; +// LogI << playerName() << " Corrected for too much perfection." << std::endl; +// } +// } +// } +// +// +// //if we're really close to the hole plop it in based on stroke accuracy +// if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && +// cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 10) +// { +// pos = m_holeData[m_currentHole].pin; +// } +// } +// +// return pos; +//} void GolfState::handleDefaultRules(const GolfBallEvent& data) { diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 6dd6e3fb8..32951d5d4 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -87,8 +87,8 @@ namespace sv void initScene(); void buildWorld(); - void makeCPUMove(); - glm::vec3 calcCPUPosition() const; + //void makeCPUMove(); + //glm::vec3 calcCPUPosition() const; void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From 1aeaf101ea0faf6e561635a0a9bf0c496f30bc95 Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 21 Jun 2023 11:29:47 +0100 Subject: [PATCH 062/233] don't draw ball trails for auto-skip CPU players --- samples/golf/src/golf/GolfState.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 4e7d7053e..ab6ecc983 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -4920,14 +4920,14 @@ void GolfState::spawnBall(const ActorInfo& info) material.setProperty("u_colour", cro::Colour::White); //material.blendMode = cro::Material::BlendMode::Multiply; //causes shadow to actually get darker as alpha reaches zero.. duh - + bool showTrail = !(m_sharedData.connectionData[info.clientID].playerData[info.playerID].isCPU && m_sharedData.fastCPU); //point shadow seen from distance entity = m_gameScene.createEntity(); entity.addComponent();// .setPosition(info.position); entity.addComponent().active = true; entity.getComponent().function = - [&, ballEnt, info](cro::Entity e, float) + [&, ballEnt, info, showTrail](cro::Entity e, float) { if (ballEnt.destroyed()) { @@ -4958,11 +4958,13 @@ void GolfState::spawnBall(const ActorInfo& info) e.getComponent().setHidden((m_currentPlayer.terrain == TerrainID::Green) || ballEnt.getComponent().isHidden()); e.getComponent().setMaterialProperty(0, "u_colour", c); - - if (m_sharedData.showBallTrail && (info.playerID == m_currentPlayer.player && info.clientID == m_currentPlayer.client) - && ballEnt.getComponent().state == static_cast(Ball::State::Flight)) + if (showTrail) { - m_ballTrail.addPoint(ballEnt.getComponent().getPosition()); + if (m_sharedData.showBallTrail && (info.playerID == m_currentPlayer.player && info.clientID == m_currentPlayer.client) + && ballEnt.getComponent().state == static_cast(Ball::State::Flight)) + { + m_ballTrail.addPoint(ballEnt.getComponent().getPosition()); + } } } } From 00a3aa5565b231c378f8d50a159aeafa1e687a2a Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 21 Jun 2023 12:15:23 +0100 Subject: [PATCH 063/233] suspend CPU golfer when scoreboard is shown --- samples/golf/src/golf/CPUGolfer.cpp | 28 ++++++++++++++++++++++++--- samples/golf/src/golf/CPUGolfer.hpp | 2 ++ samples/golf/src/golf/GolfStateUI.cpp | 2 ++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 17ffc3cfe..7a9cf6556 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -114,6 +114,7 @@ CPUGolfer::CPUGolfer(InputParser& ip, const ActivePlayer& ap, const CollisionMes m_activePlayer (ap), m_collisionMesh (cm), m_fastCPU (false), + m_suspended (false), m_puttFromTee (false), m_distanceToPin (1.f), m_target (0.f), @@ -207,7 +208,6 @@ void CPUGolfer::handleMessage(const cro::Message& msg) void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) { - if (/*!m_fastCPU &&*/ m_state == State::Inactive) { @@ -314,6 +314,11 @@ void CPUGolfer::update(float dt, glm::vec3 windVector, float distanceToPin) { m_distanceToPin = distanceToPin; + if (m_suspended) + { + return; + } + for (auto& evt : m_popEvents) { SDL_PushEvent(&evt); @@ -382,8 +387,11 @@ void CPUGolfer::setPredictionResult(glm::vec3 result, std::int32_t terrain) (terrain == TerrainID::Water || terrain == TerrainID::Scrub)) { + const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; + //retarget - if (m_puttFromTee) + //if (m_puttFromTee) + if (cro::Util::Random::value(0, 9) > Stat[CPUStat::MistakeLikelyhood]) { //fall back to default target if it's still in front auto fwd = m_baseTarget - m_activePlayer.position; @@ -395,7 +403,7 @@ void CPUGolfer::setPredictionResult(glm::vec3 result, std::int32_t terrain) m_puttFromTee = false; //saves keep trying this until next activation } - const float searchDistance = m_activePlayer.terrain == TerrainID::Green ? 0.25f : 2.f; + const float searchDistance = m_activePlayer.terrain == TerrainID::Green ? 0.25f : 5.f; auto direction = m_retargetCount % 4; auto offset = (glm::normalize(m_baseTarget - m_activePlayer.position) * searchDistance) * static_cast((m_retargetCount % RetargetsPerDirection) + 1); @@ -500,6 +508,20 @@ void CPUGolfer::setCPUCount(std::int32_t cpuCount, const SharedStateData& shared } } +void CPUGolfer::suspend(bool suspend) +{ + //can't suspend during stroke + //else we'll mess up the CPUs shot + if (m_state != State::Stroke) + { + m_suspended = suspend; + } + else + { + m_suspended = false; + } +} + //private void CPUGolfer::startThinking(float duration) { diff --git a/samples/golf/src/golf/CPUGolfer.hpp b/samples/golf/src/golf/CPUGolfer.hpp index e9abea182..2a6889002 100644 --- a/samples/golf/src/golf/CPUGolfer.hpp +++ b/samples/golf/src/golf/CPUGolfer.hpp @@ -65,12 +65,14 @@ class CPUGolfer final : public cro::GuiClient void setFastCPU(bool fast) { m_fastCPU = fast; } void setCPUCount(std::int32_t, const struct SharedStateData&); + void suspend(bool); private: InputParser& m_inputParser; const ActivePlayer& m_activePlayer; const CollisionMesh& m_collisionMesh; bool m_fastCPU; + bool m_suspended; bool m_puttFromTee; float m_distanceToPin; glm::vec3 m_target; diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index b6a31239f..278f92d19 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -2576,6 +2576,8 @@ void GolfState::showScoreboard(bool visible) visible = true; } + m_cpuGolfer.suspend(visible); + auto target = visible ? 0 : 1; //when 1 board is moved 1x screen size from centre cro::Command cmd; From 0d80a2cf73888139f5b4a27aece00354736554ef Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 21 Jun 2023 15:58:02 +0100 Subject: [PATCH 064/233] begin implementing bio text on profile editor --- crogine.sln | 2 +- samples/golf/src/golf/ProfileState.cpp | 111 ++++++++++++++++++++++++- samples/golf/src/golf/ProfileState.hpp | 4 + 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/crogine.sln b/crogine.sln index 36c317be4..50b805c90 100644 --- a/crogine.sln +++ b/crogine.sln @@ -268,7 +268,6 @@ Global {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM64.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.ActiveCfg = Debug|x64 - {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.Build.0 = Debug|x64 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x86.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|ARM.ActiveCfg = DebugASan|Win32 @@ -291,6 +290,7 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM64.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.ActiveCfg = Debug|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.Build.0 = Debug|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.Build.0 = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index 66499509f..305c73e1d 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -80,6 +80,8 @@ source distribution. #include "../ErrorCheck.hpp" +#include + namespace { struct MenuID final @@ -103,6 +105,11 @@ namespace const cro::String XboxString("LB/LT - RB/RT Rotate/Zoom"); const cro::String PSString("L1/L2 - R1/R2 Rotate/Zoom"); + constexpr cro::FloatRect BioCrop = + { + 0.f, -107.f, 96.f, 110.f + }; + constexpr std::size_t PaletteColumnCount = 8; constexpr std::array SwatchPositions = { @@ -139,14 +146,24 @@ ProfileState::ProfileState(cro::StateStack& ss, cro::State::Context ctx, SharedS m_modelScene.simulate(0.f); m_uiScene.simulate(0.f); - /*registerWindow([&]() + registerWindow([&]() { if (ImGui::Begin("Flaps")) { - ImGui::Text("Palette Index %u, UI Index %u", pIndex, sIndex); + //ImGui::Text("Palette Index %u, UI Index %u", pIndex, sIndex); + auto org = m_menuEntities[EntityID::BioText].getComponent().getOrigin(); + if (ImGui::SliderFloat("Bottom", &org.y, -400.f, 0.f)) + { + auto bounds = cro::Text::getLocalBounds(m_menuEntities[EntityID::BioText]); + org.y = std::clamp(org.y, -bounds.height - BioCrop.bottom, 0.f); + auto rect = BioCrop; + rect.bottom += org.y; + m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(rect); + m_menuEntities[EntityID::BioText].getComponent().setOrigin(org); + } } ImGui::End(); - });*/ + }); } //public @@ -211,6 +228,14 @@ bool ProfileState::handleEvent(const cro::Event& evt) applyTextEdit(); } break; + case SDLK_k: + m_menuEntities[EntityID::BioText].getComponent().getUserData()++; + m_menuEntities[EntityID::BioText].getComponent().active = true; + break; + case SDLK_l: + m_menuEntities[EntityID::BioText].getComponent().getUserData()--; + m_menuEntities[EntityID::BioText].getComponent().active = true; + break; } } else if (evt.type == SDL_KEYDOWN) @@ -1143,9 +1168,50 @@ void ProfileState::buildScene() } + auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); + + //bio string + entity = m_uiScene.createEntity(); + entity.addComponent().setPosition({ 400.f, 190.f, 0.1f }); + entity.addComponent(); + entity.addComponent(smallFont);// .setString("Cleftwhistle"); + entity.getComponent().setFillColour(TextNormalColour); + entity.getComponent().setShadowColour(LeaderboardTextDark); + entity.getComponent().setShadowOffset({ 1.f, -1.f }); + entity.getComponent().setCharacterSize(InfoTextSize); + + entity.addComponent().setUserData(0); + entity.getComponent().function = + [](cro::Entity e, float dt) + { + static constexpr float RowHeight = 8.f; + const auto bounds = cro::Text::getLocalBounds(e); + const auto maxHeight = -bounds.height - BioCrop.bottom; + + auto& idx = e.getComponent().getUserData(); + idx = std::clamp(idx, 0, static_cast(maxHeight / RowHeight)); + + const float target = idx * -RowHeight; + auto org = e.getComponent().getOrigin(); + org.y = std::min(target, org.y - (target - org.y) * dt); + + auto rect = BioCrop; + rect.bottom += org.y; + e.getComponent().setCroppingArea(rect); + e.getComponent().setOrigin(org); + + if (org.y == target) + { + e.getComponent().active = false; + } + }; + + bgEnt.getComponent().addChild(entity.getComponent()); + m_menuEntities[EntityID::BioText] = entity; + setBioString(generateRandomBio()); + //help string bounds = bgEnt.getComponent().getTextureBounds(); - auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); entity = m_uiScene.createEntity(); entity.addComponent().setPosition({bounds.width / 2.f, 14.f, 0.1f}); entity.addComponent(); @@ -1839,4 +1905,41 @@ bool ProfileState::applyTextEdit() } m_textEdit = {}; return false; +} + +std::string ProfileState::generateRandomBio() const +{ + return +R"(With a firm grip on the biggest wood and a pair of dimpled balls cupped in their hands, +this is one player who's always ready to tug at the shaft of their favourite club, withdrawing it from the sheath +of the enveloping golf bag... preparing to drive one home as hard and as far as they can go.)"; +} + +void ProfileState::setBioString(const std::string& s) +{ + static constexpr std::size_t MaxChars = 512; + static constexpr std::size_t MaxWidth = 18; + + cro::String output; + std::string tmp; + + std::size_t currentWidth = 0; + + //wordwrap string + std::stringstream ss(s.c_str()); + while (std::getline(ss, tmp, ' ') + && output.size() < MaxChars) + { + if (currentWidth + tmp.size() >= MaxWidth) + { + output.replace(output.size() - 1, 1, "\n"); + currentWidth = 0; + } + output += cro::String::fromUtf8(tmp.begin(), tmp.end()) + " "; + currentWidth += tmp.size() + 1; + } + + m_menuEntities[EntityID::BioText].getComponent().setOrigin({ 0.f, -0.f }); + m_menuEntities[EntityID::BioText].getComponent().setString(output); + m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(BioCrop); } \ No newline at end of file diff --git a/samples/golf/src/golf/ProfileState.hpp b/samples/golf/src/golf/ProfileState.hpp index 8052a7640..66caf82cc 100644 --- a/samples/golf/src/golf/ProfileState.hpp +++ b/samples/golf/src/golf/ProfileState.hpp @@ -107,6 +107,7 @@ class ProfileState final : public cro::State, public cro::GuiClient Root, HelpText, Mugshot, NameText, Swatch, AvatarPreview, + BioText, Count }; @@ -178,4 +179,7 @@ class ProfileState final : public cro::State, public cro::GuiClient void beginTextEdit(cro::Entity, cro::String*, std::size_t); void handleTextEdit(const cro::Event&); bool applyTextEdit(); //returns true if this consumed event + + std::string generateRandomBio() const; + void setBioString(const std::string&); }; \ No newline at end of file From d6fe424a3d16c3ab2a6f9f6ed0cd34611be4fe6d Mon Sep 17 00:00:00 2001 From: fallahn Date: Wed, 21 Jun 2023 19:30:41 +0100 Subject: [PATCH 065/233] increase min skill index --- crogine.sln | 4 ++-- samples/golf/src/golf/CPUGolfer.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crogine.sln b/crogine.sln index 50b805c90..7822d29d5 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 7a9cf6556..1f88d9c7e 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -458,7 +458,7 @@ std::size_t CPUGolfer::getSkillIndex() const return std::clamp((static_cast(m_skillIndex) - offset), 0, 5);*/ auto id = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; - return std::clamp((static_cast(CPUStats.size()) - id) / 5, 0, 5); + return std::clamp((static_cast(CPUStats.size()) - id) / 5, 1, 5); } void CPUGolfer::setCPUCount(std::int32_t cpuCount, const SharedStateData& sharedData) From 2ff6e6f00bc730514818dffc5393da6be26e650a Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 22 Jun 2023 13:57:26 +0100 Subject: [PATCH 066/233] tidy up unused code add read/write biography text to player profiles --- crogine.sln | 4 +- samples/golf/src/golf/CPUGolfer.cpp | 2 +- samples/golf/src/golf/ProfileState.cpp | 228 ++++++--- samples/golf/src/golf/ProfileState.hpp | 1 + .../golf/src/golf/server/ServerGolfRules.cpp | 443 ------------------ .../golf/src/golf/server/ServerGolfState.cpp | 82 ---- .../golf/src/golf/server/ServerGolfState.hpp | 4 - 7 files changed, 177 insertions(+), 587 deletions(-) diff --git a/crogine.sln b/crogine.sln index 7822d29d5..50b805c90 100644 --- a/crogine.sln +++ b/crogine.sln @@ -303,8 +303,8 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|Any CPU.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|ARM64.ActiveCfg = Release|Win32 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = Release|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = Release|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.ActiveCfg = ReleaseGNS|x64 + {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x64.Build.0 = ReleaseGNS|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.ActiveCfg = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release|x86.Build.0 = Release|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Release-asan|Any CPU.ActiveCfg = Release-asan|Win32 diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 1f88d9c7e..7a9cf6556 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -458,7 +458,7 @@ std::size_t CPUGolfer::getSkillIndex() const return std::clamp((static_cast(m_skillIndex) - offset), 0, 5);*/ auto id = m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]; - return std::clamp((static_cast(CPUStats.size()) - id) / 5, 1, 5); + return std::clamp((static_cast(CPUStats.size()) - id) / 5, 0, 5); } void CPUGolfer::setCPUCount(std::int32_t cpuCount, const SharedStateData& sharedData) diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index 305c73e1d..c73b1afcf 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -105,9 +105,10 @@ namespace const cro::String XboxString("LB/LT - RB/RT Rotate/Zoom"); const cro::String PSString("L1/L2 - R1/R2 Rotate/Zoom"); + static constexpr std::size_t MaxBioChars = 512; constexpr cro::FloatRect BioCrop = { - 0.f, -107.f, 96.f, 110.f + 0.f, -104.f, 90.f, 104.f }; constexpr std::size_t PaletteColumnCount = 8; @@ -146,24 +147,24 @@ ProfileState::ProfileState(cro::StateStack& ss, cro::State::Context ctx, SharedS m_modelScene.simulate(0.f); m_uiScene.simulate(0.f); - registerWindow([&]() - { - if (ImGui::Begin("Flaps")) - { - //ImGui::Text("Palette Index %u, UI Index %u", pIndex, sIndex); - auto org = m_menuEntities[EntityID::BioText].getComponent().getOrigin(); - if (ImGui::SliderFloat("Bottom", &org.y, -400.f, 0.f)) - { - auto bounds = cro::Text::getLocalBounds(m_menuEntities[EntityID::BioText]); - org.y = std::clamp(org.y, -bounds.height - BioCrop.bottom, 0.f); - auto rect = BioCrop; - rect.bottom += org.y; - m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(rect); - m_menuEntities[EntityID::BioText].getComponent().setOrigin(org); - } - } - ImGui::End(); - }); + //registerWindow([&]() + // { + // if (ImGui::Begin("Flaps")) + // { + // //ImGui::Text("Palette Index %u, UI Index %u", pIndex, sIndex); + // auto org = m_menuEntities[EntityID::BioText].getComponent().getOrigin(); + // if (ImGui::SliderFloat("Bottom", &org.y, -400.f, 0.f)) + // { + // auto bounds = cro::Text::getLocalBounds(m_menuEntities[EntityID::BioText]); + // org.y = std::clamp(org.y, -bounds.height - BioCrop.bottom, 0.f); + // auto rect = BioCrop; + // rect.bottom += org.y; + // m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(rect); + // m_menuEntities[EntityID::BioText].getComponent().setOrigin(org); + // } + // } + // ImGui::End(); + // }); } //public @@ -228,14 +229,14 @@ bool ProfileState::handleEvent(const cro::Event& evt) applyTextEdit(); } break; - case SDLK_k: - m_menuEntities[EntityID::BioText].getComponent().getUserData()++; - m_menuEntities[EntityID::BioText].getComponent().active = true; - break; - case SDLK_l: - m_menuEntities[EntityID::BioText].getComponent().getUserData()--; - m_menuEntities[EntityID::BioText].getComponent().active = true; - break; + //case SDLK_k: + // m_menuEntities[EntityID::BioText].getComponent().getUserData()++; + // m_menuEntities[EntityID::BioText].getComponent().active = true; + // break; + //case SDLK_l: + // m_menuEntities[EntityID::BioText].getComponent().getUserData()--; + // m_menuEntities[EntityID::BioText].getComponent().active = true; + // break; } } else if (evt.type == SDL_KEYDOWN) @@ -359,6 +360,18 @@ bool ProfileState::handleEvent(const cro::Event& evt) m_cameras[CameraID::Avatar].getComponent().setPosition(pos); } } + else + { + //check bio for scroll + bounds = cro::Text::getLocalBounds(m_menuEntities[EntityID::BioText]); + bounds = m_menuEntities[EntityID::BioText].getComponent().getWorldTransform() * bounds; + + if (bounds.contains(mousePos)) + { + m_menuEntities[EntityID::BioText].getComponent().getUserData() -= evt.wheel.y; + m_menuEntities[EntityID::BioText].getComponent().active = true; + } + } } m_uiScene.getSystem()->handleEvent(evt); @@ -521,23 +534,23 @@ void ProfileState::buildScene() m_uiScene.setSystemActive(true); m_uiScene.getSystem()->setActiveGroup(MenuID::Main); - } - //assume we launched from a cached state and update - //local profile data if necessary - if (m_activeProfile.profileID != m_profileData.playerProfiles[m_profileData.activeProfileIndex].profileID) - { - m_activeProfile = m_profileData.playerProfiles[m_profileData.activeProfileIndex]; - - //refresh the avatar settings - setAvatarIndex(indexFromAvatarID(m_activeProfile.skinID)); - setHairIndex(indexFromHairID(m_activeProfile.hairID)); - setBallIndex(indexFromBallID(m_activeProfile.ballID) % m_ballModels.size()); - refreshMugshot(); - refreshNameString(); - refreshSwatch(); + //assume we launched from a cached state and update + //local profile data if necessary + if (m_activeProfile.profileID != m_profileData.playerProfiles[m_profileData.activeProfileIndex].profileID) + { + m_activeProfile = m_profileData.playerProfiles[m_profileData.activeProfileIndex]; + + //refresh the avatar settings + setAvatarIndex(indexFromAvatarID(m_activeProfile.skinID)); + setHairIndex(indexFromHairID(m_activeProfile.hairID)); + setBallIndex(indexFromBallID(m_activeProfile.ballID) % m_ballModels.size()); + refreshMugshot(); + refreshNameString(); + refreshSwatch(); + } + refreshBio(); } - break; case RootCallbackData::FadeOut: currTime = std::max(0.f, currTime - (dt * 2.f)); @@ -1167,12 +1180,32 @@ void ProfileState::buildScene() }; } + entity = createButton("scroll_up", { 441.f, 192.f }); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_menuEntities[EntityID::BioText].getComponent().getUserData()--; + m_menuEntities[EntityID::BioText].getComponent().active = true; + } + }); + entity = createButton("scroll_down", { 441.f, 81.f }); + entity.getComponent().callbacks[cro::UIInput::ButtonUp] = + uiSystem.addCallback([&](cro::Entity, const cro::ButtonEvent& evt) + { + if (activated(evt)) + { + m_menuEntities[EntityID::BioText].getComponent().getUserData()++; + m_menuEntities[EntityID::BioText].getComponent().active = true; + } + }); auto& smallFont = m_sharedData.sharedResources->fonts.get(FontID::Info); //bio string entity = m_uiScene.createEntity(); - entity.addComponent().setPosition({ 400.f, 190.f, 0.1f }); + entity.addComponent().setPosition({ 399.f, 190.f, 0.1f }); entity.addComponent(); entity.addComponent(smallFont);// .setString("Cleftwhistle"); entity.getComponent().setFillColour(TextNormalColour); @@ -1189,11 +1222,11 @@ void ProfileState::buildScene() const auto maxHeight = -bounds.height - BioCrop.bottom; auto& idx = e.getComponent().getUserData(); - idx = std::clamp(idx, 0, static_cast(maxHeight / RowHeight)); + idx = std::clamp(idx, 0, std::abs(static_cast(maxHeight / RowHeight)) + 1); const float target = idx * -RowHeight; auto org = e.getComponent().getOrigin(); - org.y = std::min(target, org.y - (target - org.y) * dt); + org.y = target;// std::min(target, org.y - (target - org.y) * dt); auto rect = BioCrop; rect.bottom += org.y; @@ -1822,6 +1855,52 @@ void ProfileState::refreshSwatch() m_menuEntities[EntityID::Swatch].getComponent().setVertexData(verts); } +void ProfileState::refreshBio() +{ + //look for bio file and load it if it exists + auto path = Social::getUserContentPath(Social::UserContent::Profile); + if (cro::FileSystem::directoryExists(path)) + { + path += m_activeProfile.profileID + "/"; + + if (cro::FileSystem::directoryExists(path)) + { + path += "bio.txt"; + + if (cro::FileSystem::fileExists(path)) + { + std::vector buffer(MaxBioChars + 1); + + cro::RaiiRWops inFile; + inFile.file = SDL_RWFromFile(path.c_str(), "r"); + if (inFile.file) + { + auto readCount = inFile.file->read(inFile.file, buffer.data(), 1, MaxBioChars); + buffer[readCount] = 0; //nullterm + setBioString(buffer.data()); + } + } + else + { + //else set bio to random and write file + std::string bio = generateRandomBio(); + + cro::RaiiRWops outfile; + outfile.file = SDL_RWFromFile(path.c_str(), "w"); + if (outfile.file) + { + outfile.file->write(outfile.file, bio.data(), bio.size(), 1); + } + setBioString(bio); + } + } + else + { + setBioString(generateRandomBio()); + } + } +} + void ProfileState::beginTextEdit(cro::Entity stringEnt, cro::String* dst, std::size_t maxChars) { *dst = dst->substr(0, maxChars); @@ -1909,16 +1988,38 @@ bool ProfileState::applyTextEdit() std::string ProfileState::generateRandomBio() const { - return -R"(With a firm grip on the biggest wood and a pair of dimpled balls cupped in their hands, -this is one player who's always ready to tug at the shaft of their favourite club, withdrawing it from the sheath -of the enveloping golf bag... preparing to drive one home as hard and as far as they can go.)"; + switch (cro::Util::Random::value(0, 5)) + { + default: + case 0: + return + u8"With a firm grip on the biggest wood and a pair of dimpled balls cupped in their hands, this is one player who's always ready to tug at the shaft of their favourite club, withdrawing it from the sheath of the enveloping golf bag... and prepared to drive one home as hard and as far as they can go."; + case 1: + return + u8"A retired gardener this player knows a thing or two about lying in the rough. Don't underestimate them though - they could be considered the rake in the grass!"; + case 2: + return + u8"Small feet means nothing - not when you can handle your wood like this"; + case 3: + return + u8"Formerly a countryside resident this player moved to the city to experience the thrills of urban golf. Just don't sneak up on them when they're strumming the banjo."; + case 4: + return + u8"\"Good things come in small packages\" is this player's motto. Apparently they diet exclusively on fortune cookies."; + case 5: + return + u8"The clever use of turn signals got this player to where they are today."; + } } void ProfileState::setBioString(const std::string& s) { - static constexpr std::size_t MaxChars = 512; - static constexpr std::size_t MaxWidth = 18; + if (s.empty()) + { + return; + } + + static constexpr std::size_t MaxWidth = 17; cro::String output; std::string tmp; @@ -1926,11 +2027,20 @@ void ProfileState::setBioString(const std::string& s) std::size_t currentWidth = 0; //wordwrap string - std::stringstream ss(s.c_str()); - while (std::getline(ss, tmp, ' ') - && output.size() < MaxChars) + std::size_t searchStart = 0; + while (searchStart < MaxBioChars //&& searchStart < s.size() + && output.size() < MaxBioChars) { - if (currentWidth + tmp.size() >= MaxWidth) + auto nextSpace = s.find(" ", searchStart); + if (nextSpace == std::string::npos) + { + break; + } + + tmp = s.substr(searchStart, nextSpace - searchStart); + searchStart = nextSpace + 1; + + if (currentWidth + (tmp.size() + 1) > MaxWidth) { output.replace(output.size() - 1, 1, "\n"); currentWidth = 0; @@ -1939,6 +2049,14 @@ void ProfileState::setBioString(const std::string& s) currentWidth += tmp.size() + 1; } + if (currentWidth + (tmp.size() + 1) > MaxWidth) + { + output.replace(output.size() - 1, 1, "\n"); + currentWidth = 0; + } + tmp = s.substr(searchStart); + output += cro::String::fromUtf8(tmp.begin(), tmp.end()); + m_menuEntities[EntityID::BioText].getComponent().setOrigin({ 0.f, -0.f }); m_menuEntities[EntityID::BioText].getComponent().setString(output); m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(BioCrop); diff --git a/samples/golf/src/golf/ProfileState.hpp b/samples/golf/src/golf/ProfileState.hpp index 66caf82cc..e5204264c 100644 --- a/samples/golf/src/golf/ProfileState.hpp +++ b/samples/golf/src/golf/ProfileState.hpp @@ -168,6 +168,7 @@ class ProfileState final : public cro::State, public cro::GuiClient void refreshMugshot(); void refreshNameString(); void refreshSwatch(); + void refreshBio(); struct TextEdit final { diff --git a/samples/golf/src/golf/server/ServerGolfRules.cpp b/samples/golf/src/golf/server/ServerGolfRules.cpp index 71f576bd5..90b09354f 100644 --- a/samples/golf/src/golf/server/ServerGolfRules.cpp +++ b/samples/golf/src/golf/server/ServerGolfRules.cpp @@ -14,453 +14,10 @@ namespace { - //glm::vec3 randomNormal() - //{ - // glm::vec2 v( - // static_cast(cro::Util::Random::value(1, 10)) / 10.f, - // static_cast(cro::Util::Random::value(1, 10)) / 10.f); - // v.x *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; - // v.y *= cro::Util::Random::value(0, 1) == 0 ? -1.f : 1.f; - // v = glm::normalize(v); - - // CRO_ASSERT(!std::isnan(v.x), ""); - // return { v.x, 0.f, -v.y }; - //} - - //std::int32_t getClub(float dist) - //{ - // if (dist > 115.f) //forces a cut-off for pitch n putt - // { - // dist = 1000.f; - // } - - // std::int32_t clubID = ClubID::SandWedge; - // while ((Clubs[clubID].getDefaultTarget() * 1.05f) < dist - // && clubID != ClubID::Driver) - // { - // clubID--; - // } - // return clubID; - //} } using namespace sv; -//using namespace cstat; -//void GolfState::makeCPUMove() -//{ -// if(m_sharedData.fastCPU -// && m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].isCPU) -// { -// auto& ball = m_playerInfo[0].ballEntity.getComponent(); -// if (ball.state == Ball::State::Idle) -// { -// //wrap in an ent so we can add a small delay -// auto entity = m_scene.createEntity(); -// entity.addComponent().active = true; -// entity.getComponent().setUserData(1.2f); -// entity.getComponent().function = -// [&](cro::Entity e, float dt) -// { -// auto& currTime = e.getComponent().getUserData(); -// currTime -= dt; -// -// if (currTime < 0) -// { -// auto& ball = m_playerInfo[0].ballEntity.getComponent(); -// auto animID = ball.terrain == TerrainID::Green ? AnimationID::Putt : AnimationID::Celebrate; -// -// //this is just separated out so we can swap calcs more easily -// auto pos = calcCPUPosition(); -// -// -// //test terrain height and correct final position -// auto result = m_scene.getSystem()->getTerrain(pos); -// -// //technically this means CPU players never make really bad shots -// //but otherwise they just get stuck in a loop -// switch (result.terrain) -// { -// case TerrainID::Water: -// //use mistake odds to occasionally hit the water -// { -// const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; -// if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) -// { -// break; -// } -// } -// [[fallthrough]]; -// case TerrainID::Stone: -// case TerrainID::Scrub: -// { -// /*std::int32_t tries = 300; -// auto dir = glm::normalize(pos - m_playerInfo[0].position); -// do -// { -// pos -= dir; -// result = m_scene.getSystem()->getTerrain(pos); -// } while (tries-- -// && (result.terrain == TerrainID::Water || result.terrain == TerrainID::Stone || result.terrain == TerrainID::Scrub) -// && glm::length2(pos) > 1);*/ -// } -// break; -// default: break; -// } -// -// pos.y = result.intersection.y; -// -// CRO_ASSERT(!std::isnan(pos.x), ""); -// CRO_ASSERT(!std::isnan(pos.y), ""); -// CRO_ASSERT(!std::isnan(pos.z), ""); -// -// m_playerInfo[0].ballEntity.getComponent().setPosition(pos); -// m_playerInfo[0].holeScore[m_currentHole]++; -// -// //TODO this case should never happen... -// auto velOffset = pos - m_playerInfo[0].position; -// if (glm::length2(velOffset) == 0) -// { -// velOffset.x = 0.0001f; -// } -// velOffset = glm::normalize(velOffset) * 0.001f; -// -// //const auto velOffset = glm::normalize(pos - m_playerInfo[0].position) * 0.001f; -// -// //LogI << velOffset << std::endl; -// ball.terrain = result.terrain; -// switch (result.terrain) -// { -// default: -// ball.state = Ball::State::Paused; -// break; -// case TerrainID::Bunker: -// case TerrainID::Rough: -// animID = AnimationID::Disappoint; -// [[fallthrough]]; -// case TerrainID::Fairway: -// ball.state = Ball::State::Flight; -// ball.velocity = velOffset; //add a tiny bit of velocity to prevent div0/nan in BallSystem -// break; -// case TerrainID::Green: -// case TerrainID::Hole: -// ball.state = Ball::State::Putt; -// ball.velocity = velOffset; -// break; -// case TerrainID::Scrub: -// case TerrainID::Stone: -// case TerrainID::Water: -// ball.state = Ball::State::Reset; -// animID = AnimationID::Disappoint; -// break; -// } -// -// m_sharedData.host.broadcastPacket(PacketID::ActorAnimation, std::uint8_t(animID), net::NetFlag::Reliable, ConstVal::NetChannelReliable); -// -// -// -// m_sharedData.host.broadcastPacket(PacketID::CPUThink, 1, net::NetFlag::Reliable, ConstVal::NetChannelReliable); -// e.getComponent().active = false; -// m_scene.destroyEntity(e); -// } -// }; -// -// m_sharedData.host.broadcastPacket(PacketID::CPUThink, 0, net::NetFlag::Reliable, ConstVal::NetChannelReliable); -// } -// } -//} -// -//glm::vec3 GolfState::calcCPUPosition() const -//{ -// const auto targetDir = m_holeData[m_currentHole].target - m_playerInfo[0].position; -// const auto pinDir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; -// auto pos = m_holeData[m_currentHole].pin; //always prefer the pin as the target unless blocked for some reason -// -// const auto puttFromTee = m_scene.getSystem()->getPuttFromTee(); -// -// const auto cpuID = m_cpuProfileIndices[m_playerInfo[0].client * ConstVal::MaxPlayers + m_playerInfo[0].player]; -// CRO_ASSERT(cpuID != -1, ""); -// -// const std::int32_t skill = CPUStats[cpuID][CPUStat::Skill]; -// -// auto& ball = m_playerInfo[0].ballEntity.getComponent(); -// std::int32_t clubID = ClubID::Putter; -// -// //get longest range available -// if (ball.terrain != TerrainID::Green) -// { -// if (ball.terrain == TerrainID::Bunker) -// { -// clubID = ClubID::PitchWedge; -// } -// else -// { -// auto dist = glm::length(pinDir); -// clubID = getClub(dist); -// } -// -// //check to see if the club range can hit the ball into a valid area, -// //by reducing pos to max range -// const float clubDist = Clubs[clubID].getTargetAtLevel(skill); -// if (auto len2 = glm::length2(pinDir); -// len2 > (clubDist * clubDist)) -// { -// //rather than move back toward the player (which might put us in the rough) -// //head towards the target point which in theory should aim us towards the fairway -// const auto len = std::sqrt(len2) - clubDist; -// const auto correctionDir = glm::normalize(m_holeData[m_currentHole].target - m_holeData[m_currentHole].pin) * len; -// pos = m_holeData[m_currentHole].pin + correctionDir; -// -// //const float reduction = clubDist / std::sqrt(len2); -// //pos = (pinDir * reduction) + m_playerInfo[0].position; -// } -// -// auto result = m_scene.getSystem()->getTerrain(pos); -// switch (result.terrain) -// { -// default: break; -// case TerrainID::Water: -// //use the mistake odds to sometimes accept this target -// if (cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) -// { -// break; -// } -// [[fallthrough]]; -// case TerrainID::Stone: -// case TerrainID::Scrub: -// //else use the target point instead of the pin -// pos = m_holeData[m_currentHole].target; -// break; -// } -// } -// else -// { -// //else if we're on a mini-putt course see if there's a dog-leg -// if (puttFromTee) -// { -// if (auto dp = glm::dot(glm::normalize(targetDir), glm::normalize(pinDir)); -// dp > 0.4 && dp < 0.98f) //target in front, but not the same dir as pin -// { -// //don't use if too close -// if (glm::length2(targetDir) > (3.f * 3.f) -// && glm::length2(pinDir) > glm::length2(targetDir)) -// { -// pos = m_holeData[m_currentHole].target; -// } -// } -// } -// else -// { -// //regular putting - assume we go in the hole under 20cm -// if (glm::length2(pinDir) < (0.2f * 0.2f)) -// { -// return m_holeData[m_currentHole].pin; -// } -// } -// } -// -// -// const auto playerName = [&]() -// { -// return m_sharedData.clients[m_playerInfo[0].client].playerData[m_playerInfo[0].player].name.toAnsiString(); -// }; -// -// -// auto stepDir = pos - m_playerInfo[0].position; -// const auto totalDist = glm::length(stepDir); -// std::int32_t stepCount = std::max(1, static_cast(totalDist)); -// -// //use smaller steps when putting -// if (ball.terrain == TerrainID::Green) -// { -// stepCount *= 2; -// } -// stepDir /= stepCount; -// -// //using the CPU stats calculate some sort of offset from the target. -// float windEffect = 0.f; -// if (clubID < ClubID::SandWedge) -// { -// windEffect = getOffset(WindOffsets, CPUStats[cpuID][CPUStat::WindAccuracy]); -// windEffect *= Clubs[clubID].getBaseTarget() / Clubs[ClubID::Driver].getBaseTarget(); -// -// //LogI << "Wind effect " << windEffect << std::endl; -// } -// auto wind = m_scene.getSystem()->getWindDirection(); -// wind = (glm::vec3(wind.x, 0.f, wind.z) * wind.y * windEffect); -// wind /= stepCount; -// -// const auto clubMultiplier = (Clubs[clubID].getTarget(totalDist) / Clubs[ClubID::Driver].getTargetAtLevel(CPUStats[cpuID][CPUStat::Skill])); -// auto dirNorm = glm::normalize(stepDir); -// -// const float overShoot = getOffset(PowerOffsets, CPUStats[cpuID][CPUStat::PowerAccuracy]); -// auto overShootDir = dirNorm * overShoot * (clubMultiplier * clubMultiplier); -// overShootDir /= stepCount; -// -// dirNorm = { -dirNorm.z, dirNorm.y, dirNorm.x }; //perpendicular -// const float strokeAccuracy = getOffset(AccuracyOffsets, CPUStats[cpuID][CPUStat::StrokeAccuracy]); -// auto accuracyDir = dirNorm * strokeAccuracy * clubMultiplier; -// if (puttFromTee) accuracyDir *= 0.5f; -// accuracyDir /= stepCount; -// -// //TODO include offset for rough or bunker terrain - this should probably be another stat -// //for how well CPU compensates -// -// -// //calculate mistake odds - increase this with distance when putting -// bool perfect = false; -// std::int32_t puttingOdds = 0; -// if (ball.terrain == TerrainID::Green) -// { -// float odds = std::pow(std::min(1.f, totalDist / 5.f), 2.f); -// -// accuracyDir *= (1.f + odds); -// odds *= 2.f; -// -// puttingOdds = static_cast(std::round(odds)); -// } -// -// if (cro::Util::Random::value(0, 14) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + puttingOdds) -// { -// //TODO check these values so we don't accidentally -// //undo existing power/accuracy and improve them... -// LogI << playerName() << " Made a mistake!" << std::endl; -// switch (cro::Util::Random::value(0, 2)) -// { -// default: -// case 0: -// LogI << playerName() << " Fluffed power" << std::endl; -// overShootDir *= puttFromTee ? 1.3f : 2.56f; -// break; -// case 1: -// LogI << playerName() << " Fluffed accuracy" << std::endl; -// accuracyDir *= puttFromTee ? 1.01f : 1.78f; -// break; -// case 2: -// LogI << playerName() << " Fluffed power and accuracy" << std::endl; -// accuracyDir *= puttFromTee ? 1.02f : 1.92f; -// overShootDir *= puttFromTee ? 1.25f : 1.98f; -// break; -// } -// -// if (glm::length2(m_holeData[m_currentHole].pin - m_playerInfo[0].position) > (2.f * 2.f) && -// cro::Util::Random::value(0, 29) < CPUStats[cpuID][CPUStat::MistakeLikelyhood] + (puttingOdds * 2)) -// { -// LogI << playerName() << " made uber mistake" << std::endl; -// stepDir /= 2.f; -// -// stepDir += (stepDir * getOffset(MistakeCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy])); -// } -// } -// -// //calculate perfection odds only if we didn't make a mistake -// else if (cro::Util::Random::value(0, 99) < CPUStats[cpuID][CPUStat::PerfectionLikelyhood]) -// { -// if (totalDist < 20.f) -// { -// perfect = cro::Util::Random::value(1, 10) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 10; -// } -// else if (totalDist < 100.f) -// { -// perfect = cro::Util::Random::value(1, 50) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood] / 2; -// } -// else -// { -// perfect = cro::Util::Random::value(1, 100) == CPUStats[cpuID][CPUStat::PerfectionLikelyhood]; -// } -// -// -// -// -// if(perfect && -// totalDist < Clubs[clubID].getBaseTarget()) -// { -// if (puttFromTee) -// { -// //check for corner cutting -// auto dir = m_holeData[m_currentHole].pin - m_playerInfo[0].position; -// dir /= stepCount; -// -// pos = m_playerInfo[0].position; -// -// for (auto i = 0; i < stepCount; ++i) -// { -// pos += dir; -// auto terrain = m_scene.getSystem()->getTerrain(pos); -// if (terrain.terrain != TerrainID::Green) -// { -// LogI << "Perfect shot would cut corner" << std::endl; -// perfect = false; -// break; -// } -// } -// } -// -// LogI << playerName() << " Got a PERFECT shot" << std::endl; -// pos = m_holeData[m_currentHole].pin; -// } -// else -// { -// perfect = false; //need to do the calc below if we're not in range -// } -// } -// -// CRO_ASSERT(glm::length2(stepDir), ""); -// //CRO_ASSERT(glm::length2(wind), ""); -// CRO_ASSERT(glm::length2(overShootDir), ""); -// CRO_ASSERT(glm::length2(accuracyDir), ""); -// CRO_ASSERT(stepCount != 0, ""); -// -// //if not perfect -// if (!perfect) -// { -// pos = m_playerInfo[0].position; -// for (auto i = 0; i < stepCount; ++i) -// { -// pos += stepDir; -// pos += wind; -// pos += overShootDir; -// pos += accuracyDir; -// -// //check each step for terrain to see if we -// //accidentally cut corners -// if (puttFromTee) -// { -// auto terrain = m_scene.getSystem()->getTerrain(pos); -// if (terrain.terrain != TerrainID::Green) -// { -// break; -// } -// } -// } -// -// //one final check that we're not toooo close to the hole after -// //a long shot else we get too many chip-ins without the 'perfect' requirement -// if (glm::length2(pos - m_playerInfo[0].position) > (5.f * 5.f)) -// { -// if (!puttFromTee || cro::Util::Random::value(0, 9) < CPUStats[cpuID][CPUStat::MistakeLikelyhood]) -// { -// if (auto l2 = glm::length2(pos - m_holeData[m_currentHole].pin); l2 < (0.15f * 0.15f)) -// { -// auto dir = (pos - m_holeData[m_currentHole].pin) / std::sqrt(l2); -// dir *= getOffset(HoleCorrection, CPUStats[cpuID][CPUStat::MistakeAccuracy]); -// pos += dir; -// LogI << playerName() << " Corrected for too much perfection." << std::endl; -// } -// } -// } -// -// -// //if we're really close to the hole plop it in based on stroke accuracy -// if (glm::length2(pos - m_holeData[m_currentHole].pin) < (0.15f * 0.15f) && -// cro::Util::Random::value(1, 100) > CPUStats[cpuID][CPUStat::StrokeAccuracy] * 10) -// { -// pos = m_holeData[m_currentHole].pin; -// } -// } -// -// return pos; -//} void GolfState::handleDefaultRules(const GolfBallEvent& data) { diff --git a/samples/golf/src/golf/server/ServerGolfState.cpp b/samples/golf/src/golf/server/ServerGolfState.cpp index 377e41814..f58a09f52 100644 --- a/samples/golf/src/golf/server/ServerGolfState.cpp +++ b/samples/golf/src/golf/server/ServerGolfState.cpp @@ -76,7 +76,6 @@ GolfState::GolfState(SharedData& sd) m_currentBest (MaxStrokes)/*, m_skillIndex (0)*/ { - std::fill(m_cpuProfileIndices.begin(), m_cpuProfileIndices.end(), -1); if (m_mapDataValid = validateMap(); m_mapDataValid) { initScene(); @@ -706,32 +705,6 @@ void GolfState::setNextHole() return; } - //reset player positions/strokes - /*for (auto& player : m_playerInfo) - { - player.position = m_holeData[m_currentHole].tee; - player.distanceToHole = glm::length(m_holeData[m_currentHole].tee - m_holeData[m_currentHole].pin); - player.terrain = ballSystem->getTerrain(player.position).terrain; - - auto ball = player.ballEntity; - ball.getComponent().terrain = player.terrain; - ball.getComponent().velocity = glm::vec3(0.f); - ball.getComponent().setPosition(m_holeData[m_currentHole].tee); - - auto timestamp = m_serverTime.elapsed().asMilliseconds(); - - ActorInfo info; - info.serverID = static_cast(ball.getIndex()); - info.position = ball.getComponent().getPosition(); - info.rotation = cro::Util::Net::compressQuat(ball.getComponent().getRotation()); - info.windEffect = ball.getComponent().windEffect; - info.timestamp = timestamp; - info.clientID = player.client; - info.playerID = player.player; - info.state = static_cast(ball.getComponent().state); - m_sharedData.host.broadcastPacket(PacketID::ActorUpdate, info, net::NetFlag::Reliable, ConstVal::NetChannelReliable); - }*/ - //tell clients to set up next hole std::uint16_t newHole = (m_currentHole << 8) | std::uint8_t(m_holeData[m_currentHole].par); m_sharedData.host.broadcastPacket(PacketID::SetHole, newHole, net::NetFlag::Reliable, ConstVal::NetChannelReliable); @@ -1011,7 +984,6 @@ void GolfState::initScene() m_mapDataValid = m_scene.addSystem(mb)->setHoleData(m_holeData[0]); m_scene.getSystem()->setGimmeRadius(m_sharedData.gimmeRadius); - std::int32_t cpuCount = 0; for (auto i = 0u; i < m_sharedData.clients.size(); ++i) { if (m_sharedData.clients[i].connected) @@ -1023,64 +995,10 @@ void GolfState::initScene() player.player = j; player.position = m_holeData[0].tee; player.distanceToHole = glm::length(m_holeData[0].tee - m_holeData[0].pin); - - //do this regardless of fastCPU setting - it'll just get ignored if not used. - if (m_sharedData.clients[i].playerData[j].isCPU) - { - cpuCount++; - } } } } std::shuffle(m_playerInfo.begin(), m_playerInfo.end(), cro::Util::Random::rndEngine); - - - //assign indices into the CPU profile array - //based on host's club set and CPU count - if (cpuCount) - { - std::int32_t baseCPUIndex = 0; - std::int32_t stride = 1; - - switch (Club::getClubLevel()) - { - default: break; - case 0: - baseCPUIndex = 12; - stride = 16 / cpuCount; //even distribution through 16x level 0 - break; - case 1: - baseCPUIndex = 8; - stride = cpuCount > 4 ? 2 : 23 / cpuCount; //every other profile unless more than 4 - break; - case 2: - stride = cpuCount < 3 ? 1 : - cpuCount < 8 ? 2 : - 27 / cpuCount; - break; - } - - - CRO_ASSERT(baseCPUIndex + (stride * (cpuCount - 1)) < CPUStats.size(), ""); - - for (auto i = 0u; i < m_sharedData.clients.size(); ++i) - { - if (m_sharedData.clients[i].connected) - { - for (auto j = 0u; j < m_sharedData.clients[i].playerCount; ++j) - { - if (m_sharedData.clients[i].playerData[j].isCPU) - { - CRO_ASSERT(baseCPUIndex < CPUStats.size(), ""); - - auto cpuIndex = i * ConstVal::MaxPlayers + j; - m_cpuProfileIndices[cpuIndex] = baseCPUIndex; - baseCPUIndex += stride; - } - } - } - } - } } void GolfState::buildWorld() diff --git a/samples/golf/src/golf/server/ServerGolfState.hpp b/samples/golf/src/golf/server/ServerGolfState.hpp index 32951d5d4..375723d09 100644 --- a/samples/golf/src/golf/server/ServerGolfState.hpp +++ b/samples/golf/src/golf/server/ServerGolfState.hpp @@ -73,8 +73,6 @@ namespace sv cro::Clock m_turnTimer; - std::array m_cpuProfileIndices = {}; - void sendInitialGameState(std::uint8_t); void handlePlayerInput(const net::NetEvent::Packet&, bool predict); void checkReadyQuit(std::uint8_t); @@ -87,8 +85,6 @@ namespace sv void initScene(); void buildWorld(); - //void makeCPUMove(); - //glm::vec3 calcCPUPosition() const; void handleDefaultRules(const struct GolfBallEvent&); bool summariseDefaultRules(); From 9486afce30640d47599f268821b9ece9cac56e24 Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 22 Jun 2023 15:52:19 +0100 Subject: [PATCH 067/233] implement snapshot button for avatar mugshots --- samples/golf/src/golf/ProfileState.cpp | 102 +++++++++++++++++++------ samples/golf/src/golf/ProfileState.hpp | 6 +- 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index c73b1afcf..d1504800a 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -94,13 +94,15 @@ namespace BottomL, BottomD }; }; - + constexpr glm::uvec2 BallTexSize(96u, 110u); constexpr glm::uvec2 AvatarTexSize(130u, 202u); + constexpr glm::uvec2 MugshotTexSize(192u, 96u); constexpr glm::vec3 CameraBasePosition({ -0.867f, 1.325f, -1.68f }); constexpr glm::vec3 CameraZoomPosition({ -0.867f, 1.625f, -0.58f }); const glm::vec3 CameraZoomVector = glm::normalize(CameraZoomPosition - CameraBasePosition); + constexpr glm::vec3 MugCameraPosition({ -0.867f, 1.6f, -0.3f }); const cro::String XboxString("LB/LT - RB/RT Rotate/Zoom"); const cro::String PSString("L1/L2 - R1/R2 Rotate/Zoom"); @@ -133,7 +135,8 @@ ProfileState::ProfileState(cro::StateStack& ss, cro::State::Context ctx, SharedS m_ballIndex (0), m_ballHairIndex (0), m_avatarIndex (0), - m_lastSelected (0) + m_lastSelected (0), + m_mugshotUpdated (false) { ctx.mainWindow.setMouseCaptured(false); @@ -147,24 +150,27 @@ ProfileState::ProfileState(cro::StateStack& ss, cro::State::Context ctx, SharedS m_modelScene.simulate(0.f); m_uiScene.simulate(0.f); - //registerWindow([&]() - // { - // if (ImGui::Begin("Flaps")) - // { - // //ImGui::Text("Palette Index %u, UI Index %u", pIndex, sIndex); - // auto org = m_menuEntities[EntityID::BioText].getComponent().getOrigin(); - // if (ImGui::SliderFloat("Bottom", &org.y, -400.f, 0.f)) - // { - // auto bounds = cro::Text::getLocalBounds(m_menuEntities[EntityID::BioText]); - // org.y = std::clamp(org.y, -bounds.height - BioCrop.bottom, 0.f); - // auto rect = BioCrop; - // rect.bottom += org.y; - // m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(rect); - // m_menuEntities[EntityID::BioText].getComponent().setOrigin(org); - // } - // } - // ImGui::End(); - // }); + registerWindow([&]() + { + if (ImGui::Begin("Flaps")) + { + if (m_mugshotTexture.available()) + { + ImGui::Image(m_mugshotTexture.getTexture(), { 192.f, 96.f }, { 0.f, 1.f }, { 1.f, 0.f }); + } + + /*auto pos = m_cameras[CameraID::Mugshot].getComponent().getPosition(); + if (ImGui::SliderFloat("Height", &pos.y, 0.f, 2.f)) + { + m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); + } + if (ImGui::SliderFloat("Depth", &pos.z, -2.f, 4.f)) + { + m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); + }*/ + } + ImGui::End(); + }); } //public @@ -460,6 +466,7 @@ void ProfileState::render() m_avatarTexture.display(); m_uiScene.render(); + } //private @@ -498,6 +505,9 @@ void ProfileState::loadResources() m_audioEnts[AudioID::Select].addComponent() = m_menuSounds.getEmitter("switch"); m_audioEnts[AudioID::Accept].getComponent().play(); + + + m_mugshotTexture.create(MugshotTexSize.x, MugshotTexSize.y); } void ProfileState::buildScene() @@ -1034,7 +1044,7 @@ void ProfileState::buildScene() { if (activated(evt)) { - + generateMugshot(); } }); @@ -1054,6 +1064,19 @@ void ProfileState::buildScene() { m_profileData.playerProfiles[m_profileData.activeProfileIndex] = m_activeProfile; m_profileData.playerProfiles[m_profileData.activeProfileIndex].saveProfile(); + + if (m_mugshotUpdated) + { + auto path = Social::getUserContentPath(Social::UserContent::Profile) + m_activeProfile.profileID + "/mug.png"; + m_mugshotTexture.getTexture().saveToFile(path); + + m_activeProfile.mugshot = path; + m_profileData.playerProfiles[m_profileData.activeProfileIndex].mugshot = path; + m_profileData.playerProfiles[m_profileData.activeProfileIndex].saveProfile(); + + m_mugshotUpdated = false; + } + quitState(); } }); @@ -1436,6 +1459,7 @@ void ProfileState::buildPreviewScene() entity.addComponent().setPosition(AvatarPos + glm::vec3(0.f, -0.15f, 0.f)); entity.getComponent().setScale(glm::vec3(60.f)); m_profileData.grassDef->createModel(entity); + entity.getComponent().setRenderFlags((1 << 1)); //update the model textures with the current colour settings //and any available audio @@ -1536,7 +1560,17 @@ void ProfileState::buildPreviewScene() m_cameras[CameraID::Avatar].getComponent().rotate(cro::Transform::X_AXIS, -0.157f); auto& cam = m_cameras[CameraID::Avatar].addComponent(); cam.setPerspective(70.f * cro::Util::Const::degToRad, static_cast(AvatarTexSize.x) / AvatarTexSize.y, 0.1f, 6.f); - cam.viewport = { 0.f, 0.f, 1.f ,1.f }; + cam.viewport = { 0.f, 0.f, 1.f, 1.f }; + + m_cameras[CameraID::Mugshot] = m_modelScene.createEntity(); + m_cameras[CameraID::Mugshot].addComponent().setPosition(MugCameraPosition); + m_cameras[CameraID::Mugshot].getComponent().setRotation(cro::Transform::Y_AXIS, cro::Util::Const::PI); + //m_cameras[CameraID::Mugshot].getComponent().rotate(cro::Transform::X_AXIS, -0.157f); + auto& cam2 = m_cameras[CameraID::Mugshot].addComponent(); + cam2.setPerspective(70.f * cro::Util::Const::degToRad, 1.f, 0.1f, 6.f); + cam2.viewport = { 0.f, 0.f, 0.5f, 1.f }; + cam2.renderFlags = ~(1 << 1); + m_modelScene.getSunlight().getComponent().rotate(cro::Transform::Y_AXIS, 96.f * cro::Util::Const::degToRad); m_modelScene.getSunlight().getComponent().rotate(cro::Transform::X_AXIS, -39.f * cro::Util::Const::degToRad); @@ -2060,4 +2094,28 @@ void ProfileState::setBioString(const std::string& s) m_menuEntities[EntityID::BioText].getComponent().setOrigin({ 0.f, -0.f }); m_menuEntities[EntityID::BioText].getComponent().setString(output); m_menuEntities[EntityID::BioText].getComponent().setCroppingArea(BioCrop); +} + +void ProfileState::generateMugshot() +{ + m_mugshotTexture.clear(TextNormalColour); + auto& cam = m_cameras[CameraID::Mugshot].getComponent(); + cam.viewport = { 0.f, 0.f, 0.5f, 1.f }; + m_cameras[CameraID::Mugshot].getComponent().setPosition(MugCameraPosition); + m_cameras[CameraID::Mugshot].getComponent().setRotation(cro::Transform::Y_AXIS, cro::Util::Const::PI); + m_cameras[CameraID::Mugshot].getComponent().updateMatrices(m_cameras[CameraID::Mugshot].getComponent()); + m_modelScene.setActiveCamera(m_cameras[CameraID::Mugshot]); + m_modelScene.render(); + + cam.viewport = { 0.5f, 0.f, 0.5f, 1.f }; + m_cameras[CameraID::Mugshot].getComponent().setPosition(MugCameraPosition + glm::vec3(-MugCameraPosition.z + 0.1f, 0.f, -MugCameraPosition.z)); + m_cameras[CameraID::Mugshot].getComponent().setRotation(cro::Transform::Y_AXIS, cro::Util::Const::PI / 2.f); + m_cameras[CameraID::Mugshot].getComponent().updateMatrices(m_cameras[CameraID::Mugshot].getComponent()); + m_modelScene.render(); + + m_mugshotTexture.display(); + + m_mugshotUpdated = true; + m_menuEntities[EntityID::Mugshot].getComponent().setTexture(m_mugshotTexture.getTexture()); + m_menuEntities[EntityID::Mugshot].getComponent().setScale(glm::vec2(1.f)); } \ No newline at end of file diff --git a/samples/golf/src/golf/ProfileState.hpp b/samples/golf/src/golf/ProfileState.hpp index e5204264c..9dd5d5715 100644 --- a/samples/golf/src/golf/ProfileState.hpp +++ b/samples/golf/src/golf/ProfileState.hpp @@ -118,7 +118,7 @@ class ProfileState final : public cro::State, public cro::GuiClient { enum { - Avatar, Ball, + Avatar, Ball, Mugshot, Count }; @@ -183,4 +183,8 @@ class ProfileState final : public cro::State, public cro::GuiClient std::string generateRandomBio() const; void setBioString(const std::string&); + + cro::RenderTexture m_mugshotTexture; + bool m_mugshotUpdated; + void generateMugshot(); }; \ No newline at end of file From d035711e8e85b750c99fe289665751de3060437c Mon Sep 17 00:00:00 2001 From: fallahn Date: Thu, 22 Jun 2023 19:43:55 +0100 Subject: [PATCH 068/233] tweak mugshot generation --- samples/golf/src/golf/ProfileState.cpp | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/samples/golf/src/golf/ProfileState.cpp b/samples/golf/src/golf/ProfileState.cpp index d1504800a..8fadf5206 100644 --- a/samples/golf/src/golf/ProfileState.cpp +++ b/samples/golf/src/golf/ProfileState.cpp @@ -102,7 +102,7 @@ namespace constexpr glm::vec3 CameraBasePosition({ -0.867f, 1.325f, -1.68f }); constexpr glm::vec3 CameraZoomPosition({ -0.867f, 1.625f, -0.58f }); const glm::vec3 CameraZoomVector = glm::normalize(CameraZoomPosition - CameraBasePosition); - constexpr glm::vec3 MugCameraPosition({ -0.867f, 1.6f, -0.3f }); + constexpr glm::vec3 MugCameraPosition({ -0.854f, 1.6f, -0.32f }); const cro::String XboxString("LB/LT - RB/RT Rotate/Zoom"); const cro::String PSString("L1/L2 - R1/R2 Rotate/Zoom"); @@ -150,27 +150,27 @@ ProfileState::ProfileState(cro::StateStack& ss, cro::State::Context ctx, SharedS m_modelScene.simulate(0.f); m_uiScene.simulate(0.f); - registerWindow([&]() - { - if (ImGui::Begin("Flaps")) - { - if (m_mugshotTexture.available()) - { - ImGui::Image(m_mugshotTexture.getTexture(), { 192.f, 96.f }, { 0.f, 1.f }, { 1.f, 0.f }); - } + //registerWindow([&]() + // { + // if (ImGui::Begin("Flaps")) + // { + // if (m_mugshotTexture.available()) + // { + // ImGui::Image(m_mugshotTexture.getTexture(), { 192.f, 96.f }, { 0.f, 1.f }, { 1.f, 0.f }); + // } - /*auto pos = m_cameras[CameraID::Mugshot].getComponent().getPosition(); - if (ImGui::SliderFloat("Height", &pos.y, 0.f, 2.f)) - { - m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); - } - if (ImGui::SliderFloat("Depth", &pos.z, -2.f, 4.f)) - { - m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); - }*/ - } - ImGui::End(); - }); + // /*auto pos = m_cameras[CameraID::Mugshot].getComponent().getPosition(); + // if (ImGui::SliderFloat("Height", &pos.y, 0.f, 2.f)) + // { + // m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); + // } + // if (ImGui::SliderFloat("Depth", &pos.z, -2.f, 4.f)) + // { + // m_cameras[CameraID::Mugshot].getComponent().setPosition(pos); + // }*/ + // } + // ImGui::End(); + // }); } //public @@ -2098,7 +2098,7 @@ void ProfileState::setBioString(const std::string& s) void ProfileState::generateMugshot() { - m_mugshotTexture.clear(TextNormalColour); + m_mugshotTexture.clear({0xa9c0afff}); auto& cam = m_cameras[CameraID::Mugshot].getComponent(); cam.viewport = { 0.f, 0.f, 0.5f, 1.f }; m_cameras[CameraID::Mugshot].getComponent().setPosition(MugCameraPosition); @@ -2108,7 +2108,7 @@ void ProfileState::generateMugshot() m_modelScene.render(); cam.viewport = { 0.5f, 0.f, 0.5f, 1.f }; - m_cameras[CameraID::Mugshot].getComponent().setPosition(MugCameraPosition + glm::vec3(-MugCameraPosition.z + 0.1f, 0.f, -MugCameraPosition.z)); + m_cameras[CameraID::Mugshot].getComponent().setPosition(MugCameraPosition + glm::vec3(-MugCameraPosition.z + 0.05f, 0.f, -MugCameraPosition.z)); m_cameras[CameraID::Mugshot].getComponent().setRotation(cro::Transform::Y_AXIS, cro::Util::Const::PI / 2.f); m_cameras[CameraID::Mugshot].getComponent().updateMatrices(m_cameras[CameraID::Mugshot].getComponent()); m_modelScene.render(); @@ -2117,5 +2117,5 @@ void ProfileState::generateMugshot() m_mugshotUpdated = true; m_menuEntities[EntityID::Mugshot].getComponent().setTexture(m_mugshotTexture.getTexture()); - m_menuEntities[EntityID::Mugshot].getComponent().setScale(glm::vec2(1.f)); + m_menuEntities[EntityID::Mugshot].getComponent().setScale(glm::vec2(0.5f)); } \ No newline at end of file From 26309c772617682eebf6b1720317aa0d154c27bf Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 09:56:43 +0100 Subject: [PATCH 069/233] add bunker to list of terrain for CPU playrs to avoid --- samples/golf/src/golf/CPUGolfer.cpp | 4 +++- samples/golf/src/golf/GolfState.cpp | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/CPUGolfer.cpp b/samples/golf/src/golf/CPUGolfer.cpp index 7a9cf6556..b36bfcc33 100644 --- a/samples/golf/src/golf/CPUGolfer.cpp +++ b/samples/golf/src/golf/CPUGolfer.cpp @@ -275,6 +275,7 @@ void CPUGolfer::activate(glm::vec3 target, glm::vec3 fallback, bool puttFromTee) break; case TerrainID::Water: case TerrainID::Scrub: + case TerrainID::Bunker: case TerrainID::Stone: if (cro::Util::Random::value(0, 9) > Stat[CPUStat::MistakeLikelyhood]) { @@ -385,7 +386,8 @@ void CPUGolfer::setPredictionResult(glm::vec3 result, std::int32_t terrain) { if (m_retargetCount < (MaxRetargets - (MaxRetargets - getSkillIndex())) && (terrain == TerrainID::Water - || terrain == TerrainID::Scrub)) + || terrain == TerrainID::Scrub + || terrain == TerrainID::Bunker)) { const auto& Stat = CPUStats[m_cpuProfileIndices[m_activePlayer.client * ConstVal::MaxPlayers + m_activePlayer.player]]; diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index ab6ecc983..d18af1666 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -6511,7 +6511,15 @@ void GolfState::setCurrentPlayer(const ActivePlayer& player) } else { - m_cpuGolfer.activate(target, m_holeData[m_currentHole].target, m_holeData[m_currentHole].puttFromTee); + //aim for whichever is closer (target or pin) + if (glm::length2(target - player.position) < glm::length2(m_holeData[m_currentHole].pin - player.position)) + { + m_cpuGolfer.activate(target, m_holeData[m_currentHole].target, m_holeData[m_currentHole].puttFromTee); + } + else + { + m_cpuGolfer.activate(m_holeData[m_currentHole].pin, target, m_holeData[m_currentHole].puttFromTee); + } } #ifdef CRO_DEBUG_ //CPUTarget.getComponent().setPosition(m_cpuGolfer.getTarget()); From de6d4865876ae10c4da022a3574f117b86dab194 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 13:11:36 +0100 Subject: [PATCH 070/233] refresh mugshot image data when profile has been updated --- samples/golf/src/golf/MenuAvatars.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/samples/golf/src/golf/MenuAvatars.cpp b/samples/golf/src/golf/MenuAvatars.cpp index 39cda9c27..bad1e1b20 100644 --- a/samples/golf/src/golf/MenuAvatars.cpp +++ b/samples/golf/src/golf/MenuAvatars.cpp @@ -321,7 +321,7 @@ void MenuState::createAvatarMenu(cro::Entity parent) auto showAvatar = [&, mugshot](std::size_t profileIndex) mutable { - const auto& profile = m_profileData.playerProfiles[profileIndex]; + auto& profile = m_profileData.playerProfiles[profileIndex]; auto idx = indexFromAvatarID(profile.skinID); //TODO if the index is the same as the model already shown @@ -358,6 +358,20 @@ void MenuState::createAvatarMenu(cro::Entity parent) m_playerAvatars[idx].previewModel.getComponent().setMaterialProperty(0, "u_diffuseMap", m_profileTextures[profileIndex].getTextureID()); m_playerAvatars[idx].previewModel.getComponent().setHidden(false); + + + //reload the mugshot data - TODO how do we do this only if + //the data changed? + if (!profile.mugshot.empty()) + { + m_profileTextures[profileIndex].setMugshot(profile.mugshot); + + if (auto img = cropAvatarImage(profile.mugshot); img.getPixelData()) + { + //profile.mugshotData = std::move(img); + m_sharedData.localConnectionData.playerData[m_rosterMenu.activeIndex].mugshotData = std::move(img); + } + } if (m_profileTextures[profileIndex].getMugshot()) { From 7405276918110e42147ed345f485ce78fdddbed3 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 14:13:58 +0100 Subject: [PATCH 071/233] mark scorecard points as red if over par --- samples/golf/src/golf/GolfStateUI.cpp | 41 +++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index 278f92d19..2b44c4abf 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -2000,6 +2000,10 @@ void GolfState::createScoreboard() crop.height += 1.f; crop.bottom = -(bgCrop.height - 1.f) - pos.y; ent.getComponent().setCroppingArea(crop); + if (ent.hasComponent()) + { + ent.getComponent().getComponent().setCroppingArea(crop); //red text + } } //TODO these values need to be rounded to //the nearest scaled pixel ie nearest 2,3 or whatever viewScale is @@ -2045,6 +2049,17 @@ void GolfState::createScoreboard() ent.getComponent().move({ std::floor(scoreboardExpansion - offset), 0.f}); } }; + + + //child ent for red score text + auto f = m_uiScene.createEntity(); + f.addComponent().setPosition({ 0.f, -7.f, 0.f });; + f.addComponent(); + f.addComponent(font).setCharacterSize(UITextSize); + f.getComponent().setVerticalSpacing(LeaderboardTextSpacing); + f.getComponent().setFillColour(TextHighlightColour); + e.getComponent().addChild(f.getComponent()); + e.addComponent() = f; } scrollEnt.getComponent().addChild(e.getComponent()); @@ -2309,15 +2324,25 @@ void GolfState::updateScoreboard() holeNumber += 9; } std::string scoreString = std::to_string(holeNumber) + "\n" + std::to_string(m_holeData[i - 1].par); + std::string redScoreString = "\n"; for (auto j = 0u; j < playerCount; ++j) { scoreString += "\n"; + redScoreString += "\n"; auto s = scores[j].holes[i - 1]; if (s) { - scoreString += std::to_string(s); + if (s > m_holeData[i - 1].par) + { + //add to red column + redScoreString += std::to_string(s); + } + else + { + scoreString += std::to_string(s); + } } } @@ -2326,25 +2351,37 @@ void GolfState::updateScoreboard() for (auto j = 0u; j < 16 - playerCount; ++j) { scoreString += "\n"; + redScoreString += "\n"; } auto holeIndex = (i + MaxCols) - 1; if (holeIndex < m_holeData.size()) { scoreString += "\n\n" + std::to_string(i + MaxCols) + "\n" + std::to_string(m_holeData[holeIndex].par); + redScoreString += "\n\n\n"; for (auto j = 0u; j < playerCount; ++j) { scoreString += "\n"; + redScoreString += "\n"; auto s = scores[j].holes[holeIndex]; if (s) { - scoreString += std::to_string(s); + if (s > m_holeData[holeIndex].par) + { + //add to red column + redScoreString += std::to_string(s); + } + else + { + scoreString += std::to_string(s); + } } } } } ents[i].getComponent().setString(scoreString); + ents[i].getComponent().getComponent().setString(redScoreString); //yes there's an entity as a component. leaderboardEntries.emplace_back(glm::vec3(ents[i].getComponent().absolutePosition - glm::vec2(ColumnMargin, -UITextPosV), 0.f), scoreString); } From a8d662518b30a0bbb3eb84585647e55ab7a2b444 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 14:40:56 +0100 Subject: [PATCH 072/233] add particle effect for hitting pin on driving range --- samples/golf/src/golf/DrivingState.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/samples/golf/src/golf/DrivingState.cpp b/samples/golf/src/golf/DrivingState.cpp index c44fb655d..1715f83f9 100644 --- a/samples/golf/src/golf/DrivingState.cpp +++ b/samples/golf/src/golf/DrivingState.cpp @@ -552,6 +552,16 @@ void DrivingState::handleMessage(const cro::Message& msg) { //display a message with score showMessage(glm::length(PlayerPosition - data.position)); + + //does fireworks at pin + if (data.type == GolfBallEvent::Holed) + { + auto* msg2 = postMessage(MessageID::GolfMessage); + msg2->type = GolfEvent::HoleInOne; + msg2->position = data.position; + + //TODO raise achievement + } } } break; From 247e33903669a79ff6d34e07808915b36b7d40e4 Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 14:57:57 +0100 Subject: [PATCH 073/233] apply imgui theme --- samples/golf/golf.vcxproj | 1 + samples/golf/golf.vcxproj.filters | 3 +++ samples/golf/src/GolfGame.cpp | 13 ++++--------- samples/golf/src/ImTheme.hpp | 32 ++++++++++++++++--------------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/samples/golf/golf.vcxproj b/samples/golf/golf.vcxproj index 241295f51..cdfefda47 100644 --- a/samples/golf/golf.vcxproj +++ b/samples/golf/golf.vcxproj @@ -653,6 +653,7 @@ + diff --git a/samples/golf/golf.vcxproj.filters b/samples/golf/golf.vcxproj.filters index d955b5d69..25b173036 100644 --- a/samples/golf/golf.vcxproj.filters +++ b/samples/golf/golf.vcxproj.filters @@ -731,6 +731,9 @@ Header Files\golf\server + + Header Files + diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index c7899644f..a1c51610f 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -62,6 +62,8 @@ source distribution. #include "Achievements.hpp" #include "golf/Clubs.hpp" +#include "ImTheme.hpp" + #include #include @@ -551,15 +553,6 @@ bool GolfGame::initialise() { cro::Util::String::parseURL(Social::WebURL); } - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::Text("In memory of Liesbeth Penning"); }); registerCommand("log_benchmark", @@ -725,6 +718,8 @@ bool GolfGame::initialise() m_stateStack.pushState(StateID::SplashScreen); #endif + applyImGuiStyle(); + return true; } diff --git a/samples/golf/src/ImTheme.hpp b/samples/golf/src/ImTheme.hpp index 3a3c877d1..5ac7a08ca 100644 --- a/samples/golf/src/ImTheme.hpp +++ b/samples/golf/src/ImTheme.hpp @@ -1,42 +1,44 @@ -static inline void SetupImGuiStyle() +#include + +static inline void applyImGuiStyle() { //golf style ImGuiStyle& style = ImGui::GetStyle(); style.Alpha = 1.0f; - style.DisabledAlpha = 0.6000000238418579f; + //style.DisabledAlpha = 0.6000000238418579f; style.WindowPadding = ImVec2(8.0f, 8.0f); - style.WindowRounding = 0.0f; + style.WindowRounding = 4.0f; style.WindowBorderSize = 1.0f; style.WindowMinSize = ImVec2(32.0f, 32.0f); - style.WindowTitleAlign = ImVec2(0.5f, 0.2000000029802322f); + style.WindowTitleAlign = ImVec2(0.5f, 0.2f); style.WindowMenuButtonPosition = ImGuiDir_Left; style.ChildRounding = 0.0f; style.ChildBorderSize = 1.0f; - style.PopupRounding = 5.199999809265137f; + style.PopupRounding = 5.2f; style.PopupBorderSize = 1.0f; - style.FramePadding = ImVec2(3.900000095367432f, 3.0f); + style.FramePadding = ImVec2(3.9f, 3.0f); style.FrameRounding = 7.5f; style.FrameBorderSize = 0.0f; style.ItemSpacing = ImVec2(8.0f, 4.0f); style.ItemInnerSpacing = ImVec2(4.0f, 4.0f); - style.CellPadding = ImVec2(4.0f, 2.0f); + //style.CellPadding = ImVec2(4.0f, 2.0f); style.IndentSpacing = 21.0f; style.ColumnsMinSpacing = 6.0f; style.ScrollbarSize = 20.0f; style.ScrollbarRounding = 2.5f; style.GrabMinSize = 10.0f; - style.GrabRounding = 3.799999952316284f; + style.GrabRounding = 3.8f; style.TabRounding = 4.0f; style.TabBorderSize = 0.0f; style.TabMinWidthForCloseButton = 0.0f; style.ColorButtonPosition = ImGuiDir_Right; - style.ButtonTextAlign = ImVec2(0.6000000238418579f, 0.5f); + style.ButtonTextAlign = ImVec2(0.6f, 0.5f); style.SelectableTextAlign = ImVec2(0.0f, 0.0f); style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 0.9725490212440491f, 0.8823529481887817f, 1.0f); style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.7843137383460999f, 0.7215686440467834f, 0.6235294342041016f, 1.0f); - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.3137255012989044f, 0.1568627506494522f, 0.1843137294054031f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.3137255012989044f, 0.1568627506494522f, 0.1843137294054031f, 0.9f); style.Colors[ImGuiCol_ChildBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); style.Colors[ImGuiCol_PopupBg] = ImVec4(0.3137255012989044f, 0.1568627506494522f, 0.1843137294054031f, 1.0f); style.Colors[ImGuiCol_Border] = ImVec4(0.3960784375667572f, 0.2627451121807098f, 0.1843137294054031f, 1.0f); @@ -76,11 +78,11 @@ static inline void SetupImGuiStyle() style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.9490196108818054f, 0.8117647171020508f, 0.3607843220233917f, 1.0f); style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.9490196108818054f, 0.8117647171020508f, 0.3607843220233917f, 1.0f); style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.9254902005195618f, 0.4666666686534882f, 0.239215686917305f, 1.0f); - style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.4941176474094391f, 0.4274509847164154f, 0.2156862765550613f, 1.0f); - style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.4941176474094391f, 0.4274509847164154f, 0.2156862765550613f, 1.0f); - style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.2588235437870026f, 0.2588235437870026f, 0.2784313857555389f, 1.0f); - style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 0.9725490212440491f, 0.8823529481887817f, 1.0f); + //style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.4941176474094391f, 0.4274509847164154f, 0.2156862765550613f, 1.0f); + //style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.4941176474094391f, 0.4274509847164154f, 0.2156862765550613f, 1.0f); + //style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.2588235437870026f, 0.2588235437870026f, 0.2784313857555389f, 1.0f); + //style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); + //style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 0.9725490212440491f, 0.8823529481887817f, 1.0f); style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.4313725531101227f, 0.7450980544090271f, 0.4392156898975372f, 1.0f); style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.9490196108818054f, 0.8117647171020508f, 0.3607843220233917f, 1.0f); style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.4470588266849518f, 0.4470588266849518f, 0.8980392217636108f, 0.800000011920929f); From 049a5905b17c49d76e89253c36710b3654f31ada Mon Sep 17 00:00:00 2001 From: fallahn Date: Fri, 23 Jun 2023 15:33:45 +0100 Subject: [PATCH 074/233] integrate implot library --- crogine/include/crogine/gui/Gui.hpp | 1 + crogine/include/crogine/gui/detail/implot.h | 1292 +++++ crogine/src/CMakeLists.txt | 3 + crogine/src/core/App.cpp | 2 + crogine/src/imgui/implot.cpp | 5752 +++++++++++++++++++ crogine/src/imgui/implot_demo.cpp | 2450 ++++++++ crogine/src/imgui/implot_internal.h | 1667 ++++++ crogine/src/imgui/implot_items.cpp | 2692 +++++++++ samples/golf/src/golf/MenuState.cpp | 4 + windows/crowin.vcxproj | 4 + windows/crowin.vcxproj.filters | 12 + 11 files changed, 13879 insertions(+) create mode 100644 crogine/include/crogine/gui/detail/implot.h create mode 100644 crogine/src/imgui/implot.cpp create mode 100644 crogine/src/imgui/implot_demo.cpp create mode 100644 crogine/src/imgui/implot_internal.h create mode 100644 crogine/src/imgui/implot_items.cpp diff --git a/crogine/include/crogine/gui/Gui.hpp b/crogine/include/crogine/gui/Gui.hpp index 9549f0101..223a2be79 100644 --- a/crogine/include/crogine/gui/Gui.hpp +++ b/crogine/include/crogine/gui/Gui.hpp @@ -32,6 +32,7 @@ source distribution. #include "detail/imgui.h" #include "detail/imgui_stdlib.h" #include "detail/ImGuizmo.h" +#include "detail/implot.h" #include #include diff --git a/crogine/include/crogine/gui/detail/implot.h b/crogine/include/crogine/gui/detail/implot.h new file mode 100644 index 000000000..502621e56 --- /dev/null +++ b/crogine/include/crogine/gui/detail/implot.h @@ -0,0 +1,1292 @@ +// MIT License + +// Copyright (c) 2022 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.14 + +// Table of Contents: +// +// [SECTION] Macros and Defines +// [SECTION] Enums and Types +// [SECTION] Callbacks +// [SECTION] Contexts +// [SECTION] Begin/End Plot +// [SECTION] Begin/End Subplot +// [SECTION] Setup +// [SECTION] SetNext +// [SECTION] Plot Items +// [SECTION] Plot Tools +// [SECTION] Plot Utils +// [SECTION] Legend Utils +// [SECTION] Drag and Drop +// [SECTION] Styling +// [SECTION] Colormaps +// [SECTION] Input Mapping +// [SECTION] Miscellaneous +// [SECTION] Demo +// [SECTION] Obsolete API + +#pragma once +#include "imgui.h" + +//----------------------------------------------------------------------------- +// [SECTION] Macros and Defines +//----------------------------------------------------------------------------- + +// Define attributes of all API symbols declarations (e.g. for DLL under Windows) +// Using ImPlot via a shared library is not recommended, because we don't guarantee +// backward nor forward ABI compatibility and also function call overhead. If you +// do use ImPlot as a DLL, be sure to call SetImGuiContext (see Miscellanous section). +#ifndef IMPLOT_API +#define IMPLOT_API IMGUI_API +#endif + +// ImPlot version string. +#define IMPLOT_VERSION "0.14" +// Indicates variable should deduced automatically. +#define IMPLOT_AUTO -1 +// Special color used to indicate that a color should be deduced automatically. +#define IMPLOT_AUTO_COL ImVec4(0,0,0,-1) +// Macro for templated plotting functions; keeps header clean. +#define IMPLOT_TMP template IMPLOT_API + +//----------------------------------------------------------------------------- +// [SECTION] Enums and Types +//----------------------------------------------------------------------------- + +// Forward declarations +struct ImPlotContext; // ImPlot context (opaque struct, see implot_internal.h) + +// Enums/Flags +typedef int ImAxis; // -> enum ImAxis_ +typedef int ImPlotFlags; // -> enum ImPlotFlags_ +typedef int ImPlotAxisFlags; // -> enum ImPlotAxisFlags_ +typedef int ImPlotSubplotFlags; // -> enum ImPlotSubplotFlags_ +typedef int ImPlotLegendFlags; // -> enum ImPlotLegendFlags_ +typedef int ImPlotMouseTextFlags; // -> enum ImPlotMouseTextFlags_ +typedef int ImPlotDragToolFlags; // -> ImPlotDragToolFlags_ +typedef int ImPlotColormapScaleFlags; // -> ImPlotColormapScaleFlags_ + +typedef int ImPlotItemFlags; // -> ImPlotItemFlags_ +typedef int ImPlotLineFlags; // -> ImPlotLineFlags_ +typedef int ImPlotScatterFlags; // -> ImPlotScatterFlags +typedef int ImPlotStairsFlags; // -> ImPlotStairsFlags_ +typedef int ImPlotShadedFlags; // -> ImPlotShadedFlags_ +typedef int ImPlotBarsFlags; // -> ImPlotBarsFlags_ +typedef int ImPlotBarGroupsFlags; // -> ImPlotBarGroupsFlags_ +typedef int ImPlotErrorBarsFlags; // -> ImPlotErrorBarsFlags_ +typedef int ImPlotStemsFlags; // -> ImPlotStemsFlags_ +typedef int ImPlotInfLinesFlags; // -> ImPlotInfLinesFlags_ +typedef int ImPlotPieChartFlags; // -> ImPlotPieChartFlags_ +typedef int ImPlotHeatmapFlags; // -> ImPlotHeatmapFlags_ +typedef int ImPlotHistogramFlags; // -> ImPlotHistogramFlags_ +typedef int ImPlotDigitalFlags; // -> ImPlotDigitalFlags_ +typedef int ImPlotImageFlags; // -> ImPlotImageFlags_ +typedef int ImPlotTextFlags; // -> ImPlotTextFlags_ +typedef int ImPlotDummyFlags; // -> ImPlotDummyFlags_ + +typedef int ImPlotCond; // -> enum ImPlotCond_ +typedef int ImPlotCol; // -> enum ImPlotCol_ +typedef int ImPlotStyleVar; // -> enum ImPlotStyleVar_ +typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotMarker; // -> enum ImPlotMarker_ +typedef int ImPlotColormap; // -> enum ImPlotColormap_ +typedef int ImPlotLocation; // -> enum ImPlotLocation_ +typedef int ImPlotBin; // -> enum ImPlotBin_ + +// Axis indices. The values assigned may change; NEVER hardcode these. +enum ImAxis_ { + // horizontal axes + ImAxis_X1 = 0, // enabled by default + ImAxis_X2, // disabled by default + ImAxis_X3, // disabled by default + // vertical axes + ImAxis_Y1, // enabled by default + ImAxis_Y2, // disabled by default + ImAxis_Y3, // disabled by default + // bookeeping + ImAxis_COUNT +}; + +// Options for plots (see BeginPlot). +enum ImPlotFlags_ { + ImPlotFlags_None = 0, // default + ImPlotFlags_NoTitle = 1 << 0, // the plot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. "##MyPlot") + ImPlotFlags_NoLegend = 1 << 1, // the legend will not be displayed + ImPlotFlags_NoMouseText = 1 << 2, // the mouse position, in plot coordinates, will not be displayed inside of the plot + ImPlotFlags_NoInputs = 1 << 3, // the user will not be able to interact with the plot + ImPlotFlags_NoMenus = 1 << 4, // the user will not be able to open context menus + ImPlotFlags_NoBoxSelect = 1 << 5, // the user will not be able to box-select + ImPlotFlags_NoChild = 1 << 6, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) + ImPlotFlags_NoFrame = 1 << 7, // the ImGui frame will not be rendered + ImPlotFlags_Equal = 1 << 8, // x and y axes pairs will be constrained to have the same units/pixel + ImPlotFlags_Crosshairs = 1 << 9, // the default mouse cursor will be replaced with a crosshair when hovered + ImPlotFlags_CanvasOnly = ImPlotFlags_NoTitle | ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText +}; + +// Options for plot axes (see SetupAxis). +enum ImPlotAxisFlags_ { + ImPlotAxisFlags_None = 0, // default + ImPlotAxisFlags_NoLabel = 1 << 0, // the axis label will not be displayed (axis labels are also hidden if the supplied string name is nullptr) + ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed + ImPlotAxisFlags_NoInitialFit = 1 << 4, // axis will not be initially fit to data extents on the first rendered frame + ImPlotAxisFlags_NoMenus = 1 << 5, // the user will not be able to open context menus with right-click + ImPlotAxisFlags_NoSideSwitch = 1 << 6, // the user will not be able to switch the axis side by dragging it + ImPlotAxisFlags_NoHighlight = 1 << 7, // the axis will not have its background highlighted when hovered or held + ImPlotAxisFlags_Opposite = 1 << 8, // axis ticks and labels will be rendered on the conventionally opposite side (i.e, right or top) + ImPlotAxisFlags_Foreground = 1 << 9, // grid lines will be displayed in the foreground (i.e. on top of data) instead of the background + ImPlotAxisFlags_Invert = 1 << 10, // the axis will be inverted + ImPlotAxisFlags_AutoFit = 1 << 11, // axis will be auto-fitting to data extents + ImPlotAxisFlags_RangeFit = 1 << 12, // axis will only fit points if the point is in the visible range of the **orthogonal** axis + ImPlotAxisFlags_PanStretch = 1 << 13, // panning in a locked or constrained state will cause the axis to stretch if possible + ImPlotAxisFlags_LockMin = 1 << 14, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 15, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, + ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels, + ImPlotAxisFlags_AuxDefault = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite +}; + +// Options for subplots (see BeginSubplot) +enum ImPlotSubplotFlags_ { + ImPlotSubplotFlags_None = 0, // default + ImPlotSubplotFlags_NoTitle = 1 << 0, // the subplot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. "##MySubplot") + ImPlotSubplotFlags_NoLegend = 1 << 1, // the legend will not be displayed (only applicable if ImPlotSubplotFlags_ShareItems is enabled) + ImPlotSubplotFlags_NoMenus = 1 << 2, // the user will not be able to open context menus with right-click + ImPlotSubplotFlags_NoResize = 1 << 3, // resize splitters between subplot cells will be not be provided + ImPlotSubplotFlags_NoAlign = 1 << 4, // subplot edges will not be aligned vertically or horizontally + ImPlotSubplotFlags_ShareItems = 1 << 5, // items across all subplots will be shared and rendered into a single legend entry + ImPlotSubplotFlags_LinkRows = 1 << 6, // link the y-axis limits of all plots in each row (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkCols = 1 << 7, // link the x-axis limits of all plots in each column (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkAllX = 1 << 8, // link the x-axis limits in every plot in the subplot (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkAllY = 1 << 9, // link the y-axis limits in every plot in the subplot (does not apply to auxiliary axes) + ImPlotSubplotFlags_ColMajor = 1 << 10 // subplots are added in column major order instead of the default row major order +}; + +// Options for legends (see SetupLegend) +enum ImPlotLegendFlags_ { + ImPlotLegendFlags_None = 0, // default + ImPlotLegendFlags_NoButtons = 1 << 0, // legend icons will not function as hide/show buttons + ImPlotLegendFlags_NoHighlightItem = 1 << 1, // plot items will not be highlighted when their legend entry is hovered + ImPlotLegendFlags_NoHighlightAxis = 1 << 2, // axes will not be highlighted when legend entries are hovered (only relevant if x/y-axis count > 1) + ImPlotLegendFlags_NoMenus = 1 << 3, // the user will not be able to open context menus with right-click + ImPlotLegendFlags_Outside = 1 << 4, // legend will be rendered outside of the plot area + ImPlotLegendFlags_Horizontal = 1 << 5, // legend entries will be displayed horizontally + ImPlotLegendFlags_Sort = 1 << 6, // legend entries will be displayed in alphabetical order +}; + +// Options for mouse hover text (see SetupMouseText) +enum ImPlotMouseTextFlags_ { + ImPlotMouseTextFlags_None = 0, // default + ImPlotMouseTextFlags_NoAuxAxes = 1 << 0, // only show the mouse position for primary axes + ImPlotMouseTextFlags_NoFormat = 1 << 1, // axes label formatters won't be used to render text + ImPlotMouseTextFlags_ShowAlways = 1 << 2, // always display mouse position even if plot not hovered +}; + +// Options for DragPoint, DragLine, DragRect +enum ImPlotDragToolFlags_ { + ImPlotDragToolFlags_None = 0, // default + ImPlotDragToolFlags_NoCursors = 1 << 0, // drag tools won't change cursor icons when hovered or held + ImPlotDragToolFlags_NoFit = 1 << 1, // the drag tool won't be considered for plot fits + ImPlotDragToolFlags_NoInputs = 1 << 2, // lock the tool from user inputs + ImPlotDragToolFlags_Delayed = 1 << 3, // tool rendering will be delayed one frame; useful when applying position-constraints +}; + +// Flags for ColormapScale +enum ImPlotColormapScaleFlags_ { + ImPlotColormapScaleFlags_None = 0, // default + ImPlotColormapScaleFlags_NoLabel = 1 << 0, // the colormap axis label will not be displayed + ImPlotColormapScaleFlags_Opposite = 1 << 1, // render the colormap label and tick labels on the opposite side + ImPlotColormapScaleFlags_Invert = 1 << 2, // invert the colormap bar and axis scale (this only affects rendering; if you only want to reverse the scale mapping, make scale_min > scale_max) +}; + +// Flags for ANY PlotX function +enum ImPlotItemFlags_ { + ImPlotItemFlags_None = 0, + ImPlotItemFlags_NoLegend = 1 << 0, // the item won't have a legend entry displayed + ImPlotItemFlags_NoFit = 1 << 1, // the item won't be considered for plot fits +}; + +// Flags for PlotLine +enum ImPlotLineFlags_ { + ImPlotLineFlags_None = 0, // default + ImPlotLineFlags_Segments = 1 << 10, // a line segment will be rendered from every two consecutive points + ImPlotLineFlags_Loop = 1 << 11, // the last and first point will be connected to form a closed loop + ImPlotLineFlags_SkipNaN = 1 << 12, // NaNs values will be skipped instead of rendered as missing data + ImPlotLineFlags_NoClip = 1 << 13, // markers (if displayed) on the edge of a plot will not be clipped + ImPlotLineFlags_Shaded = 1 << 14, // a filled region between the line and horizontal origin will be rendered; use PlotShaded for more advanced cases +}; + +// Flags for PlotScatter +enum ImPlotScatterFlags_ { + ImPlotScatterFlags_None = 0, // default + ImPlotScatterFlags_NoClip = 1 << 10, // markers on the edge of a plot will not be clipped +}; + +// Flags for PlotStairs +enum ImPlotStairsFlags_ { + ImPlotStairsFlags_None = 0, // default + ImPlotStairsFlags_PreStep = 1 << 10, // the y value is continued constantly to the left from every x position, i.e. the interval (x[i-1], x[i]] has the value y[i] + ImPlotStairsFlags_Shaded = 1 << 11 // a filled region between the stairs and horizontal origin will be rendered; use PlotShaded for more advanced cases +}; + +// Flags for PlotShaded (placeholder) +enum ImPlotShadedFlags_ { + ImPlotShadedFlags_None = 0 // default +}; + +// Flags for PlotBars +enum ImPlotBarsFlags_ { + ImPlotBarsFlags_None = 0, // default + ImPlotBarsFlags_Horizontal = 1 << 10, // bars will be rendered horizontally on the current y-axis +}; + +// Flags for PlotBarGroups +enum ImPlotBarGroupsFlags_ { + ImPlotBarGroupsFlags_None = 0, // default + ImPlotBarGroupsFlags_Horizontal = 1 << 10, // bar groups will be rendered horizontally on the current y-axis + ImPlotBarGroupsFlags_Stacked = 1 << 11, // items in a group will be stacked on top of each other +}; + +// Flags for PlotErrorBars +enum ImPlotErrorBarsFlags_ { + ImPlotErrorBarsFlags_None = 0, // default + ImPlotErrorBarsFlags_Horizontal = 1 << 10, // error bars will be rendered horizontally on the current y-axis +}; + +// Flags for PlotStems +enum ImPlotStemsFlags_ { + ImPlotStemsFlags_None = 0, // default + ImPlotStemsFlags_Horizontal = 1 << 10, // stems will be rendered horizontally on the current y-axis +}; + +// Flags for PlotInfLines +enum ImPlotInfLinesFlags_ { + ImPlotInfLinesFlags_None = 0, // default + ImPlotInfLinesFlags_Horizontal = 1 << 10 // lines will be rendered horizontally on the current y-axis +}; + +// Flags for PlotPieChart +enum ImPlotPieChartFlags_ { + ImPlotPieChartFlags_None = 0, // default + ImPlotPieChartFlags_Normalize = 1 << 10 // force normalization of pie chart values (i.e. always make a full circle if sum < 0) +}; + +// Flags for PlotHeatmap +enum ImPlotHeatmapFlags_ { + ImPlotHeatmapFlags_None = 0, // default + ImPlotHeatmapFlags_ColMajor = 1 << 10, // data will be read in column major order +}; + +// Flags for PlotHistogram and PlotHistogram2D +enum ImPlotHistogramFlags_ { + ImPlotHistogramFlags_None = 0, // default + ImPlotHistogramFlags_Horizontal = 1 << 10, // histogram bars will be rendered horizontally (not supported by PlotHistogram2D) + ImPlotHistogramFlags_Cumulative = 1 << 11, // each bin will contain its count plus the counts of all previous bins (not supported by PlotHistogram2D) + ImPlotHistogramFlags_Density = 1 << 12, // counts will be normalized, i.e. the PDF will be visualized, or the CDF will be visualized if Cumulative is also set + ImPlotHistogramFlags_NoOutliers = 1 << 13, // exclude values outside the specifed histogram range from the count toward normalizing and cumulative counts + ImPlotHistogramFlags_ColMajor = 1 << 14 // data will be read in column major order (not supported by PlotHistogram) +}; + +// Flags for PlotDigital (placeholder) +enum ImPlotDigitalFlags_ { + ImPlotDigitalFlags_None = 0 // default +}; + +// Flags for PlotImage (placeholder) +enum ImPlotImageFlags_ { + ImPlotImageFlags_None = 0 // default +}; + +// Flags for PlotText +enum ImPlotTextFlags_ { + ImPlotTextFlags_None = 0, // default + ImPlotTextFlags_Vertical = 1 << 10 // text will be rendered vertically +}; + +// Flags for PlotDummy (placeholder) +enum ImPlotDummyFlags_ { + ImPlotDummyFlags_None = 0 // default +}; + +// Represents a condition for SetupAxisLimits etc. (same as ImGuiCond, but we only support a subset of those enums) +enum ImPlotCond_ +{ + ImPlotCond_None = ImGuiCond_None, // No condition (always set the variable), same as _Always + ImPlotCond_Always = ImGuiCond_Always, // No condition (always set the variable) + ImPlotCond_Once = ImGuiCond_Once, // Set the variable once per runtime session (only the first call will succeed) +}; + +// Plot styling colors. +enum ImPlotCol_ { + // item styling colors + ImPlotCol_Line, // plot line/outline color (defaults to next unused color in current colormap) + ImPlotCol_Fill, // plot fill color for bars (defaults to the current line color) + ImPlotCol_MarkerOutline, // marker outline color (defaults to the current line color) + ImPlotCol_MarkerFill, // marker fill color (defaults to the current line color) + ImPlotCol_ErrorBar, // error bar color (defaults to ImGuiCol_Text) + // plot styling colors + ImPlotCol_FrameBg, // plot frame background color (defaults to ImGuiCol_FrameBg) + ImPlotCol_PlotBg, // plot area background color (defaults to ImGuiCol_WindowBg) + ImPlotCol_PlotBorder, // plot area border color (defaults to ImGuiCol_Border) + ImPlotCol_LegendBg, // legend background color (defaults to ImGuiCol_PopupBg) + ImPlotCol_LegendBorder, // legend border color (defaults to ImPlotCol_PlotBorder) + ImPlotCol_LegendText, // legend text color (defaults to ImPlotCol_InlayText) + ImPlotCol_TitleText, // plot title text color (defaults to ImGuiCol_Text) + ImPlotCol_InlayText, // color of text appearing inside of plots (defaults to ImGuiCol_Text) + ImPlotCol_AxisText, // axis label and tick lables color (defaults to ImGuiCol_Text) + ImPlotCol_AxisGrid, // axis grid color (defaults to 25% ImPlotCol_AxisText) + ImPlotCol_AxisTick, // axis tick color (defaults to AxisGrid) + ImPlotCol_AxisBg, // background color of axis hover region (defaults to transparent) + ImPlotCol_AxisBgHovered, // axis hover color (defaults to ImGuiCol_ButtonHovered) + ImPlotCol_AxisBgActive, // axis active color (defaults to ImGuiCol_ButtonActive) + ImPlotCol_Selection, // box-selection color (defaults to yellow) + ImPlotCol_Crosshairs, // crosshairs color (defaults to ImPlotCol_PlotBorder) + ImPlotCol_COUNT +}; + +// Plot styling variables. +enum ImPlotStyleVar_ { + // item styling variables + ImPlotStyleVar_LineWeight, // float, plot item line weight in pixels + ImPlotStyleVar_Marker, // int, marker specification + ImPlotStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius") + ImPlotStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels + ImPlotStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills + ImPlotStyleVar_ErrorBarSize, // float, error bar whisker width in pixels + ImPlotStyleVar_ErrorBarWeight, // float, error bar whisker weight in pixels + ImPlotStyleVar_DigitalBitHeight, // float, digital channels bit height (at 1) in pixels + ImPlotStyleVar_DigitalBitGap, // float, digital channels bit padding gap in pixels + // plot styling variables + ImPlotStyleVar_PlotBorderSize, // float, thickness of border around plot area + ImPlotStyleVar_MinorAlpha, // float, alpha multiplier applied to minor axis grid lines + ImPlotStyleVar_MajorTickLen, // ImVec2, major tick lengths for X and Y axes + ImPlotStyleVar_MinorTickLen, // ImVec2, minor tick lengths for X and Y axes + ImPlotStyleVar_MajorTickSize, // ImVec2, line thickness of major ticks + ImPlotStyleVar_MinorTickSize, // ImVec2, line thickness of minor ticks + ImPlotStyleVar_MajorGridSize, // ImVec2, line thickness of major grid lines + ImPlotStyleVar_MinorGridSize, // ImVec2, line thickness of minor grid lines + ImPlotStyleVar_PlotPadding, // ImVec2, padding between widget frame and plot area, labels, or outside legends (i.e. main padding) + ImPlotStyleVar_LabelPadding, // ImVec2, padding between axes labels, tick labels, and plot edge + ImPlotStyleVar_LegendPadding, // ImVec2, legend padding from plot edges + ImPlotStyleVar_LegendInnerPadding, // ImVec2, legend inner padding from legend edges + ImPlotStyleVar_LegendSpacing, // ImVec2, spacing between legend entries + ImPlotStyleVar_MousePosPadding, // ImVec2, padding between plot edge and interior info text + ImPlotStyleVar_AnnotationPadding, // ImVec2, text padding around annotation labels + ImPlotStyleVar_FitPadding, // ImVec2, additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) + ImPlotStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot + ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk + ImPlotStyleVar_COUNT +}; + +// Axis scale +enum ImPlotScale_ { + ImPlotScale_Linear = 0, // default linear scale + ImPlotScale_Time, // date/time scale + ImPlotScale_Log10, // base 10 logartithmic scale + ImPlotScale_SymLog, // symmetric log scale +}; + +// Marker specifications. +enum ImPlotMarker_ { + ImPlotMarker_None = -1, // no marker + ImPlotMarker_Circle, // a circle marker (default) + ImPlotMarker_Square, // a square maker + ImPlotMarker_Diamond, // a diamond marker + ImPlotMarker_Up, // an upward-pointing triangle marker + ImPlotMarker_Down, // an downward-pointing triangle marker + ImPlotMarker_Left, // an leftward-pointing triangle marker + ImPlotMarker_Right, // an rightward-pointing triangle marker + ImPlotMarker_Cross, // a cross marker (not fillable) + ImPlotMarker_Plus, // a plus marker (not fillable) + ImPlotMarker_Asterisk, // a asterisk marker (not fillable) + ImPlotMarker_COUNT +}; + +// Built-in colormaps +enum ImPlotColormap_ { + ImPlotColormap_Deep = 0, // a.k.a. seaborn deep (qual=true, n=10) (default) + ImPlotColormap_Dark = 1, // a.k.a. matplotlib "Set1" (qual=true, n=9 ) + ImPlotColormap_Pastel = 2, // a.k.a. matplotlib "Pastel1" (qual=true, n=9 ) + ImPlotColormap_Paired = 3, // a.k.a. matplotlib "Paired" (qual=true, n=12) + ImPlotColormap_Viridis = 4, // a.k.a. matplotlib "viridis" (qual=false, n=11) + ImPlotColormap_Plasma = 5, // a.k.a. matplotlib "plasma" (qual=false, n=11) + ImPlotColormap_Hot = 6, // a.k.a. matplotlib/MATLAB "hot" (qual=false, n=11) + ImPlotColormap_Cool = 7, // a.k.a. matplotlib/MATLAB "cool" (qual=false, n=11) + ImPlotColormap_Pink = 8, // a.k.a. matplotlib/MATLAB "pink" (qual=false, n=11) + ImPlotColormap_Jet = 9, // a.k.a. MATLAB "jet" (qual=false, n=11) + ImPlotColormap_Twilight = 10, // a.k.a. matplotlib "twilight" (qual=false, n=11) + ImPlotColormap_RdBu = 11, // red/blue, Color Brewer (qual=false, n=11) + ImPlotColormap_BrBG = 12, // brown/blue-green, Color Brewer (qual=false, n=11) + ImPlotColormap_PiYG = 13, // pink/yellow-green, Color Brewer (qual=false, n=11) + ImPlotColormap_Spectral = 14, // color spectrum, Color Brewer (qual=false, n=11) + ImPlotColormap_Greys = 15, // white/black (qual=false, n=2 ) +}; + +// Used to position items on a plot (e.g. legends, labels, etc.) +enum ImPlotLocation_ { + ImPlotLocation_Center = 0, // center-center + ImPlotLocation_North = 1 << 0, // top-center + ImPlotLocation_South = 1 << 1, // bottom-center + ImPlotLocation_West = 1 << 2, // center-left + ImPlotLocation_East = 1 << 3, // center-right + ImPlotLocation_NorthWest = ImPlotLocation_North | ImPlotLocation_West, // top-left + ImPlotLocation_NorthEast = ImPlotLocation_North | ImPlotLocation_East, // top-right + ImPlotLocation_SouthWest = ImPlotLocation_South | ImPlotLocation_West, // bottom-left + ImPlotLocation_SouthEast = ImPlotLocation_South | ImPlotLocation_East // bottom-right +}; + +// Enums for different automatic histogram binning methods (k = bin count or w = bin width) +enum ImPlotBin_ { + ImPlotBin_Sqrt = -1, // k = sqrt(n) + ImPlotBin_Sturges = -2, // k = 1 + log2(n) + ImPlotBin_Rice = -3, // k = 2 * cbrt(n) + ImPlotBin_Scott = -4, // w = 3.49 * sigma / cbrt(n) +}; + +// Double precision version of ImVec2 used by ImPlot. Extensible by end users. +struct ImPlotPoint { + double x, y; + ImPlotPoint() { x = y = 0.0; } + ImPlotPoint(double _x, double _y) { x = _x; y = _y; } + ImPlotPoint(const ImVec2& p) { x = p.x; y = p.y; } + double operator[] (size_t idx) const { return (&x)[idx]; } + double& operator[] (size_t idx) { return (&x)[idx]; } +#ifdef IMPLOT_POINT_CLASS_EXTRA + IMPLOT_POINT_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h + // to convert back and forth between your math types and ImPlotPoint. +#endif +}; + +// Range defined by a min/max value. +struct ImPlotRange { + double Min, Max; + ImPlotRange() { Min = 0; Max = 0; } + ImPlotRange(double _min, double _max) { Min = _min; Max = _max; } + bool Contains(double value) const { return value >= Min && value <= Max; } + double Size() const { return Max - Min; } + double Clamp(double value) const { return (value < Min) ? Min : (value > Max) ? Max : value; } +}; + +// Combination of two range limits for X and Y axes. Also an AABB defined by Min()/Max(). +struct ImPlotRect { + ImPlotRange X, Y; + ImPlotRect() { } + ImPlotRect(double x_min, double x_max, double y_min, double y_max) { X.Min = x_min; X.Max = x_max; Y.Min = y_min; Y.Max = y_max; } + bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } + bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } + ImPlotPoint Size() const { return ImPlotPoint(X.Size(), Y.Size()); } + ImPlotPoint Clamp(const ImPlotPoint& p) { return Clamp(p.x, p.y); } + ImPlotPoint Clamp(double x, double y) { return ImPlotPoint(X.Clamp(x),Y.Clamp(y)); } + ImPlotPoint Min() const { return ImPlotPoint(X.Min, Y.Min); } + ImPlotPoint Max() const { return ImPlotPoint(X.Max, Y.Max); } +}; + +// Plot style structure +struct ImPlotStyle { + // item styling variables + float LineWeight; // = 1, item line weight in pixels + int Marker; // = ImPlotMarker_None, marker specification + float MarkerSize; // = 4, marker size in pixels (roughly the marker's "radius") + float MarkerWeight; // = 1, outline weight of markers in pixels + float FillAlpha; // = 1, alpha modifier applied to plot fills + float ErrorBarSize; // = 5, error bar whisker width in pixels + float ErrorBarWeight; // = 1.5, error bar whisker weight in pixels + float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels + float DigitalBitGap; // = 4, digital channels bit padding gap in pixels + // plot styling variables + float PlotBorderSize; // = 1, line thickness of border around plot area + float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines + ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes + ImVec2 MinorTickLen; // = 5,5 minor tick lengths for X and Y axes + ImVec2 MajorTickSize; // = 1,1 line thickness of major ticks + ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks + ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines + ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines + ImVec2 PlotPadding; // = 10,10 padding between widget frame and plot area, labels, or outside legends (i.e. main padding) + ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge + ImVec2 LegendPadding; // = 10,10 legend padding from plot edges + ImVec2 LegendInnerPadding; // = 5,5 legend inner padding from legend edges + ImVec2 LegendSpacing; // = 5,0 spacing between legend entries + ImVec2 MousePosPadding; // = 10,10 padding between plot edge and interior mouse location text + ImVec2 AnnotationPadding; // = 2,2 text padding around annotation labels + ImVec2 FitPadding; // = 0,0 additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) + ImVec2 PlotDefaultSize; // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot + ImVec2 PlotMinSize; // = 200,150 minimum size plot frame can be when shrunk + // style colors + ImVec4 Colors[ImPlotCol_COUNT]; // Array of styling colors. Indexable with ImPlotCol_ enums. + // colormap + ImPlotColormap Colormap; // The current colormap. Set this to either an ImPlotColormap_ enum or an index returned by AddColormap. + // settings/flags + bool UseLocalTime; // = false, axis labels will be formatted for your timezone when ImPlotAxisFlag_Time is enabled + bool UseISO8601; // = false, dates will be formatted according to ISO 8601 where applicable (e.g. YYYY-MM-DD, YYYY-MM, --MM-DD, etc.) + bool Use24HourClock; // = false, times will be formatted using a 24 hour clock + IMPLOT_API ImPlotStyle(); +}; + +// Support for legacy versions +#if (IMGUI_VERSION_NUM < 18716) // Renamed in 1.88 +#define ImGuiMod_None 0 +#define ImGuiMod_Ctrl ImGuiKeyModFlags_Ctrl +#define ImGuiMod_Shift ImGuiKeyModFlags_Shift +#define ImGuiMod_Alt ImGuiKeyModFlags_Alt +#define ImGuiMod_Super ImGuiKeyModFlags_Super +#elif (IMGUI_VERSION_NUM < 18823) // Renamed in 1.89, sorry +#define ImGuiMod_None 0 +#define ImGuiMod_Ctrl ImGuiModFlags_Ctrl +#define ImGuiMod_Shift ImGuiModFlags_Shift +#define ImGuiMod_Alt ImGuiModFlags_Alt +#define ImGuiMod_Super ImGuiModFlags_Super +#endif + +// Input mapping structure. Default values listed. See also MapInputDefault, MapInputReverse. +struct ImPlotInputMap { + ImGuiMouseButton Pan; // LMB enables panning when held, + int PanMod; // none optional modifier that must be held for panning/fitting + ImGuiMouseButton Fit; // LMB initiates fit when double clicked + ImGuiMouseButton Select; // RMB begins box selection when pressed and confirms selection when released + ImGuiMouseButton SelectCancel; // LMB cancels active box selection when pressed; cannot be same as Select + int SelectMod; // none optional modifier that must be held for box selection + int SelectHorzMod; // Alt expands active box selection horizontally to plot edge when held + int SelectVertMod; // Shift expands active box selection vertically to plot edge when held + ImGuiMouseButton Menu; // RMB opens context menus (if enabled) when clicked + int OverrideMod; // Ctrl when held, all input is ignored; used to enable axis/plots as DND sources + int ZoomMod; // none optional modifier that must be held for scroll wheel zooming + float ZoomRate; // 0.1f zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click); make negative to invert + IMPLOT_API ImPlotInputMap(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Callbacks +//----------------------------------------------------------------------------- + +// Callback signature for axis tick label formatter. +typedef int (*ImPlotFormatter)(double value, char* buff, int size, void* user_data); + +// Callback signature for data getter. +typedef ImPlotPoint (*ImPlotGetter)(int idx, void* user_data); + +// Callback signature for axis transform. +typedef double (*ImPlotTransform)(double value, void* user_data); + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Contexts +//----------------------------------------------------------------------------- + +// Creates a new ImPlot context. Call this after ImGui::CreateContext. +IMPLOT_API ImPlotContext* CreateContext(); +// Destroys an ImPlot context. Call this before ImGui::DestroyContext. nullptr = destroy current context. +IMPLOT_API void DestroyContext(ImPlotContext* ctx = nullptr); +// Returns the current ImPlot context. nullptr if no context has ben set. +IMPLOT_API ImPlotContext* GetCurrentContext(); +// Sets the current ImPlot context. +IMPLOT_API void SetCurrentContext(ImPlotContext* ctx); + +// Sets the current **ImGui** context. This is ONLY necessary if you are compiling +// ImPlot as a DLL (not recommended) separate from your ImGui compilation. It +// sets the global variable GImGui, which is not shared across DLL boundaries. +// See GImGui documentation in imgui.cpp for more details. +IMPLOT_API void SetImGuiContext(ImGuiContext* ctx); + +//----------------------------------------------------------------------------- +// [SECTION] Begin/End Plot +//----------------------------------------------------------------------------- + +// Starts a 2D plotting context. If this function returns true, EndPlot() MUST +// be called! You are encouraged to use the following convention: +// +// if (BeginPlot(...)) { +// PlotLine(...); +// ... +// EndPlot(); +// } +// +// Important notes: +// +// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID +// collisions or don't want to display a title in the plot, use double hashes +// (e.g. "MyPlot##HiddenIdText" or "##NoTitle"). +// - #size is the **frame** size of the plot widget, not the plot area. The default +// size of plots (i.e. when ImVec2(0,0)) can be modified in your ImPlotStyle. +IMPLOT_API bool BeginPlot(const char* title_id, const ImVec2& size=ImVec2(-1,0), ImPlotFlags flags=0); + +// Only call EndPlot() if BeginPlot() returns true! Typically called at the end +// of an if statement conditioned on BeginPlot(). See example above. +IMPLOT_API void EndPlot(); + +//----------------------------------------------------------------------------- +// [SECTION] Begin/End Subplots +//----------------------------------------------------------------------------- + +// Starts a subdivided plotting context. If the function returns true, +// EndSubplots() MUST be called! Call BeginPlot/EndPlot AT MOST [rows*cols] +// times in between the begining and end of the subplot context. Plots are +// added in row major order. +// +// Example: +// +// if (BeginSubplots("My Subplot",2,3,ImVec2(800,400)) { +// for (int i = 0; i < 6; ++i) { +// if (BeginPlot(...)) { +// ImPlot::PlotLine(...); +// ... +// EndPlot(); +// } +// } +// EndSubplots(); +// } +// +// Produces: +// +// [0] | [1] | [2] +// ----|-----|---- +// [3] | [4] | [5] +// +// Important notes: +// +// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID +// collisions or don't want to display a title in the plot, use double hashes +// (e.g. "MySubplot##HiddenIdText" or "##NoTitle"). +// - #rows and #cols must be greater than 0. +// - #size is the size of the entire grid of subplots, not the individual plots +// - #row_ratios and #col_ratios must have AT LEAST #rows and #cols elements, +// respectively. These are the sizes of the rows and columns expressed in ratios. +// If the user adjusts the dimensions, the arrays are updated with new ratios. +// +// Important notes regarding BeginPlot from inside of BeginSubplots: +// +// - The #title_id parameter of _BeginPlot_ (see above) does NOT have to be +// unique when called inside of a subplot context. Subplot IDs are hashed +// for your convenience so you don't have call PushID or generate unique title +// strings. Simply pass an empty string to BeginPlot unless you want to title +// each subplot. +// - The #size parameter of _BeginPlot_ (see above) is ignored when inside of a +// subplot context. The actual size of the subplot will be based on the +// #size value you pass to _BeginSubplots_ and #row/#col_ratios if provided. + +IMPLOT_API bool BeginSubplots(const char* title_id, + int rows, + int cols, + const ImVec2& size, + ImPlotSubplotFlags flags = 0, + float* row_ratios = nullptr, + float* col_ratios = nullptr); + +// Only call EndSubplots() if BeginSubplots() returns true! Typically called at the end +// of an if statement conditioned on BeginSublots(). See example above. +IMPLOT_API void EndSubplots(); + +//----------------------------------------------------------------------------- +// [SECTION] Setup +//----------------------------------------------------------------------------- + +// The following API allows you to setup and customize various aspects of the +// current plot. The functions should be called immediately after BeginPlot +// and before any other API calls. Typical usage is as follows: + +// if (BeginPlot(...)) { 1) begin a new plot +// SetupAxis(ImAxis_X1, "My X-Axis"); 2) make Setup calls +// SetupAxis(ImAxis_Y1, "My Y-Axis"); +// SetupLegend(ImPlotLocation_North); +// ... +// SetupFinish(); 3) [optional] explicitly finish setup +// PlotLine(...); 4) plot items +// ... +// EndPlot(); 5) end the plot +// } +// +// Important notes: +// +// - Always call Setup code at the top of your BeginPlot conditional statement. +// - Setup is locked once you start plotting or explicitly call SetupFinish. +// Do NOT call Setup code after you begin plotting or after you make +// any non-Setup API calls (e.g. utils like PlotToPixels also lock Setup) +// - Calling SetupFinish is OPTIONAL, but probably good practice. If you do not +// call it yourself, then the first subsequent plotting or utility function will +// call it for you. + +// Enables an axis or sets the label and/or flags for an existing axis. Leave #label = nullptr for no label. +IMPLOT_API void SetupAxis(ImAxis axis, const char* label=nullptr, ImPlotAxisFlags flags=0); +// Sets an axis range limits. If ImPlotCond_Always is used, the axes limits will be locked. +IMPLOT_API void SetupAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once); +// Links an axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot. +IMPLOT_API void SetupAxisLinks(ImAxis axis, double* link_min, double* link_max); +// Sets the format of numeric axis labels via formater specifier (default="%g"). Formated values will be double (i.e. use %f). +IMPLOT_API void SetupAxisFormat(ImAxis axis, const char* fmt); +// Sets the format of numeric axis labels via formatter callback. Given #value, write a label into #buff. Optionally pass user data. +IMPLOT_API void SetupAxisFormat(ImAxis axis, ImPlotFormatter formatter, void* data=nullptr); +// Sets an axis' ticks and optionally the labels. To keep the default ticks, set #keep_default=true. +IMPLOT_API void SetupAxisTicks(ImAxis axis, const double* values, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false); +// Sets an axis' ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true. +IMPLOT_API void SetupAxisTicks(ImAxis axis, double v_min, double v_max, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false); +// Sets an axis' scale using built-in options. +IMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotScale scale); +// Sets an axis' scale using user supplied forward and inverse transfroms. +IMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotTransform forward, ImPlotTransform inverse, void* data=nullptr); +// Sets an axis' limits constraints. +IMPLOT_API void SetupAxisLimitsConstraints(ImAxis axis, double v_min, double v_max); +// Sets an axis' zoom constraints. +IMPLOT_API void SetupAxisZoomConstraints(ImAxis axis, double z_min, double z_max); + +// Sets the label and/or flags for primary X and Y axes (shorthand for two calls to SetupAxis). +IMPLOT_API void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags=0, ImPlotAxisFlags y_flags=0); +// Sets the primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits). +IMPLOT_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once); + +// Sets up the plot legend. +IMPLOT_API void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags=0); +// Set the location of the current plot's mouse position text (default = South|East). +IMPLOT_API void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags=0); + +// Explicitly finalize plot setup. Once you call this, you cannot make anymore Setup calls for the current plot! +// Note that calling this function is OPTIONAL; it will be called by the first subsequent setup-locking API call. +IMPLOT_API void SetupFinish(); + +//----------------------------------------------------------------------------- +// [SECTION] SetNext +//----------------------------------------------------------------------------- + +// Though you should default to the `Setup` API above, there are some scenarios +// where (re)configuring a plot or axis before `BeginPlot` is needed (e.g. if +// using a preceding button or slider widget to change the plot limits). In +// this case, you can use the `SetNext` API below. While this is not as feature +// rich as the Setup API, most common needs are provided. These functions can be +// called anwhere except for inside of `Begin/EndPlot`. For example: + +// if (ImGui::Button("Center Plot")) +// ImPlot::SetNextPlotLimits(-1,1,-1,1); +// if (ImPlot::BeginPlot(...)) { +// ... +// ImPlot::EndPlot(); +// } +// +// Important notes: +// +// - You must still enable non-default axes with SetupAxis for these functions +// to work properly. + +// Sets an upcoming axis range limits. If ImPlotCond_Always is used, the axes limits will be locked. +IMPLOT_API void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once); +// Links an upcoming axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot! +IMPLOT_API void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max); +// Set an upcoming axis to auto fit to its data. +IMPLOT_API void SetNextAxisToFit(ImAxis axis); + +// Sets the upcoming primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits). +IMPLOT_API void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once); +// Sets all upcoming axes to auto fit to their data. +IMPLOT_API void SetNextAxesToFit(); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Items +//----------------------------------------------------------------------------- + +// The main plotting API is provied below. Call these functions between +// Begin/EndPlot and after any Setup API calls. Each plots data on the current +// x and y axes, which can be changed with `SetAxis/Axes`. +// +// The templated functions are explicitly instantiated in implot_items.cpp. +// They are not intended to be used generically with custom types. You will get +// a linker error if you try! All functions support the following scalar types: +// +// float, double, ImS8, ImU8, ImS16, ImU16, ImS32, ImU32, ImS64, ImU64 +// +// +// If you need to plot custom or non-homogenous data you have a few options: +// +// 1. If your data is a simple struct/class (e.g. Vector2f), you can use striding. +// This is the most performant option if applicable. +// +// struct Vector2f { float X, Y; }; +// ... +// Vector2f data[42]; +// ImPlot::PlotLine("line", &data[0].x, &data[0].y, 42, 0, 0, sizeof(Vector2f)); +// +// 2. Write a custom getter C function or C++ lambda and pass it and optionally your data to +// an ImPlot function post-fixed with a G (e.g. PlotScatterG). This has a slight performance +// cost, but probably not enough to worry about unless your data is very large. Examples: +// +// ImPlotPoint MyDataGetter(void* data, int idx) { +// MyData* my_data = (MyData*)data; +// ImPlotPoint p; +// p.x = my_data->GetTime(idx); +// p.y = my_data->GetValue(idx); +// return p +// } +// ... +// auto my_lambda = [](int idx, void*) { +// double t = idx / 999.0; +// return ImPlotPoint(t, 0.5+0.5*std::sin(2*PI*10*t)); +// }; +// ... +// if (ImPlot::BeginPlot("MyPlot")) { +// MyData my_data; +// ImPlot::PlotScatterG("scatter", MyDataGetter, &my_data, my_data.Size()); +// ImPlot::PlotLineG("line", my_lambda, nullptr, 1000); +// ImPlot::EndPlot(); +// } +// +// NB: All types are converted to double before plotting. You may lose information +// if you try plotting extremely large 64-bit integral types. Proceed with caution! + +// Plots a standard 2D line plot. +IMPLOT_TMP void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotLineG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotLineFlags flags=0); + +// Plots a standard 2D scatter plot. Default marker is ImPlotMarker_Circle. +IMPLOT_TMP void PlotScatter(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotScatterG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotScatterFlags flags=0); + +// Plots a a stairstep graph. The y value is continued constantly to the right from every x position, i.e. the interval [x[i], x[i+1]) has the value y[i] +IMPLOT_TMP void PlotStairs(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotStairs(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotStairsG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotStairsFlags flags=0); + +// Plots a shaded (filled) region between two lines, or a line and a horizontal reference. Set yref to +/-INFINITY for infinite fill extents. +IMPLOT_TMP void PlotShaded(const char* label_id, const T* values, int count, double yref=0, double xscale=1, double xstart=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double yref=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotShadedG(const char* label_id, ImPlotGetter getter1, void* data1, ImPlotGetter getter2, void* data2, int count, ImPlotShadedFlags flags=0); + +// Plots a bar graph. Vertical by default. #bar_size and #shift are in plot units. +IMPLOT_TMP void PlotBars(const char* label_id, const T* values, int count, double bar_size=0.67, double shift=0, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotBarsG(const char* label_id, ImPlotGetter getter, void* data, int count, double bar_size, ImPlotBarsFlags flags=0); + +// Plots a group of bars. #values is a row-major matrix with #item_count rows and #group_count cols. #label_ids should have #item_count elements. +IMPLOT_TMP void PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size=0.67, double shift=0, ImPlotBarGroupsFlags flags=0); + +// Plots vertical error bar. The label_id should be the same as the label_id of the associated line or bar plot. +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots stems. Vertical by default. +IMPLOT_TMP void PlotStems(const char* label_id, const T* values, int count, double ref=0, double scale=1, double start=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots infinite vertical or horizontal lines (e.g. for references or asymptotes). +IMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots a pie chart. Center and radius are in plot units. #label_fmt can be set to nullptr for no labels. +IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt="%.1f", double angle0=90, ImPlotPieChartFlags flags=0); + +// Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels. +IMPLOT_TMP void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min=0, double scale_max=0, const char* label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), ImPlotHeatmapFlags flags=0); + +// Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #range is left unspecified, the min/max of #values will be used as the range. +// Otherwise, outlier values outside of the range are not binned. The largest bin count or density is returned. +IMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), ImPlotHistogramFlags flags=0); + +// Plots two dimensional, bivariate histogram as a heatmap. #x_bins and #y_bins can be a positive integer or an ImPlotBin. If #range is left unspecified, the min/max of +// #xs an #ys will be used as the ranges. Otherwise, outlier values outside of range are not binned. The largest bin count or density is returned. +IMPLOT_TMP double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, ImPlotRect range=ImPlotRect(), ImPlotHistogramFlags flags=0); + +// Plots digital data. Digital plots do not respond to y drag or zoom, and are always referenced to the bottom of the plot. +IMPLOT_TMP void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotDigitalG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotDigitalFlags flags=0); + +// Plots an axis-aligned image. #bounds_min/bounds_max are in plot coordinates (y-up) and #uv0/uv1 are in texture coordinates (y-down). +IMPLOT_API void PlotImage(const char* label_id, ImTextureID user_texture_id, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0=ImVec2(0,0), const ImVec2& uv1=ImVec2(1,1), const ImVec4& tint_col=ImVec4(1,1,1,1), ImPlotImageFlags flags=0); + +// Plots a centered text label at point x,y with an optional pixel offset. Text color can be changed with ImPlot::PushStyleColor(ImPlotCol_InlayText, ...). +IMPLOT_API void PlotText(const char* text, double x, double y, const ImVec2& pix_offset=ImVec2(0,0), ImPlotTextFlags flags=0); + +// Plots a dummy item (i.e. adds a legend entry colored by ImPlotCol_Line) +IMPLOT_API void PlotDummy(const char* label_id, ImPlotDummyFlags flags=0); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Tools +//----------------------------------------------------------------------------- + +// The following can be used to render interactive elements and/or annotations. +// Like the item plotting functions above, they apply to the current x and y +// axes, which can be changed with `SetAxis/SetAxes`. + +// Shows a draggable point at x,y. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragPoint(int id, double* x, double* y, const ImVec4& col, float size = 4, ImPlotDragToolFlags flags=0); +// Shows a draggable vertical guide line at an x-value. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragLineX(int id, double* x, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0); +// Shows a draggable horizontal guide line at a y-value. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragLineY(int id, double* y, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0); +// Shows a draggable and resizeable rectangle. +IMPLOT_API bool DragRect(int id, double* x1, double* y1, double* x2, double* y2, const ImVec4& col, ImPlotDragToolFlags flags=0); + +// Shows an annotation callout at a chosen point. Clamping keeps annotations in the plot area. Annotations are always rendered on top. +IMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, bool round = false); +IMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, ...) IM_FMTARGS(6); +IMPLOT_API void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, va_list args) IM_FMTLIST(6); + +// Shows a x-axis tag at the specified coordinate value. +IMPLOT_API void TagX(double x, const ImVec4& col, bool round = false); +IMPLOT_API void TagX(double x, const ImVec4& col, const char* fmt, ...) IM_FMTARGS(3); +IMPLOT_API void TagXV(double x, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3); + +// Shows a y-axis tag at the specified coordinate value. +IMPLOT_API void TagY(double y, const ImVec4& col, bool round = false); +IMPLOT_API void TagY(double y, const ImVec4& col, const char* fmt, ...) IM_FMTARGS(3); +IMPLOT_API void TagYV(double y, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +// Select which axis/axes will be used for subsequent plot elements. +IMPLOT_API void SetAxis(ImAxis axis); +IMPLOT_API void SetAxes(ImAxis x_axis, ImAxis y_axis); + +// Convert pixels to a position in the current plot's coordinate system. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Convert a position in the current plot's coordinate system to pixels. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImVec2 PlotToPixels(double x, double y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Get the current Plot position (top-left) in pixels. +IMPLOT_API ImVec2 GetPlotPos(); +// Get the curent Plot size in pixels. +IMPLOT_API ImVec2 GetPlotSize(); + +// Returns the mouse position in x,y coordinates of the current plot. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotPoint GetPlotMousePos(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +// Returns the current plot axis range. +IMPLOT_API ImPlotRect GetPlotLimits(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Returns true if the plot area in the current plot is hovered. +IMPLOT_API bool IsPlotHovered(); +// Returns true if the axis label area in the current plot is hovered. +IMPLOT_API bool IsAxisHovered(ImAxis axis); +// Returns true if the bounding frame of a subplot is hovered. +IMPLOT_API bool IsSubplotsHovered(); + +// Returns true if the current plot is being box selected. +IMPLOT_API bool IsPlotSelected(); +// Returns the current plot box selection bounds. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotRect GetPlotSelection(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +// Cancels a the current plot box selection. +IMPLOT_API void CancelPlotSelection(); + +// Hides or shows the next plot item (i.e. as if it were toggled from the legend). +// Use ImPlotCond_Always if you need to forcefully set this every frame. +IMPLOT_API void HideNextItem(bool hidden = true, ImPlotCond cond = ImPlotCond_Once); + +// Use the following around calls to Begin/EndPlot to align l/r/t/b padding. +// Consider using Begin/EndSubplots first. They are more feature rich and +// accomplish the same behaviour by default. The functions below offer lower +// level control of plot alignment. + +// Align axis padding over multiple plots in a single row or column. #group_id must +// be unique. If this function returns true, EndAlignedPlots() must be called. +IMPLOT_API bool BeginAlignedPlots(const char* group_id, bool vertical = true); +// Only call EndAlignedPlots() if BeginAlignedPlots() returns true! +IMPLOT_API void EndAlignedPlots(); + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils +//----------------------------------------------------------------------------- + +// Begin a popup for a legend entry. +IMPLOT_API bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button=1); +// End a popup for a legend entry. +IMPLOT_API void EndLegendPopup(); +// Returns true if a plot item legend entry is hovered. +IMPLOT_API bool IsLegendEntryHovered(const char* label_id); + +//----------------------------------------------------------------------------- +// [SECTION] Drag and Drop +//----------------------------------------------------------------------------- + +// Turns the current plot's plotting area into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetPlot(); +// Turns the current plot's X-axis into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetAxis(ImAxis axis); +// Turns the current plot's legend into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetLegend(); +// Ends a drag and drop target (currently just an alias for ImGui::EndDragDropTarget). +IMPLOT_API void EndDragDropTarget(); + +// NB: By default, plot and axes drag and drop *sources* require holding the Ctrl modifier to initiate the drag. +// You can change the modifier if desired. If ImGuiMod_None is provided, the axes will be locked from panning. + +// Turns the current plot's plotting area into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags=0); +// Turns the current plot's X-axis into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags=0); +// Turns an item in the current plot's legend into drag and drop source. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags=0); +// Ends a drag and drop source (currently just an alias for ImGui::EndDragDropSource). +IMPLOT_API void EndDragDropSource(); + +//----------------------------------------------------------------------------- +// [SECTION] Styling +//----------------------------------------------------------------------------- + +// Styling colors in ImPlot works similarly to styling colors in ImGui, but +// with one important difference. Like ImGui, all style colors are stored in an +// indexable array in ImPlotStyle. You can permanently modify these values through +// GetStyle().Colors, or temporarily modify them with Push/Pop functions below. +// However, by default all style colors in ImPlot default to a special color +// IMPLOT_AUTO_COL. The behavior of this color depends upon the style color to +// which it as applied: +// +// 1) For style colors associated with plot items (e.g. ImPlotCol_Line), +// IMPLOT_AUTO_COL tells ImPlot to color the item with the next unused +// color in the current colormap. Thus, every item will have a different +// color up to the number of colors in the colormap, at which point the +// colormap will roll over. For most use cases, you should not need to +// set these style colors to anything but IMPLOT_COL_AUTO; you are +// probably better off changing the current colormap. However, if you +// need to explicitly color a particular item you may either Push/Pop +// the style color around the item in question, or use the SetNextXXXStyle +// API below. If you permanently set one of these style colors to a specific +// color, or forget to call Pop, then all subsequent items will be styled +// with the color you set. +// +// 2) For style colors associated with plot styling (e.g. ImPlotCol_PlotBg), +// IMPLOT_AUTO_COL tells ImPlot to set that color from color data in your +// **ImGuiStyle**. The ImGuiCol_ that these style colors default to are +// detailed above, and in general have been mapped to produce plots visually +// consistent with your current ImGui style. Of course, you are free to +// manually set these colors to whatever you like, and further can Push/Pop +// them around individual plots for plot-specific styling (e.g. coloring axes). + +// Provides access to plot style structure for permanant modifications to colors, sizes, etc. +IMPLOT_API ImPlotStyle& GetStyle(); + +// Style plot colors for current ImGui style (default). +IMPLOT_API void StyleColorsAuto(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Classic". +IMPLOT_API void StyleColorsClassic(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Dark". +IMPLOT_API void StyleColorsDark(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Light". +IMPLOT_API void StyleColorsLight(ImPlotStyle* dst = nullptr); + +// Use PushStyleX to temporarily modify your ImPlotStyle. The modification +// will last until the matching call to PopStyleX. You MUST call a pop for +// every push, otherwise you will leak memory! This behaves just like ImGui. + +// Temporarily modify a style color. Don't forget to call PopStyleColor! +IMPLOT_API void PushStyleColor(ImPlotCol idx, ImU32 col); +IMPLOT_API void PushStyleColor(ImPlotCol idx, const ImVec4& col); +// Undo temporary style color modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopStyleColor(int count = 1); + +// Temporarily modify a style variable of float type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, float val); +// Temporarily modify a style variable of int type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, int val); +// Temporarily modify a style variable of ImVec2 type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val); +// Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopStyleVar(int count = 1); + +// The following can be used to modify the style of the next plot item ONLY. They do +// NOT require calls to PopStyleX. Leave style attributes you don't want modified to +// IMPLOT_AUTO or IMPLOT_AUTO_COL. Automatic styles will be deduced from the current +// values in your ImPlotStyle or from Colormap data. + +// Set the line color and weight for the next item only. +IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); +// Set the fill color for the next item only. +IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO); +// Set the marker style for the next item only. +IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); +// Set the error bar style for the next item only. +IMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO); + +// Gets the last item primary color (i.e. its legend icon color) +IMPLOT_API ImVec4 GetLastItemColor(); + +// Returns the null terminated string name for an ImPlotCol. +IMPLOT_API const char* GetStyleColorName(ImPlotCol idx); +// Returns the null terminated string name for an ImPlotMarker. +IMPLOT_API const char* GetMarkerName(ImPlotMarker idx); + +//----------------------------------------------------------------------------- +// [SECTION] Colormaps +//----------------------------------------------------------------------------- + +// Item styling is based on colormaps when the relevant ImPlotCol_XXX is set to +// IMPLOT_AUTO_COL (default). Several built-in colormaps are available. You can +// add and then push/pop your own colormaps as well. To permanently set a colormap, +// modify the Colormap index member of your ImPlotStyle. + +// Colormap data will be ignored and a custom color will be used if you have done one of the following: +// 1) Modified an item style color in your ImPlotStyle to anything other than IMPLOT_AUTO_COL. +// 2) Pushed an item style color using PushStyleColor(). +// 3) Set the next item style with a SetNextXXXStyle function. + +// Add a new colormap. The color data will be copied. The colormap can be used by pushing either the returned index or the +// string name with PushColormap. The colormap name must be unique and the size must be greater than 1. You will receive +// an assert otherwise! By default colormaps are considered to be qualitative (i.e. discrete). If you want to create a +// continuous colormap, set #qual=false. This will treat the colors you provide as keys, and ImPlot will build a linearly +// interpolated lookup table. The memory footprint of this table will be exactly ((size-1)*255+1)*4 bytes. +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImVec4* cols, int size, bool qual=true); +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImU32* cols, int size, bool qual=true); + +// Returns the number of available colormaps (i.e. the built-in + user-added count). +IMPLOT_API int GetColormapCount(); +// Returns a null terminated string name for a colormap given an index. Returns nullptr if index is invalid. +IMPLOT_API const char* GetColormapName(ImPlotColormap cmap); +// Returns an index number for a colormap given a valid string name. Returns -1 if name is invalid. +IMPLOT_API ImPlotColormap GetColormapIndex(const char* name); + +// Temporarily switch to one of the built-in (i.e. ImPlotColormap_XXX) or user-added colormaps (i.e. a return value of AddColormap). Don't forget to call PopColormap! +IMPLOT_API void PushColormap(ImPlotColormap cmap); +// Push a colormap by string name. Use built-in names such as "Default", "Deep", "Jet", etc. or a string you provided to AddColormap. Don't forget to call PopColormap! +IMPLOT_API void PushColormap(const char* name); +// Undo temporary colormap modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopColormap(int count = 1); + +// Returns the next color from the current colormap and advances the colormap for the current plot. +// Can also be used with no return value to skip colors if desired. You need to call this between Begin/EndPlot! +IMPLOT_API ImVec4 NextColormapColor(); + +// Colormap utils. If cmap = IMPLOT_AUTO (default), the current colormap is assumed. +// Pass an explicit colormap index (built-in or user-added) to specify otherwise. + +// Returns the size of a colormap. +IMPLOT_API int GetColormapSize(ImPlotColormap cmap = IMPLOT_AUTO); +// Returns a color from a colormap given an index >= 0 (modulo will be performed). +IMPLOT_API ImVec4 GetColormapColor(int idx, ImPlotColormap cmap = IMPLOT_AUTO); +// Sample a color from the current colormap given t between 0 and 1. +IMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO); + +// Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. "##NoLabel"). If scale_min > scale_max, the scale to color mapping will be reversed. +IMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), const char* format = "%g", ImPlotColormapScaleFlags flags = 0, ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1]. +IMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = nullptr, const char* format = "", ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a button with a colormap gradient brackground. +IMPLOT_API bool ColormapButton(const char* label, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO); + +// When items in a plot sample their color from a colormap, the color is cached and does not change +// unless explicitly overriden. Therefore, if you change the colormap after the item has already been plotted, +// item colors will NOT update. If you need item colors to resample the new colormap, then use this +// function to bust the cached colors. If #plot_title_id is nullptr, then every item in EVERY existing plot +// will be cache busted. Otherwise only the plot specified by #plot_title_id will be busted. For the +// latter, this function must be called in the same ImGui ID scope that the plot is in. You should rarely if ever +// need this function, but it is available for applications that require runtime colormap swaps (e.g. Heatmaps demo). +IMPLOT_API void BustColorCache(const char* plot_title_id = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Input Mapping +//----------------------------------------------------------------------------- + +// Provides access to input mapping structure for permanant modifications to controls for pan, select, etc. +IMPLOT_API ImPlotInputMap& GetInputMap(); + +// Default input mapping: pan = LMB drag, box select = RMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll. +IMPLOT_API void MapInputDefault(ImPlotInputMap* dst = nullptr); +// Reverse input mapping: pan = RMB drag, box select = LMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll. +IMPLOT_API void MapInputReverse(ImPlotInputMap* dst = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Miscellaneous +//----------------------------------------------------------------------------- + +// Render icons similar to those that appear in legends (nifty for data lists). +IMPLOT_API void ItemIcon(const ImVec4& col); +IMPLOT_API void ItemIcon(ImU32 col); +IMPLOT_API void ColormapIcon(ImPlotColormap cmap); + +// Get the plot draw list for custom rendering to the current plot area. Call between Begin/EndPlot. +IMPLOT_API ImDrawList* GetPlotDrawList(); +// Push clip rect for rendering to current plot area. The rect can be expanded or contracted by #expand pixels. Call between Begin/EndPlot. +IMPLOT_API void PushPlotClipRect(float expand=0); +// Pop plot clip rect. Call between Begin/EndPlot. +IMPLOT_API void PopPlotClipRect(); + +// Shows ImPlot style selector dropdown menu. +IMPLOT_API bool ShowStyleSelector(const char* label); +// Shows ImPlot colormap selector dropdown menu. +IMPLOT_API bool ShowColormapSelector(const char* label); +// Shows ImPlot input map selector dropdown menu. +IMPLOT_API bool ShowInputMapSelector(const char* label); +// Shows ImPlot style editor block (not a window). +IMPLOT_API void ShowStyleEditor(ImPlotStyle* ref = nullptr); +// Add basic help/info block for end users (not a window). +IMPLOT_API void ShowUserGuide(); +// Shows ImPlot metrics/debug information window. +IMPLOT_API void ShowMetricsWindow(bool* p_popen = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Demo +//----------------------------------------------------------------------------- + +// Shows the ImPlot demo window (add implot_demo.cpp to your sources!) +IMPLOT_API void ShowDemoWindow(bool* p_open = nullptr); + +} // namespace ImPlot + +//----------------------------------------------------------------------------- +// [SECTION] Obsolete API +//----------------------------------------------------------------------------- + +// The following functions will be removed! Keep your copy of implot up to date! +// Occasionally set '#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS' to stay ahead. +// If you absolutely must use these functions and do not want to receive compiler +// warnings, set '#define IMPLOT_DISABLE_OBSOLETE_WARNINGS'. + +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS + +#ifndef IMPLOT_DISABLE_DEPRECATED_WARNINGS +#if __cplusplus > 201402L +#define IMPLOT_DEPRECATED(method) [[deprecated]] method +#elif defined( __GNUC__ ) && !defined( __INTEL_COMPILER ) && ( __GNUC__ > 3 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 1 ) ) +#define IMPLOT_DEPRECATED(method) method __attribute__( ( deprecated ) ) +#elif defined( _MSC_VER ) +#define IMPLOT_DEPRECATED(method) __declspec(deprecated) method +#else +#define IMPLOT_DEPRECATED(method) method +#endif +#else +#define IMPLOT_DEPRECATED(method) method +#endif + +enum ImPlotFlagsObsolete_ { + ImPlotFlags_YAxis2 = 1 << 20, + ImPlotFlags_YAxis3 = 1 << 21, +}; + +namespace ImPlot { + +// OBSOLETED in v0.13 -> PLANNED REMOVAL in v1.0 +IMPLOT_DEPRECATED( IMPLOT_API bool BeginPlot(const char* title_id, + const char* x_label, // = nullptr, + const char* y_label, // = nullptr, + const ImVec2& size = ImVec2(-1,0), + ImPlotFlags flags = ImPlotFlags_None, + ImPlotAxisFlags x_flags = 0, + ImPlotAxisFlags y_flags = 0, + ImPlotAxisFlags y2_flags = ImPlotAxisFlags_AuxDefault, + ImPlotAxisFlags y3_flags = ImPlotAxisFlags_AuxDefault, + const char* y2_label = nullptr, + const char* y3_label = nullptr) ); + +} // namespace ImPlot + +#endif diff --git a/crogine/src/CMakeLists.txt b/crogine/src/CMakeLists.txt index 746d61d20..6f5d2e356 100644 --- a/crogine/src/CMakeLists.txt +++ b/crogine/src/CMakeLists.txt @@ -153,6 +153,9 @@ set(PROJECT_SRC ${PROJECT_DIR}/imgui/imgui_stdlib.cpp ${PROJECT_DIR}/imgui/imgui_widgets.cpp ${PROJECT_DIR}/imgui/ImGuizmo.cpp + ${PROJECT_DIR}/imgui/implot.cpp + ${PROJECT_DIR}/imgui/implot_demo.cpp + ${PROJECT_DIR}/imgui/implot_items.cpp ${PROJECT_DIR}/imgui/ImSequencer.cpp ${PROJECT_DIR}/network/NetClient.cpp diff --git a/crogine/src/core/App.cpp b/crogine/src/core/App.cpp index 8b603ee8d..3bb365fca 100644 --- a/crogine/src/core/App.cpp +++ b/crogine/src/core/App.cpp @@ -320,6 +320,7 @@ void App::run() m_window.setMultisamplingEnabled(glIsEnabled(GL_MULTISAMPLE)); ImGui::CreateContext(); + ImPlot::CreateContext(); setImguiStyle(&ImGui::GetStyle()); //ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; @@ -427,6 +428,7 @@ void App::run() finalise(); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); + ImPlot::DestroyContext(); ImGui::DestroyContext(); m_window.close(); } diff --git a/crogine/src/imgui/implot.cpp b/crogine/src/imgui/implot.cpp new file mode 100644 index 000000000..72d788716 --- /dev/null +++ b/crogine/src/imgui/implot.cpp @@ -0,0 +1,5752 @@ +// MIT License + +// Copyright (c) 2022 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.14 + +/* + +API BREAKING CHANGES +==================== +Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. +Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. +When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. +You can read releases logs https://github.com/epezent/implot/releases for more details. + +- 2022/06/19 (0.14) - The signature of ColormapScale has changed to accommodate a new ImPlotColormapScaleFlags parameter +- 2022/06/17 (0.14) - **IMPORTANT** All PlotX functions now take an ImPlotX_Flags `flags` parameter. Where applicable, it is located before the existing `offset` and `stride` parameters. + If you were providing offset and stride values, you will need to update your function call to include a `flags` value. If you fail to do this, you will likely see + unexpected results or crashes without a compiler warning since these three are all default args. We apologize for the inconvenience, but this was a necessary evil. + - PlotBarsH has been removed; use PlotBars + ImPlotBarsFlags_Horizontal instead + - PlotErrorBarsH has been removed; use PlotErrorBars + ImPlotErrorBarsFlags_Horizontal + - PlotHistogram/PlotHistogram2D signatures changed; `cumulative`, `density`, and `outliers` options now specified via ImPlotHistogramFlags + - PlotPieChart signature changed; `normalize` option now specified via ImPlotPieChartFlags + - PlotText signature changes; `vertical` option now specified via `ImPlotTextFlags_Vertical` + - `PlotVLines` and `PlotHLines` replaced with `PlotInfLines` (+ ImPlotInfLinesFlags_Horizontal ) + - arguments of ImPlotGetter have been reversed to be consistent with other API callbacks + - SetupAxisScale + ImPlotScale have replaced ImPlotAxisFlags_LogScale and ImPlotAxisFlags_Time flags + - ImPlotFormatters should now return an int indicating the size written + - the signature of ImPlotGetter has been reversed so that void* user_data is the last argument and consistent with other callbacks +- 2021/10/19 (0.13) - MAJOR API OVERHAUL! See #168 and #272 + - TRIVIAL RENAME: + - ImPlotLimits -> ImPlotRect + - ImPlotYAxis_ -> ImAxis_ + - SetPlotYAxis -> SetAxis + - BeginDragDropTarget -> BeginDragDropTargetPlot + - BeginDragDropSource -> BeginDragDropSourcePlot + - ImPlotFlags_NoMousePos -> ImPlotFlags_NoMouseText + - SetNextPlotLimits -> SetNextAxesLimits + - SetMouseTextLocation -> SetupMouseText + - SIGNATURE MODIFIED: + - PixelsToPlot/PlotToPixels -> added optional X-Axis arg + - GetPlotMousePos -> added optional X-Axis arg + - GetPlotLimits -> added optional X-Axis arg + - GetPlotSelection -> added optional X-Axis arg + - DragLineX/Y/DragPoint -> now takes int id; removed labels (render with Annotation/Tag instead) + - REPLACED: + - IsPlotXAxisHovered/IsPlotXYAxisHovered -> IsAxisHovered(ImAxis) + - BeginDragDropTargetX/BeginDragDropTargetY -> BeginDragDropTargetAxis(ImAxis) + - BeginDragDropSourceX/BeginDragDropSourceY -> BeginDragDropSourceAxis(ImAxis) + - ImPlotCol_XAxis, ImPlotCol_YAxis1, etc. -> ImPlotCol_AxisText (push/pop this around SetupAxis to style individual axes) + - ImPlotCol_XAxisGrid, ImPlotCol_Y1AxisGrid -> ImPlotCol_AxisGrid (push/pop this around SetupAxis to style individual axes) + - SetNextPlotLimitsX/Y -> SetNextAxisLimits(ImAxis) + - LinkNextPlotLimits -> SetNextAxisLinks(ImAxis) + - FitNextPlotAxes -> SetNextAxisToFit(ImAxis)/SetNextAxesToFit + - SetLegendLocation -> SetupLegend + - ImPlotFlags_NoHighlight -> ImPlotLegendFlags_NoHighlight + - ImPlotOrientation -> ImPlotLegendFlags_Horizontal + - Annotate -> Annotation + - REMOVED: + - GetPlotQuery, SetPlotQuery, IsPlotQueried -> use DragRect + - SetNextPlotTicksX, SetNextPlotTicksY -> use SetupAxisTicks + - SetNextPlotFormatX, SetNextPlotFormatY -> use SetupAxisFormat + - AnnotateClamped -> use Annotation(bool clamp = true) + - OBSOLETED: + - BeginPlot (original signature) -> use simplified signature + Setup API +- 2021/07/30 (0.12) - The offset argument of `PlotXG` functions was been removed. Implement offsetting in your getter callback instead. +- 2021/03/08 (0.9) - SetColormap and PushColormap(ImVec4*) were removed. Use AddColormap for custom colormap support. LerpColormap was changed to SampleColormap. + ShowColormapScale was changed to ColormapScale and requires additional arguments. +- 2021/03/07 (0.9) - The signature of ShowColormapScale was modified to accept a ImVec2 size. +- 2021/02/28 (0.9) - BeginLegendDragDropSource was changed to BeginDragDropSourceItem with a number of other drag and drop improvements. +- 2021/01/18 (0.9) - The default behavior for opening context menus was change from double right-click to single right-click. ImPlotInputMap and related functions were moved + to implot_internal.h due to its immaturity. +- 2020/10/16 (0.8) - ImPlotStyleVar_InfoPadding was changed to ImPlotStyleVar_MousePosPadding +- 2020/09/10 (0.8) - The single array versions of PlotLine, PlotScatter, PlotStems, and PlotShaded were given additional arguments for x-scale and x0. +- 2020/09/07 (0.8) - Plotting functions which accept a custom getter function pointer have been post-fixed with a G (e.g. PlotLineG) +- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset + is simply 0. This more closely matches ImGui's style and makes it easier to enable non-default but commonly used flags (e.g. ImPlotAxisFlags_Time). +- 2020/08/28 (0.5) - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it. +- 2020/08/25 (0.5) - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation. +- 2020/08/17 (0.5) - PlotText was changed so that text is centered horizontally and vertically about the desired point. +- 2020/08/16 (0.5) - An ImPlotContext must be explicitly created and destroyed now with `CreateContext` and `DestroyContext`. Previously, the context was statically initialized in this source file. +- 2020/06/13 (0.4) - The flags `ImPlotAxisFlag_Adaptive` and `ImPlotFlags_Cull` were removed. Both are now done internally by default. +- 2020/06/03 (0.3) - The signature and behavior of PlotPieChart was changed so that data with sum less than 1 can optionally be normalized. The label format can now be specified as well. +- 2020/06/01 (0.3) - SetPalette was changed to `SetColormap` for consistency with other plotting libraries. `RestorePalette` was removed. Use `SetColormap(ImPlotColormap_Default)`. +- 2020/05/31 (0.3) - Plot functions taking custom ImVec2* getters were removed. Use the ImPlotPoint* getter versions instead. +- 2020/05/29 (0.3) - The signature of ImPlotLimits::Contains was changed to take two doubles instead of ImVec2 +- 2020/05/16 (0.2) - All plotting functions were reverted to being prefixed with "Plot" to maintain a consistent VerbNoun style. `Plot` was split into `PlotLine` + and `PlotScatter` (however, `PlotLine` can still be used to plot scatter points as `Plot` did before.). `Bar` is not `PlotBars`, to indicate + that multiple bars will be plotted. +- 2020/05/13 (0.2) - `ImMarker` was change to `ImPlotMarker` and `ImAxisFlags` was changed to `ImPlotAxisFlags`. +- 2020/05/11 (0.2) - `ImPlotFlags_Selection` was changed to `ImPlotFlags_BoxSelect` +- 2020/05/11 (0.2) - The namespace ImGui:: was replaced with ImPlot::. As a result, the following additional changes were made: + - Functions that were prefixed or decorated with the word "Plot" have been truncated. E.g., `ImGui::PlotBars` is now just `ImPlot::Bar`. + It should be fairly obvious what was what. + - Some functions have been given names that would have otherwise collided with the ImGui namespace. This has been done to maintain a consistent + style with ImGui. E.g., 'ImGui::PushPlotStyleVar` is now 'ImPlot::PushStyleVar'. +- 2020/05/10 (0.2) - The following function/struct names were changes: + - ImPlotRange -> ImPlotLimits + - GetPlotRange() -> GetPlotLimits() + - SetNextPlotRange -> SetNextPlotLimits + - SetNextPlotRangeX -> SetNextPlotLimitsX + - SetNextPlotRangeY -> SetNextPlotLimitsY +- 2020/05/10 (0.2) - Plot queries are pixel based by default. Query rects that maintain relative plot position have been removed. This was done to support multi-y-axis. + +*/ + +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include "implot_internal.h" + +#include + +// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean "all corners" but in order to support older versions we are more explicit. +#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll) +#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#endif + +// Global plot context +#ifndef GImPlot +ImPlotContext* GImPlot = nullptr; +#endif + +//----------------------------------------------------------------------------- +// Struct Implementations +//----------------------------------------------------------------------------- + +ImPlotInputMap::ImPlotInputMap() { + ImPlot::MapInputDefault(this); +} + +ImPlotStyle::ImPlotStyle() { + + LineWeight = 1; + Marker = ImPlotMarker_None; + MarkerSize = 4; + MarkerWeight = 1; + FillAlpha = 1; + ErrorBarSize = 5; + ErrorBarWeight = 1.5f; + DigitalBitHeight = 8; + DigitalBitGap = 4; + + PlotBorderSize = 1; + MinorAlpha = 0.25f; + MajorTickLen = ImVec2(10,10); + MinorTickLen = ImVec2(5,5); + MajorTickSize = ImVec2(1,1); + MinorTickSize = ImVec2(1,1); + MajorGridSize = ImVec2(1,1); + MinorGridSize = ImVec2(1,1); + PlotPadding = ImVec2(10,10); + LabelPadding = ImVec2(5,5); + LegendPadding = ImVec2(10,10); + LegendInnerPadding = ImVec2(5,5); + LegendSpacing = ImVec2(5,0); + MousePosPadding = ImVec2(10,10); + AnnotationPadding = ImVec2(2,2); + FitPadding = ImVec2(0,0); + PlotDefaultSize = ImVec2(400,300); + PlotMinSize = ImVec2(200,150); + + ImPlot::StyleColorsAuto(this); + + Colormap = ImPlotColormap_Deep; + + UseLocalTime = false; + Use24HourClock = false; + UseISO8601 = false; +} + +//----------------------------------------------------------------------------- +// Style +//----------------------------------------------------------------------------- + +namespace ImPlot { + +const char* GetStyleColorName(ImPlotCol col) { + static const char* col_names[ImPlotCol_COUNT] = { + "Line", + "Fill", + "MarkerOutline", + "MarkerFill", + "ErrorBar", + "FrameBg", + "PlotBg", + "PlotBorder", + "LegendBg", + "LegendBorder", + "LegendText", + "TitleText", + "InlayText", + "AxisText", + "AxisGrid", + "AxisTick", + "AxisBg", + "AxisBgHovered", + "AxisBgActive", + "Selection", + "Crosshairs" + }; + return col_names[col]; +} + +const char* GetMarkerName(ImPlotMarker marker) { + switch (marker) { + case ImPlotMarker_None: return "None"; + case ImPlotMarker_Circle: return "Circle"; + case ImPlotMarker_Square: return "Square"; + case ImPlotMarker_Diamond: return "Diamond"; + case ImPlotMarker_Up: return "Up"; + case ImPlotMarker_Down: return "Down"; + case ImPlotMarker_Left: return "Left"; + case ImPlotMarker_Right: return "Right"; + case ImPlotMarker_Cross: return "Cross"; + case ImPlotMarker_Plus: return "Plus"; + case ImPlotMarker_Asterisk: return "Asterisk"; + default: return ""; + } +} + +ImVec4 GetAutoColor(ImPlotCol idx) { + ImVec4 col(0,0,0,1); + switch(idx) { + case ImPlotCol_Line: return col; // these are plot dependent! + case ImPlotCol_Fill: return col; // these are plot dependent! + case ImPlotCol_MarkerOutline: return col; // these are plot dependent! + case ImPlotCol_MarkerFill: return col; // these are plot dependent! + case ImPlotCol_ErrorBar: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg); + case ImPlotCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + case ImPlotCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border); + case ImPlotCol_LegendBg: return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg); + case ImPlotCol_LegendBorder: return GetStyleColorVec4(ImPlotCol_PlotBorder); + case ImPlotCol_LegendText: return GetStyleColorVec4(ImPlotCol_InlayText); + case ImPlotCol_TitleText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_InlayText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_AxisText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_AxisGrid: return GetStyleColorVec4(ImPlotCol_AxisText) * ImVec4(1,1,1,0.25f); + case ImPlotCol_AxisTick: return GetStyleColorVec4(ImPlotCol_AxisGrid); + case ImPlotCol_AxisBg: return ImVec4(0,0,0,0); + case ImPlotCol_AxisBgHovered: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + case ImPlotCol_AxisBgActive: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + case ImPlotCol_Selection: return ImVec4(1,1,0,1); + case ImPlotCol_Crosshairs: return GetStyleColorVec4(ImPlotCol_PlotBorder); + default: return col; + } +} + +struct ImPlotStyleVarInfo { + ImGuiDataType Type; + ImU32 Count; + ImU32 Offset; + void* GetVarPtr(ImPlotStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImPlotStyleVarInfo GPlotStyleVarInfo[] = +{ + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight + { ImGuiDataType_S32, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap + + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickLen) }, // ImPlotStyleVar_MinorTickLen + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorTickSize) }, // ImPlotStyleVar_MajorTickSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorTickSize) }, // ImPlotStyleVar_MinorTickSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MajorGridSize) }, // ImPlotStyleVar_MajorGridSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MinorGridSize) }, // ImPlotStyleVar_MinorGridSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotPadding) }, // ImPlotStyleVar_PlotPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LabelPadding) }, // ImPlotStyleVar_LabelPaddine + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, LegendSpacing) }, // ImPlotStyleVar_LegendSpacing + + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, MousePosPadding) }, // ImPlotStyleVar_MousePosPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, FitPadding) }, // ImPlotStyleVar_FitPadding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize +}; + +static const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) { + IM_ASSERT(idx >= 0 && idx < ImPlotStyleVar_COUNT); + IM_ASSERT(IM_ARRAYSIZE(GPlotStyleVarInfo) == ImPlotStyleVar_COUNT); + return &GPlotStyleVarInfo[idx]; +} + +//----------------------------------------------------------------------------- +// Generic Helpers +//----------------------------------------------------------------------------- + +void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char *text_begin, const char* text_end) { + // the code below is based loosely on ImFont::RenderText + if (!text_end) + text_end = text_begin + strlen(text_begin); + ImGuiContext& g = *GImGui; + ImFont* font = g.Font; + // Align to be pixel perfect + pos.x = IM_FLOOR(pos.x); + pos.y = IM_FLOOR(pos.y); + const float scale = g.FontSize / font->FontSize; + const char* s = text_begin; + int chars_exp = (int)(text_end - s); + int chars_rnd = 0; + const int vtx_count_max = chars_exp * 4; + const int idx_count_max = chars_exp * 6; + DrawList->PrimReserve(idx_count_max, vtx_count_max); + while (s < text_end) { + unsigned int c = (unsigned int)*s; + if (c < 0x80) { + s += 1; + } + else { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + const ImFontGlyph * glyph = font->FindGlyph((ImWchar)c); + if (glyph == nullptr) { + continue; + } + DrawList->PrimQuadUV(pos + ImVec2(glyph->Y0, -glyph->X0) * scale, pos + ImVec2(glyph->Y0, -glyph->X1) * scale, + pos + ImVec2(glyph->Y1, -glyph->X1) * scale, pos + ImVec2(glyph->Y1, -glyph->X0) * scale, + ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V0), + ImVec2(glyph->U1, glyph->V1), ImVec2(glyph->U0, glyph->V1), + col); + pos.y -= glyph->AdvanceX * scale; + chars_rnd++; + } + // Give back unused vertices + int chars_skp = chars_exp-chars_rnd; + DrawList->PrimUnreserve(chars_skp*6, chars_skp*4); +} + +void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end) { + float txt_ht = ImGui::GetTextLineHeight(); + const char* title_end = ImGui::FindRenderedTextEnd(text_begin, text_end); + ImVec2 text_size; + float y = 0; + while (const char* tmp = (const char*)memchr(text_begin, '\n', title_end-text_begin)) { + text_size = ImGui::CalcTextSize(text_begin,tmp,true); + DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,tmp); + text_begin = tmp + 1; + y += txt_ht; + } + text_size = ImGui::CalcTextSize(text_begin,title_end,true); + DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,title_end); +} + +double NiceNum(double x, bool round) { + double f; + double nf; + int expv = (int)floor(ImLog10(x)); + f = x / ImPow(10.0, (double)expv); + if (round) + if (f < 1.5) + nf = 1; + else if (f < 3) + nf = 2; + else if (f < 7) + nf = 5; + else + nf = 10; + else if (f <= 1) + nf = 1; + else if (f <= 2) + nf = 2; + else if (f <= 5) + nf = 5; + else + nf = 10; + return nf * ImPow(10.0, expv); +} + +//----------------------------------------------------------------------------- +// Context Utils +//----------------------------------------------------------------------------- + +void SetImGuiContext(ImGuiContext* ctx) { + ImGui::SetCurrentContext(ctx); +} + +ImPlotContext* CreateContext() { + ImPlotContext* ctx = IM_NEW(ImPlotContext)(); + Initialize(ctx); + if (GImPlot == nullptr) + SetCurrentContext(ctx); + return ctx; +} + +void DestroyContext(ImPlotContext* ctx) { + if (ctx == nullptr) + ctx = GImPlot; + if (GImPlot == ctx) + SetCurrentContext(nullptr); + IM_DELETE(ctx); +} + +ImPlotContext* GetCurrentContext() { + return GImPlot; +} + +void SetCurrentContext(ImPlotContext* ctx) { + GImPlot = ctx; +} + +#define IMPLOT_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name)/sizeof(ImU32), qual) +#define IM_RGB(r,g,b) IM_COL32(r,g,b,255) + +void Initialize(ImPlotContext* ctx) { + ResetCtxForNextPlot(ctx); + ResetCtxForNextAlignedPlots(ctx); + ResetCtxForNextSubplot(ctx); + + const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396 }; + const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409 }; + const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986 }; + const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481}; + const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301 }; + const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752 }; + const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295 }; + const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015 }; + const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295 }; + const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335 }; + const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)}; + const ImU32 RdBu[] = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)}; + const ImU32 BrBG[] = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)}; + const ImU32 PiYG[] = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)}; + const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)}; + const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK }; + + IMPLOT_APPEND_CMAP(Deep, true); + IMPLOT_APPEND_CMAP(Dark, true); + IMPLOT_APPEND_CMAP(Pastel, true); + IMPLOT_APPEND_CMAP(Paired, true); + IMPLOT_APPEND_CMAP(Viridis, false); + IMPLOT_APPEND_CMAP(Plasma, false); + IMPLOT_APPEND_CMAP(Hot, false); + IMPLOT_APPEND_CMAP(Cool, false); + IMPLOT_APPEND_CMAP(Pink, false); + IMPLOT_APPEND_CMAP(Jet, false); + IMPLOT_APPEND_CMAP(Twilight, false); + IMPLOT_APPEND_CMAP(RdBu, false); + IMPLOT_APPEND_CMAP(BrBG, false); + IMPLOT_APPEND_CMAP(PiYG, false); + IMPLOT_APPEND_CMAP(Spectral, false); + IMPLOT_APPEND_CMAP(Greys, false); +} + +void ResetCtxForNextPlot(ImPlotContext* ctx) { + // end child window if it was made + if (ctx->ChildWindowMade) + ImGui::EndChild(); + ctx->ChildWindowMade = false; + // reset the next plot/item data + ctx->NextPlotData.Reset(); + ctx->NextItemData.Reset(); + // reset labels + ctx->Annotations.Reset(); + ctx->Tags.Reset(); + // reset extents/fit + ctx->OpenContextThisFrame = false; + // reset digital plot items count + ctx->DigitalPlotItemCnt = 0; + ctx->DigitalPlotOffset = 0; + // nullify plot + ctx->CurrentPlot = nullptr; + ctx->CurrentItem = nullptr; + ctx->PreviousItem = nullptr; +} + +void ResetCtxForNextAlignedPlots(ImPlotContext* ctx) { + ctx->CurrentAlignmentH = nullptr; + ctx->CurrentAlignmentV = nullptr; +} + +void ResetCtxForNextSubplot(ImPlotContext* ctx) { + ctx->CurrentSubplot = nullptr; + ctx->CurrentAlignmentH = nullptr; + ctx->CurrentAlignmentV = nullptr; +} + +//----------------------------------------------------------------------------- +// Plot Utils +//----------------------------------------------------------------------------- + +ImPlotPlot* GetPlot(const char* title) { + ImGuiWindow* Window = GImGui->CurrentWindow; + const ImGuiID ID = Window->GetID(title); + return GImPlot->Plots.GetByKey(ID); +} + +ImPlotPlot* GetCurrentPlot() { + return GImPlot->CurrentPlot; +} + +void BustPlotCache() { + ImPlotContext& gp = *GImPlot; + gp.Plots.Clear(); + gp.Subplots.Clear(); +} + +//----------------------------------------------------------------------------- +// Legend Utils +//----------------------------------------------------------------------------- + +ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation loc, const ImVec2& pad) { + ImVec2 pos; + if (ImHasFlag(loc, ImPlotLocation_West) && !ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Min.x + pad.x; + else if (!ImHasFlag(loc, ImPlotLocation_West) && ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Max.x - pad.x - inner_size.x; + else + pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f; + // legend reference point y + if (ImHasFlag(loc, ImPlotLocation_North) && !ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Min.y + pad.y; + else if (!ImHasFlag(loc, ImPlotLocation_North) && ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Max.y - pad.y - inner_size.y; + else + pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f; + pos.x = IM_ROUND(pos.x); + pos.y = IM_ROUND(pos.y); + return pos; +} + +ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) { + // vars + const int nItems = items.GetLegendCount(); + const float txt_ht = ImGui::GetTextLineHeight(); + const float icon_size = txt_ht; + // get label max width + float max_label_width = 0; + float sum_label_width = 0; + for (int i = 0; i < nItems; ++i) { + const char* label = items.GetLegendLabel(i); + const float label_width = ImGui::CalcTextSize(label, nullptr, true).x; + max_label_width = label_width > max_label_width ? label_width : max_label_width; + sum_label_width += label_width; + } + // calc legend size + const ImVec2 legend_size = vertical ? + ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) : + ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht); + return legend_size; +} + +int LegendSortingComp(const void* _a, const void* _b) { + ImPlotItemGroup* items = GImPlot->SortItems; + const int a = *(const int*)_a; + const int b = *(const int*)_b; + const char* label_a = items->GetLegendLabel(a); + const char* label_b = items->GetLegendLabel(b); + return strcmp(label_a,label_b); +} + +bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList) { + // vars + const float txt_ht = ImGui::GetTextLineHeight(); + const float icon_size = txt_ht; + const float icon_shrink = 2; + ImU32 col_txt = GetStyleColorU32(ImPlotCol_LegendText); + ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f); + // render each legend item + float sum_label_width = 0; + bool any_item_hovered = false; + + const int num_items = items.GetLegendCount(); + if (num_items < 1) + return hovered; + // build render order + ImPlotContext& gp = *GImPlot; + ImVector& indices = gp.TempInt1; + indices.resize(num_items); + for (int i = 0; i < num_items; ++i) + indices[i] = i; + if (ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_Sort) && num_items > 1) { + gp.SortItems = &items; + qsort(indices.Data, num_items, sizeof(int), LegendSortingComp); + } + // render + for (int i = 0; i < num_items; ++i) { + const int idx = indices[i]; + ImPlotItem* item = items.GetLegendItem(idx); + const char* label = items.GetLegendLabel(idx); + const float label_width = ImGui::CalcTextSize(label, nullptr, true).x; + const ImVec2 top_left = vertical ? + legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) : + legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0); + sum_label_width += label_width; + ImRect icon_bb; + icon_bb.Min = top_left + ImVec2(icon_shrink,icon_shrink); + icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink); + ImRect label_bb; + label_bb.Min = top_left; + label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size); + ImU32 col_txt_hl; + ImU32 col_item = ImAlphaU32(item->Color,1); + + ImRect button_bb(icon_bb.Min, label_bb.Max); + + ImGui::KeepAliveID(item->ID); + + bool item_hov = false; + bool item_hld = false; + bool item_clk = ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoButtons) + ? false + : ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld); + + if (item_clk) + item->Show = !item->Show; + + + const bool can_hover = (item_hov) + && (!ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightItem) + || !ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)); + + if (can_hover) { + item->LegendHoverRect.Min = icon_bb.Min; + item->LegendHoverRect.Max = label_bb.Max; + item->LegendHovered = true; + col_txt_hl = ImMixU32(col_txt, col_item, 64); + any_item_hovered = true; + } + else { + col_txt_hl = ImGui::GetColorU32(col_txt); + } + ImU32 col_icon; + if (item_hld) + col_icon = item->Show ? ImAlphaU32(col_item,0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f); + else if (item_hov) + col_icon = item->Show ? ImAlphaU32(col_item,0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f); + else + col_icon = item->Show ? col_item : col_txt_dis; + + DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon); + const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr); + if (label != text_display_end) + DrawList.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl : col_txt_dis, label, text_display_end); + } + return hovered && !any_item_hovered; +} + +//----------------------------------------------------------------------------- +// Locators +//----------------------------------------------------------------------------- + +static const float TICK_FILL_X = 0.8f; +static const float TICK_FILL_Y = 1.0f; + +void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + if (range.Min == range.Max) + return; + const int nMinor = 10; + const int nMajor = ImMax(2, (int)IM_ROUND(pixels / (vertical ? 300.0f : 400.0f))); + const double nice_range = NiceNum(range.Size() * 0.99, false); + const double interval = NiceNum(nice_range / (nMajor - 1), true); + const double graphmin = floor(range.Min / interval) * interval; + const double graphmax = ceil(range.Max / interval) * interval; + bool first_major_set = false; + int first_major_idx = 0; + const int idx0 = ticker.TickCount(); // ticker may have user custom ticks + ImVec2 total_size(0,0); + for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) { + // is this zero? combat zero formatting issues + if (major-interval < 0 && major+interval > 0) + major = 0; + if (range.Contains(major)) { + if (!first_major_set) { + first_major_idx = ticker.TickCount(); + first_major_set = true; + } + total_size += ticker.AddTick(major, true, 0, true, formatter, formatter_data).LabelSize; + } + for (int i = 1; i < nMinor; ++i) { + double minor = major + i * interval / nMinor; + if (range.Contains(minor)) { + total_size += ticker.AddTick(minor, false, 0, true, formatter, formatter_data).LabelSize; + } + } + } + // prune if necessary + if ((!vertical && total_size.x > pixels*TICK_FILL_X) || (vertical && total_size.y > pixels*TICK_FILL_Y)) { + for (int i = first_major_idx-1; i >= idx0; i -= 2) + ticker.Ticks[i].ShowLabel = false; + for (int i = first_major_idx+1; i < ticker.TickCount(); i += 2) + ticker.Ticks[i].ShowLabel = false; + } +} + +bool CalcLogarithmicExponents(const ImPlotRange& range, float pix, bool vertical, int& exp_min, int& exp_max, int& exp_step) { + if (range.Min * range.Max > 0) { + const int nMajor = vertical ? ImMax(2, (int)IM_ROUND(pix * 0.02f)) : ImMax(2, (int)IM_ROUND(pix * 0.01f)); // TODO: magic numbers + double log_min = ImLog10(ImAbs(range.Min)); + double log_max = ImLog10(ImAbs(range.Max)); + double log_a = ImMin(log_min,log_max); + double log_b = ImMax(log_min,log_max); + exp_step = ImMax(1,(int)(log_b - log_a) / nMajor); + exp_min = (int)log_a; + exp_max = (int)log_b; + if (exp_step != 1) { + while(exp_step % 3 != 0) exp_step++; // make step size multiple of three + while(exp_min % exp_step != 0) exp_min--; // decrease exp_min until exp_min + N * exp_step will be 0 + } + return true; + } + return false; +} + +void AddTicksLogarithmic(const ImPlotRange& range, int exp_min, int exp_max, int exp_step, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) { + const double sign = ImSign(range.Max); + for (int e = exp_min - exp_step; e < (exp_max + exp_step); e += exp_step) { + double major1 = sign*ImPow(10, (double)(e)); + double major2 = sign*ImPow(10, (double)(e + 1)); + double interval = (major2 - major1) / 9; + if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON)) + ticker.AddTick(major1, true, 0, true, formatter, data); + for (int j = 0; j < exp_step; ++j) { + major1 = sign*ImPow(10, (double)(e+j)); + major2 = sign*ImPow(10, (double)(e+j+1)); + interval = (major2 - major1) / 9; + for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) { + double minor = major1 + i * interval; + if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON)) + ticker.AddTick(minor, false, 0, false, formatter, data); + } + } + } +} + +void Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + int exp_min, exp_max, exp_step; + if (CalcLogarithmicExponents(range, pixels, vertical, exp_min, exp_max, exp_step)) + AddTicksLogarithmic(range, exp_min, exp_max, exp_step, ticker, formatter, formatter_data); +} + +float CalcSymLogPixel(double plt, const ImPlotRange& range, float pixels) { + double scaleToPixels = pixels / range.Size(); + double scaleMin = TransformForward_SymLog(range.Min,nullptr); + double scaleMax = TransformForward_SymLog(range.Max,nullptr); + double s = TransformForward_SymLog(plt, nullptr); + double t = (s - scaleMin) / (scaleMax - scaleMin); + plt = range.Min + range.Size() * t; + + return (float)(0 + scaleToPixels * (plt - range.Min)); +} + +void Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + if (range.Min >= -1 && range.Max <= 1) { + Locator_Default(ticker, range, pixels, vertical, formatter, formatter_data); + } + else if (range.Min * range.Max < 0) { // cross zero + const float pix_min = 0; + const float pix_max = pixels; + const float pix_p1 = CalcSymLogPixel(1, range, pixels); + const float pix_n1 = CalcSymLogPixel(-1, range, pixels); + int exp_min_p, exp_max_p, exp_step_p; + int exp_min_n, exp_max_n, exp_step_n; + CalcLogarithmicExponents(ImPlotRange(1,range.Max), ImAbs(pix_max-pix_p1),vertical,exp_min_p,exp_max_p,exp_step_p); + CalcLogarithmicExponents(ImPlotRange(range.Min,-1),ImAbs(pix_n1-pix_min),vertical,exp_min_n,exp_max_n,exp_step_n); + int exp_step = ImMax(exp_step_n, exp_step_p); + ticker.AddTick(0,true,0,true,formatter,formatter_data); + AddTicksLogarithmic(ImPlotRange(1,range.Max), exp_min_p,exp_max_p,exp_step,ticker,formatter,formatter_data); + AddTicksLogarithmic(ImPlotRange(range.Min,-1),exp_min_n,exp_max_n,exp_step,ticker,formatter,formatter_data); + } + else { + Locator_Log10(ticker, range, pixels, vertical, formatter, formatter_data); + } +} + +void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) { + for (int i = 0; i < n; ++i) { + if (labels != nullptr) + ticker.AddTick(values[i], false, 0, true, labels[i]); + else + ticker.AddTick(values[i], false, 0, true, formatter, data); + } +} + +//----------------------------------------------------------------------------- +// Time Ticks and Utils +//----------------------------------------------------------------------------- + +// this may not be thread safe? +static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { + 0.000001, + 0.001, + 1, + 60, + 3600, + 86400, + 2629800, + 31557600 +}; + +inline ImPlotTimeUnit GetUnitForRange(double range) { + static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; + for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { + if (range <= cutoffs[i]) + return (ImPlotTimeUnit)i; + } + return ImPlotTimeUnit_Yr; +} + +inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) { + if (max_divs < divs[0]) + return 0; + for (int i = 1; i < size; ++i) { + if (max_divs < divs[i]) + return step[i-1]; + } + return step[size-1]; +} + +inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { + if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) { + static const int step[] = {500,250,200,100,50,25,20,10,5,2,1}; + static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000}; + return LowerBoundStep(max_divs, divs, step, 11); + } + if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) { + static const int step[] = {30,15,10,5,1}; + static const int divs[] = {2,4,6,12,60}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Hr) { + static const int step[] = {12,6,3,2,1}; + static const int divs[] = {2,4,8,12,24}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Day) { + static const int step[] = {14,7,2,1}; + static const int divs[] = {2,4,14,28}; + return LowerBoundStep(max_divs, divs, step, 4); + } + else if (unit == ImPlotTimeUnit_Mo) { + static const int step[] = {6,3,2,1}; + static const int divs[] = {2,4,6,12}; + return LowerBoundStep(max_divs, divs, step, 4); + } + return 0; +} + +ImPlotTime MkGmtTime(struct tm *ptm) { + ImPlotTime t; +#ifdef _WIN32 + t.S = _mkgmtime(ptm); +#else + t.S = timegm(ptm); +#endif + if (t.S < 0) + t.S = 0; + return t; +} + +tm* GetGmtTime(const ImPlotTime& t, tm* ptm) +{ +#ifdef _WIN32 + if (gmtime_s(ptm, &t.S) == 0) + return ptm; + else + return nullptr; +#else + return gmtime_r(&t.S, ptm); +#endif +} + +ImPlotTime MkLocTime(struct tm *ptm) { + ImPlotTime t; + t.S = mktime(ptm); + if (t.S < 0) + t.S = 0; + return t; +} + +tm* GetLocTime(const ImPlotTime& t, tm* ptm) { +#ifdef _WIN32 + if (localtime_s(ptm, &t.S) == 0) + return ptm; + else + return nullptr; +#else + return localtime_r(&t.S, ptm); +#endif +} + +inline ImPlotTime MkTime(struct tm *ptm) { + if (GetStyle().UseLocalTime) + return MkLocTime(ptm); + else + return MkGmtTime(ptm); +} + +inline tm* GetTime(const ImPlotTime& t, tm* ptm) { + if (GetStyle().UseLocalTime) + return GetLocTime(t,ptm); + else + return GetGmtTime(t,ptm); +} + +ImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, int us) { + tm& Tm = GImPlot->Tm; + + int yr = year - 1900; + if (yr < 0) + yr = 0; + + sec = sec + us / 1000000; + us = us % 1000000; + + Tm.tm_sec = sec; + Tm.tm_min = min; + Tm.tm_hour = hour; + Tm.tm_mday = day; + Tm.tm_mon = month; + Tm.tm_year = yr; + + ImPlotTime t = MkTime(&Tm); + + t.Us = us; + return t; +} + +int GetYear(const ImPlotTime& t) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + return Tm.tm_year + 1900; +} + +ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { + tm& Tm = GImPlot->Tm; + ImPlotTime t_out = t; + switch(unit) { + case ImPlotTimeUnit_Us: t_out.Us += count; break; + case ImPlotTimeUnit_Ms: t_out.Us += count * 1000; break; + case ImPlotTimeUnit_S: t_out.S += count; break; + case ImPlotTimeUnit_Min: t_out.S += count * 60; break; + case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; + case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; + case ImPlotTimeUnit_Mo: for (int i = 0; i < abs(count); ++i) { + GetTime(t_out, &Tm); + if (count > 0) + t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); + else if (count < 0) + t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - (Tm.tm_mon == 0 ? 1 : 0), Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING + } + break; + case ImPlotTimeUnit_Yr: for (int i = 0; i < abs(count); ++i) { + if (count > 0) + t_out.S += 86400 * (365 + (int)IsLeapYear(GetYear(t_out))); + else if (count < 0) + t_out.S -= 86400 * (365 + (int)IsLeapYear(GetYear(t_out) - 1)); + // this is incorrect if leap year and we are past Feb 28 + } + break; + default: break; + } + t_out.RollOver(); + return t_out; +} + +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + ImPlotContext& gp = *GImPlot; + GetTime(t, &gp.Tm); + switch (unit) { + case ImPlotTimeUnit_S: return ImPlotTime(t.S, 0); + case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, (t.Us / 1000) * 1000); + case ImPlotTimeUnit_Us: return t; + case ImPlotTimeUnit_Yr: gp.Tm.tm_mon = 0; // fall-through + case ImPlotTimeUnit_Mo: gp.Tm.tm_mday = 1; // fall-through + case ImPlotTimeUnit_Day: gp.Tm.tm_hour = 0; // fall-through + case ImPlotTimeUnit_Hr: gp.Tm.tm_min = 0; // fall-through + case ImPlotTimeUnit_Min: gp.Tm.tm_sec = 0; break; + default: return t; + } + return MkTime(&gp.Tm); +} + +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + return AddTime(FloorTime(t, unit), unit, 1); +} + +ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + ImPlotTime t1 = FloorTime(t, unit); + ImPlotTime t2 = AddTime(t1,unit,1); + if (t1.S == t2.S) + return t.Us - t1.Us < t2.Us - t.Us ? t1 : t2; + return t.S - t1.S < t2.S - t.S ? t1 : t2; +} + +ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part) { + ImPlotContext& gp = *GImPlot; + tm& Tm = gp.Tm; + GetTime(date_part, &gp.Tm); + int y = Tm.tm_year; + int m = Tm.tm_mon; + int d = Tm.tm_mday; + GetTime(tod_part, &gp.Tm); + Tm.tm_year = y; + Tm.tm_mon = m; + Tm.tm_mday = d; + ImPlotTime t = MkTime(&Tm); + t.Us = tod_part.Us; + return t; +} + +// TODO: allow users to define these +static const char* MONTH_NAMES[] = {"January","February","March","April","May","June","July","August","September","October","November","December"}; +static const char* WD_ABRVS[] = {"Su","Mo","Tu","We","Th","Fr","Sa"}; +static const char* MONTH_ABRVS[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + +int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + const int us = t.Us % 1000; + const int ms = t.Us / 1000; + const int sec = Tm.tm_sec; + const int min = Tm.tm_min; + if (use_24_hr_clk) { + const int hr = Tm.tm_hour; + switch(fmt) { + case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us); + case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us); + case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms); + case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec); + case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms); + case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%02d:%02d:%02d.%03d", hr, min, sec, ms); + case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%02d:%02d:%02d", hr, min, sec); + case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%02d:%02d", hr, min); + case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%02d:00", hr); + default: return 0; + } + } + else { + const char* ap = Tm.tm_hour < 12 ? "am" : "pm"; + const int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12; + switch(fmt) { + case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us); + case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us); + case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms); + case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec); + case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms); + case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%d:%02d:%02d.%03d%s", hr, min, sec, ms, ap); + case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); + case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%d:%02d%s", hr, min, ap); + case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%d%s", hr, ap); + default: return 0; + } + } +} + +int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + const int day = Tm.tm_mday; + const int mon = Tm.tm_mon + 1; + const int year = Tm.tm_year + 1900; + const int yr = year % 100; + if (use_iso_8601) { + switch (fmt) { + case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "--%02d-%02d", mon, day); + case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d-%02d-%02d", year, mon, day); + case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%d-%02d", year, mon); + case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "--%02d", mon); + case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year); + default: return 0; + } + } + else { + switch (fmt) { + case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "%d/%d", mon, day); + case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d/%d/%02d", mon, day, yr); + case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%s %d", MONTH_ABRVS[Tm.tm_mon], year); + case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "%s", MONTH_ABRVS[Tm.tm_mon]); + case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year); + default: return 0; + } + } + } + +int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt) { + int written = 0; + if (fmt.Date != ImPlotDateFmt_None) + written += FormatDate(t, buffer, size, fmt.Date, fmt.UseISO8601); + if (fmt.Time != ImPlotTimeFmt_None) { + if (fmt.Date != ImPlotDateFmt_None) + buffer[written++] = ' '; + written += FormatTime(t, &buffer[written], size - written, fmt.Time, fmt.Use24HourClock); + } + return written; +} + +inline float GetDateTimeWidth(ImPlotDateTimeSpec fmt) { + static const ImPlotTime t_max_width = MakeTime(2888, 12, 22, 12, 58, 58, 888888); // best guess at time that maximizes pixel width + char buffer[32]; + FormatDateTime(t_max_width, buffer, 32, fmt); + return ImGui::CalcTextSize(buffer).x; +} + +inline bool TimeLabelSame(const char* l1, const char* l2) { + size_t len1 = strlen(l1); + size_t len2 = strlen(l2); + size_t n = len1 < len2 ? len1 : len2; + return strcmp(l1 + len1 - n, l2 + len2 - n) == 0; +} + +static const ImPlotDateTimeSpec TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_S), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Hr), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Mo, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatMouseCursor[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SUs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_Hr), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_MoYr, ImPlotTimeFmt_None) +}; + +inline ImPlotDateTimeSpec GetDateTimeFmt(const ImPlotDateTimeSpec* ctx, ImPlotTimeUnit idx) { + ImPlotStyle& style = GetStyle(); + ImPlotDateTimeSpec fmt = ctx[idx]; + fmt.UseISO8601 = style.UseISO8601; + fmt.Use24HourClock = style.Use24HourClock; + return fmt; +} + +void Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + IM_ASSERT_USER_ERROR(vertical == false, "Cannot locate Time ticks on vertical axis!"); + (void)vertical; + // get units for level 0 and level 1 labels + const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (pixels / 100)); // level = 0 (top) + const ImPlotTimeUnit unit1 = ImClamp(unit0 + 1, 0, ImPlotTimeUnit_COUNT-1); // level = 1 (bottom) + // get time format specs + const ImPlotDateTimeSpec fmt0 = GetDateTimeFmt(TimeFormatLevel0, unit0); + const ImPlotDateTimeSpec fmt1 = GetDateTimeFmt(TimeFormatLevel1, unit1); + const ImPlotDateTimeSpec fmtf = GetDateTimeFmt(TimeFormatLevel1First, unit1); + // min max times + const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min); + const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max); + // maximum allowable density of labels + const float max_density = 0.5f; + // book keeping + int last_major_offset = -1; + // formatter data + Formatter_Time_Data ftd; + ftd.UserFormatter = formatter; + ftd.UserFormatterData = formatter_data; + if (unit0 != ImPlotTimeUnit_Yr) { + // pixels per major (level 1) division + const float pix_per_major_div = pixels / (float)(range.Size() / TimeUnitSpans[unit1]); + // nominal pixels taken up by labels + const float fmt0_width = GetDateTimeWidth(fmt0); + const float fmt1_width = GetDateTimeWidth(fmt1); + const float fmtf_width = GetDateTimeWidth(fmtf); + // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions + const int minor_per_major = (int)(max_density * pix_per_major_div / fmt0_width); + // the minor step size (level 0) + const int step = GetTimeStep(minor_per_major, unit0); + // generate ticks + ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1); + while (t1 < t_max) { + // get next major + const ImPlotTime t2 = AddTime(t1, unit1, 1); + // add major tick + if (t1 >= t_min && t1 <= t_max) { + // minor level 0 tick + ftd.Time = t1; ftd.Spec = fmt0; + ticker.AddTick(t1.ToDouble(), true, 0, true, Formatter_Time, &ftd); + // major level 1 tick + ftd.Time = t1; ftd.Spec = last_major_offset < 0 ? fmtf : fmt1; + ImPlotTick& tick_maj = ticker.AddTick(t1.ToDouble(), true, 1, true, Formatter_Time, &ftd); + const char* this_major = ticker.GetText(tick_maj); + if (last_major_offset >= 0 && TimeLabelSame(ticker.TextBuffer.Buf.Data + last_major_offset, this_major)) + tick_maj.ShowLabel = false; + last_major_offset = tick_maj.TextOffset; + } + // add minor ticks up until next major + if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) { + ImPlotTime t12 = AddTime(t1, unit0, step); + while (t12 < t2) { + float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * pixels; + if (t12 >= t_min && t12 <= t_max) { + ftd.Time = t12; ftd.Spec = fmt0; + ticker.AddTick(t12.ToDouble(), false, 0, px_to_t2 >= fmt0_width, Formatter_Time, &ftd); + if (last_major_offset < 0 && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) { + ftd.Time = t12; ftd.Spec = fmtf; + ImPlotTick& tick_maj = ticker.AddTick(t12.ToDouble(), true, 1, true, Formatter_Time, &ftd); + last_major_offset = tick_maj.TextOffset; + } + } + t12 = AddTime(t12, unit0, step); + } + } + t1 = t2; + } + } + else { + const ImPlotDateTimeSpec fmty = GetDateTimeFmt(TimeFormatLevel0, ImPlotTimeUnit_Yr); + const float label_width = GetDateTimeWidth(fmty); + const int max_labels = (int)(max_density * pixels / label_width); + const int year_min = GetYear(t_min); + const int year_max = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr)); + const double nice_range = NiceNum((year_max - year_min)*0.99,false); + const double interval = NiceNum(nice_range / (max_labels - 1), true); + const int graphmin = (int)(floor(year_min / interval) * interval); + const int graphmax = (int)(ceil(year_max / interval) * interval); + const int step = (int)interval <= 0 ? 1 : (int)interval; + + for (int y = graphmin; y < graphmax; y += step) { + ImPlotTime t = MakeTime(y); + if (t >= t_min && t <= t_max) { + ftd.Time = t; ftd.Spec = fmty; + ticker.AddTick(t.ToDouble(), true, 0, true, Formatter_Time, &ftd); + } + } + } +} + +//----------------------------------------------------------------------------- +// Context Menu +//----------------------------------------------------------------------------- + +template +bool DragFloat(const char*, F*, float, F, F) { + return false; +} + +template <> +bool DragFloat(const char* label, double* v, float v_speed, double v_min, double v_max) { + return ImGui::DragScalar(label, ImGuiDataType_Double, v, v_speed, &v_min, &v_max, "%.3f", 1); +} + +template <> +bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max) { + return ImGui::DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, "%.3f", 1); +} + +inline void BeginDisabledControls(bool cond) { + if (cond) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f); + } +} + +inline void EndDisabledControls(bool cond) { + if (cond) { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } +} + +void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_allowed*/) { + + ImGui::PushItemWidth(75); + bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting(); + bool label = axis.HasLabel(); + bool grid = axis.HasGridLines(); + bool ticks = axis.HasTickMarks(); + bool labels = axis.HasTickLabels(); + double drag_speed = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits. + + if (axis.Scale == ImPlotScale_Time) { + ImPlotTime tmin = ImPlotTime::FromDouble(axis.Range.Min); + ImPlotTime tmax = ImPlotTime::FromDouble(axis.Range.Max); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMin() || always_locked); + if (ImGui::BeginMenu("Min Time")) { + if (ShowTimePicker("mintime", &tmin)) { + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("mindate",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) { + tmin = CombineDateTime(axis.PickerTimeMin, tmin); + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(axis.IsLockedMin() || always_locked); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMax() || always_locked); + if (ImGui::BeginMenu("Max Time")) { + if (ShowTimePicker("maxtime", &tmax)) { + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("maxdate",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) { + tmax = CombineDateTime(axis.PickerTimeMax, tmax); + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(axis.IsLockedMax() || always_locked); + } + else { + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMin() || always_locked); + double temp_min = axis.Range.Min; + if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) { + axis.SetMin(temp_min,true); + if (equal_axis != nullptr) + equal_axis->SetAspect(axis.GetAspect()); + } + EndDisabledControls(axis.IsLockedMin() || always_locked); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMax() || always_locked); + double temp_max = axis.Range.Max; + if (DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) { + axis.SetMax(temp_max,true); + if (equal_axis != nullptr) + equal_axis->SetAspect(axis.GetAspect()); + } + EndDisabledControls(axis.IsLockedMax() || always_locked); + } + + ImGui::Separator(); + + ImGui::CheckboxFlags("Auto-Fit",(unsigned int*)&axis.Flags, ImPlotAxisFlags_AutoFit); + // TODO + // BeginDisabledControls(axis.IsTime() && time_allowed); + // ImGui::CheckboxFlags("Log Scale",(unsigned int*)&axis.Flags, ImPlotAxisFlags_LogScale); + // EndDisabledControls(axis.IsTime() && time_allowed); + // if (time_allowed) { + // BeginDisabledControls(axis.IsLog() || axis.IsSymLog()); + // ImGui::CheckboxFlags("Time",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Time); + // EndDisabledControls(axis.IsLog() || axis.IsSymLog()); + // } + ImGui::Separator(); + ImGui::CheckboxFlags("Invert",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Invert); + ImGui::CheckboxFlags("Opposite",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Opposite); + ImGui::Separator(); + BeginDisabledControls(axis.LabelOffset == -1); + if (ImGui::Checkbox("Label", &label)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoLabel); + EndDisabledControls(axis.LabelOffset == -1); + if (ImGui::Checkbox("Grid Lines", &grid)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoGridLines); + if (ImGui::Checkbox("Tick Marks", &ticks)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); + if (ImGui::Checkbox("Tick Labels", &labels)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels); + +} + +bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible) { + const float s = ImGui::GetFrameHeight(); + bool ret = false; + if (ImGui::Checkbox("Show",&visible)) + ret = true; + if (legend.CanGoInside) + ImGui::CheckboxFlags("Outside",(unsigned int*)&legend.Flags, ImPlotLegendFlags_Outside); + if (ImGui::RadioButton("H", ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal))) + legend.Flags |= ImPlotLegendFlags_Horizontal; + ImGui::SameLine(); + if (ImGui::RadioButton("V", !ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal))) + legend.Flags &= ~ImPlotLegendFlags_Horizontal; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2,2)); + if (ImGui::Button("NW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthWest; } ImGui::SameLine(); + if (ImGui::Button("N", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_North; } ImGui::SameLine(); + if (ImGui::Button("NE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthEast; } + if (ImGui::Button("W", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_West; } ImGui::SameLine(); + if (ImGui::InvisibleButton("C", ImVec2(1.5f*s,s))) { } ImGui::SameLine(); + if (ImGui::Button("E", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_East; } + if (ImGui::Button("SW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthWest; } ImGui::SameLine(); + if (ImGui::Button("S", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_South; } ImGui::SameLine(); + if (ImGui::Button("SE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthEast; } + ImGui::PopStyleVar(); + return ret; +} + +void ShowSubplotsContextMenu(ImPlotSubplot& subplot) { + if ((ImGui::BeginMenu("Linking"))) { + if (ImGui::MenuItem("Link Rows",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows); + if (ImGui::MenuItem("Link Cols",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols); + if (ImGui::MenuItem("Link All X",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX); + if (ImGui::MenuItem("Link All Y",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY); + ImGui::EndMenu(); + } + if ((ImGui::BeginMenu("Settings"))) { + BeginDisabledControls(!subplot.HasTitle); + if (ImGui::MenuItem("Title",nullptr,subplot.HasTitle && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle); + EndDisabledControls(!subplot.HasTitle); + if (ImGui::MenuItem("Resizable",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoResize); + if (ImGui::MenuItem("Align",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign); + if (ImGui::MenuItem("Share Items",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + ImGui::EndMenu(); + } +} + +void ShowPlotContextMenu(ImPlotPlot& plot) { + ImPlotContext& gp = *GImPlot; + const bool owns_legend = gp.CurrentItems == &plot.Items; + const bool equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + + char buf[16] = {}; + + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.Enabled || !x_axis.HasMenus()) + continue; + ImGui::PushID(i); + ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "X-Axis" : "X-Axis %d", i + 1); + if (ImGui::BeginMenu(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : buf)) { + ShowAxisContextMenu(x_axis, equal ? x_axis.OrthoAxis : nullptr, false); + ImGui::EndMenu(); + } + ImGui::PopID(); + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.Enabled || !y_axis.HasMenus()) + continue; + ImGui::PushID(i); + ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1); + if (ImGui::BeginMenu(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : buf)) { + ShowAxisContextMenu(y_axis, equal ? y_axis.OrthoAxis : nullptr, false); + ImGui::EndMenu(); + } + ImGui::PopID(); + } + + ImGui::Separator(); + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoMenus)) { + if ((ImGui::BeginMenu("Legend"))) { + if (owns_legend) { + if (ShowLegendContextMenu(plot.Items.Legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend); + } + else if (gp.CurrentSubplot != nullptr) { + if (ShowLegendContextMenu(gp.CurrentSubplot->Items.Legend, !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend))) + ImFlipFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend); + } + ImGui::EndMenu(); + } + } + if ((ImGui::BeginMenu("Settings"))) { + if (ImGui::MenuItem("Equal", nullptr, ImHasFlag(plot.Flags, ImPlotFlags_Equal))) + ImFlipFlag(plot.Flags, ImPlotFlags_Equal); + if (ImGui::MenuItem("Box Select",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoBoxSelect); + BeginDisabledControls(plot.TitleOffset == -1); + if (ImGui::MenuItem("Title",nullptr,plot.HasTitle())) + ImFlipFlag(plot.Flags, ImPlotFlags_NoTitle); + EndDisabledControls(plot.TitleOffset == -1); + if (ImGui::MenuItem("Mouse Position",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoMouseText); + if (ImGui::MenuItem("Crosshairs",nullptr,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs))) + ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs); + ImGui::EndMenu(); + } + if (gp.CurrentSubplot != nullptr && !ImHasFlag(gp.CurrentPlot->Flags, ImPlotSubplotFlags_NoMenus)) { + ImGui::Separator(); + if ((ImGui::BeginMenu("Subplots"))) { + ShowSubplotsContextMenu(*gp.CurrentSubplot); + ImGui::EndMenu(); + } + } +} + +//----------------------------------------------------------------------------- +// Axis Utils +//----------------------------------------------------------------------------- + +static inline int AxisPrecision(const ImPlotAxis& axis) { + const double range = axis.Ticker.TickCount() > 1 ? (axis.Ticker.Ticks[1].PlotPos - axis.Ticker.Ticks[0].PlotPos) : axis.Range.Size(); + return Precision(range); +} + +static inline double RoundAxisValue(const ImPlotAxis& axis, double value) { + return RoundTo(value, AxisPrecision(axis)); +} + +void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round) { + ImPlotContext& gp = *GImPlot; + // TODO: We shouldn't explicitly check that the axis is Time here. Ideally, + // Formatter_Time would handle the formatting for us, but the code below + // needs additional arguments which are not currently available in ImPlotFormatter + if (axis.Locator == Locator_Time) { + ImPlotTimeUnit unit = axis.Vertical + ? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100)) // TODO: magic value! + : GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100)); // TODO: magic value! + FormatDateTime(ImPlotTime::FromDouble(value), buff, size, GetDateTimeFmt(TimeFormatMouseCursor, unit)); + } + else { + if (round) + value = RoundAxisValue(axis, value); + axis.Formatter(value, buff, size, axis.FormatterData); + } +} + +void UpdateAxisColors(ImPlotAxis& axis) { + const ImVec4 col_grid = GetStyleColorVec4(ImPlotCol_AxisGrid); + axis.ColorMaj = ImGui::GetColorU32(col_grid); + axis.ColorMin = ImGui::GetColorU32(col_grid*ImVec4(1,1,1,GImPlot->Style.MinorAlpha)); + axis.ColorTick = GetStyleColorU32(ImPlotCol_AxisTick); + axis.ColorTxt = GetStyleColorU32(ImPlotCol_AxisText); + axis.ColorBg = GetStyleColorU32(ImPlotCol_AxisBg); + axis.ColorHov = GetStyleColorU32(ImPlotCol_AxisBgHovered); + axis.ColorAct = GetStyleColorU32(ImPlotCol_AxisBgActive); + // axis.ColorHiLi = IM_COL32_BLACK_TRANS; +} + +void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignmentData* align) { + + ImPlotContext& gp = *GImPlot; + + const float T = ImGui::GetTextLineHeight(); + const float P = gp.Style.LabelPadding.y; + const float K = gp.Style.MinorTickLen.x; + + int count_T = 0; + int count_B = 0; + float last_T = plot.AxesRect.Min.y; + float last_B = plot.AxesRect.Max.y; + + for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { // FYI: can iterate forward + ImPlotAxis& axis = plot.XAxis(i); + if (!axis.Enabled) + continue; + const bool label = axis.HasLabel(); + const bool ticks = axis.HasTickLabels(); + const bool opp = axis.IsOpposite(); + const bool time = axis.Scale == ImPlotScale_Time; + if (opp) { + if (count_T++ > 0) + pad_T += K + P; + if (label) + pad_T += T + P; + if (ticks) + pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); + axis.Datum1 = plot.CanvasRect.Min.y + pad_T; + axis.Datum2 = last_T; + last_T = axis.Datum1; + } + else { + if (count_B++ > 0) + pad_B += K + P; + if (label) + pad_B += T + P; + if (ticks) + pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); + axis.Datum1 = plot.CanvasRect.Max.y - pad_B; + axis.Datum2 = last_B; + last_B = axis.Datum1; + } + } + + if (align) { + count_T = count_B = 0; + float delta_T, delta_B; + align->Update(pad_T,pad_B,delta_T,delta_B); + for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { + ImPlotAxis& axis = plot.XAxis(i); + if (!axis.Enabled) + continue; + if (axis.IsOpposite()) { + axis.Datum1 += delta_T; + axis.Datum2 += count_T++ > 1 ? delta_T : 0; + } + else { + axis.Datum1 -= delta_B; + axis.Datum2 -= count_B++ > 1 ? delta_B : 0; + } + } + } +} + +void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignmentData* align) { + + // [ pad_L ] [ pad_R ] + // .................CanvasRect................ + // :TPWPK.PTPWP _____PlotRect____ PWPTP.KPWPT: + // :A # |- A # |- -| # A -| # A: + // :X | X | | X | x: + // :I # |- I # |- -| # I -| # I: + // :S | S | | S | S: + // :3 # |- 0 # |-_______________-| # 1 -| # 2: + // :.........................................: + // + // T = text height + // P = label padding + // K = minor tick length + // W = label width + + ImPlotContext& gp = *GImPlot; + + const float T = ImGui::GetTextLineHeight(); + const float P = gp.Style.LabelPadding.x; + const float K = gp.Style.MinorTickLen.y; + + int count_L = 0; + int count_R = 0; + float last_L = plot.AxesRect.Min.x; + float last_R = plot.AxesRect.Max.x; + + for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { // FYI: can iterate forward + ImPlotAxis& axis = plot.YAxis(i); + if (!axis.Enabled) + continue; + const bool label = axis.HasLabel(); + const bool ticks = axis.HasTickLabels(); + const bool opp = axis.IsOpposite(); + if (opp) { + if (count_R++ > 0) + pad_R += K + P; + if (label) + pad_R += T + P; + if (ticks) + pad_R += axis.Ticker.MaxSize.x + P; + axis.Datum1 = plot.CanvasRect.Max.x - pad_R; + axis.Datum2 = last_R; + last_R = axis.Datum1; + } + else { + if (count_L++ > 0) + pad_L += K + P; + if (label) + pad_L += T + P; + if (ticks) + pad_L += axis.Ticker.MaxSize.x + P; + axis.Datum1 = plot.CanvasRect.Min.x + pad_L; + axis.Datum2 = last_L; + last_L = axis.Datum1; + } + } + + plot.PlotRect.Min.x = plot.CanvasRect.Min.x + pad_L; + plot.PlotRect.Max.x = plot.CanvasRect.Max.x - pad_R; + + if (align) { + count_L = count_R = 0; + float delta_L, delta_R; + align->Update(pad_L,pad_R,delta_L,delta_R); + for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { + ImPlotAxis& axis = plot.YAxis(i); + if (!axis.Enabled) + continue; + if (axis.IsOpposite()) { + axis.Datum1 -= delta_R; + axis.Datum2 -= count_R++ > 1 ? delta_R : 0; + } + else { + axis.Datum1 += delta_L; + axis.Datum2 += count_L++ > 1 ? delta_L : 0; + } + } + } +} + +//----------------------------------------------------------------------------- +// RENDERING +//----------------------------------------------------------------------------- + +static inline void RenderGridLinesX(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticker.TickCount() / rect.GetWidth(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticker.TickCount(); t++) { + const ImPlotTick& xt = ticker.Ticks[t]; + if (xt.PixelPos < rect.Min.x || xt.PixelPos > rect.Max.x) + continue; + if (xt.Level == 0) { + if (xt.Major) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_min, size_min); + } + } +} + +static inline void RenderGridLinesY(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticker.TickCount() / rect.GetHeight(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticker.TickCount(); t++) { + const ImPlotTick& yt = ticker.Ticks[t]; + if (yt.PixelPos < rect.Min.y || yt.PixelPos > rect.Max.y) + continue; + if (yt.Major) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_min, size_min); + } +} + +static inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min, const ImVec2& p_max, const ImVec4& col) { + const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f)); + const ImU32 col_bd = ImGui::GetColorU32(col); + DrawList.AddRectFilled(p_min, p_max, col_bg); + DrawList.AddRect(p_min, p_max, col_bd); +} + +//----------------------------------------------------------------------------- +// Input Handling +//----------------------------------------------------------------------------- + +static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f; +static const float BOX_SELECT_DRAG_THRESHOLD = 4.0f; + +bool UpdateInput(ImPlotPlot& plot) { + + bool changed = false; + + ImPlotContext& gp = *GImPlot; + ImGuiIO& IO = ImGui::GetIO(); + + // BUTTON STATE ----------------------------------------------------------- + + const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowItemOverlap + | ImGuiButtonFlags_PressedOnClick + | ImGuiButtonFlags_PressedOnDoubleClick + | ImGuiButtonFlags_MouseButtonLeft + | ImGuiButtonFlags_MouseButtonRight + | ImGuiButtonFlags_MouseButtonMiddle; + const ImGuiButtonFlags axis_button_flags = ImGuiButtonFlags_FlattenChildren + | plot_button_flags; + + const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect,plot.ID,&plot.Hovered,&plot.Held,plot_button_flags); + ImGui::SetItemAllowOverlap(); + + if (plot_clicked) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) && IO.MouseClicked[gp.InputMap.Select] && ImHasFlag(IO.KeyMods, gp.InputMap.SelectMod)) { + plot.Selecting = true; + plot.SelectStart = IO.MousePos; + plot.SelectRect = ImRect(0,0,0,0); + } + if (IO.MouseDoubleClicked[gp.InputMap.Fit]) { + plot.FitThisFrame = true; + for (int i = 0; i < ImAxis_COUNT; ++i) + plot.Axes[i].FitThisFrame = true; + } + } + + const bool can_pan = IO.MouseDown[gp.InputMap.Pan] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod); + + plot.Held = plot.Held && can_pan; + + bool x_click[IMPLOT_NUM_X_AXES] = {false}; + bool x_held[IMPLOT_NUM_X_AXES] = {false}; + bool x_hov[IMPLOT_NUM_X_AXES] = {false}; + + bool y_click[IMPLOT_NUM_Y_AXES] = {false}; + bool y_held[IMPLOT_NUM_Y_AXES] = {false}; + bool y_hov[IMPLOT_NUM_Y_AXES] = {false}; + + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& xax = plot.XAxis(i); + if (xax.Enabled) { + ImGui::KeepAliveID(xax.ID); + x_click[i] = ImGui::ButtonBehavior(xax.HoverRect,xax.ID,&xax.Hovered,&xax.Held,axis_button_flags); + if (x_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit]) + plot.FitThisFrame = xax.FitThisFrame = true; + xax.Held = xax.Held && can_pan; + x_hov[i] = xax.Hovered || plot.Hovered; + x_held[i] = xax.Held || plot.Held; + } + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& yax = plot.YAxis(i); + if (yax.Enabled) { + ImGui::KeepAliveID(yax.ID); + y_click[i] = ImGui::ButtonBehavior(yax.HoverRect,yax.ID,&yax.Hovered,&yax.Held,axis_button_flags); + if (y_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit]) + plot.FitThisFrame = yax.FitThisFrame = true; + yax.Held = yax.Held && can_pan; + y_hov[i] = yax.Hovered || plot.Hovered; + y_held[i] = yax.Held || plot.Held; + } + } + + // cancel due to DND activity + if (GImGui->DragDropActive || (IO.KeyMods == gp.InputMap.OverrideMod && gp.InputMap.OverrideMod != 0)) + return false; + + // STATE ------------------------------------------------------------------- + + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + + const bool any_x_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_y_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + const bool any_hov = any_x_hov || any_y_hov; + const bool any_held = any_x_held || any_y_held; + + const ImVec2 select_drag = ImGui::GetMouseDragDelta(gp.InputMap.Select); + const ImVec2 pan_drag = ImGui::GetMouseDragDelta(gp.InputMap.Pan); + const float select_drag_sq = ImLengthSqr(select_drag); + const float pan_drag_sq = ImLengthSqr(pan_drag); + const bool selecting = plot.Selecting && select_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD; + const bool panning = any_held && pan_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD; + + // CONTEXT MENU ----------------------------------------------------------- + + if (IO.MouseReleased[gp.InputMap.Menu] && !plot.ContextLocked) + gp.OpenContextThisFrame = true; + + if (selecting || panning) + plot.ContextLocked = true; + else if (!(IO.MouseDown[gp.InputMap.Menu] || IO.MouseReleased[gp.InputMap.Menu])) + plot.ContextLocked = false; + + // DRAG INPUT ------------------------------------------------------------- + + if (any_held && !plot.Selecting) { + int drag_direction = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_held[i] && !x_axis.IsInputLocked()) { + drag_direction |= (1 << 1); + bool increasing = x_axis.IsInverted() ? IO.MouseDelta.x > 0 : IO.MouseDelta.x < 0; + if (IO.MouseDelta.x != 0 && !x_axis.IsPanLocked(increasing)) { + const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - IO.MouseDelta.x); + const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x - IO.MouseDelta.x); + x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l); + x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r); + if (axis_equal && x_axis.OrthoAxis != nullptr) + x_axis.OrthoAxis->SetAspect(x_axis.GetAspect()); + changed = true; + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_held[i] && !y_axis.IsInputLocked()) { + drag_direction |= (1 << 2); + bool increasing = y_axis.IsInverted() ? IO.MouseDelta.y < 0 : IO.MouseDelta.y > 0; + if (IO.MouseDelta.y != 0 && !y_axis.IsPanLocked(increasing)) { + const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - IO.MouseDelta.y); + const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y - IO.MouseDelta.y); + y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b); + y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t); + if (axis_equal && y_axis.OrthoAxis != nullptr) + y_axis.OrthoAxis->SetAspect(y_axis.GetAspect()); + changed = true; + } + } + } + if (IO.MouseDragMaxDistanceSqr[gp.InputMap.Pan] > MOUSE_CURSOR_DRAG_THRESHOLD) { + switch (drag_direction) { + case 0 : ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); break; + case (1 << 1) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); break; + case (1 << 2) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); break; + default : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); break; + } + } + } + + // SCROLL INPUT ----------------------------------------------------------- + + if (any_hov && IO.MouseWheel != 0 && ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod)) { + + float zoom_rate = gp.InputMap.ZoomRate; + if (IO.MouseWheel > 0) + zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate)); + ImVec2 rect_size = plot.PlotRect.GetSize(); + float tx = ImRemap(IO.MousePos.x, plot.PlotRect.Min.x, plot.PlotRect.Max.x, 0.0f, 1.0f); + float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f); + + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + const bool equal_zoom = axis_equal && x_axis.OrthoAxis != nullptr; + const bool equal_locked = (equal_zoom != false) && x_axis.OrthoAxis->IsInputLocked(); + if (x_hov[i] && !x_axis.IsInputLocked() && !equal_locked) { + float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; + const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate * correction); + const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate * correction); + x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l); + x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r); + if (axis_equal && x_axis.OrthoAxis != nullptr) + x_axis.OrthoAxis->SetAspect(x_axis.GetAspect()); + changed = true; + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + const bool equal_zoom = axis_equal && y_axis.OrthoAxis != nullptr; + const bool equal_locked = equal_zoom && y_axis.OrthoAxis->IsInputLocked(); + if (y_hov[i] && !y_axis.IsInputLocked() && !equal_locked) { + float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; + const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate * correction); + const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate * correction); + y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b); + y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t); + if (axis_equal && y_axis.OrthoAxis != nullptr) + y_axis.OrthoAxis->SetAspect(y_axis.GetAspect()); + changed = true; + } + } + } + + // BOX-SELECTION ---------------------------------------------------------- + + if (plot.Selecting) { + const ImVec2 d = plot.SelectStart - IO.MousePos; + const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImFabs(d.x) > 2; + const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod) && ImFabs(d.y) > 2; + // confirm + if (IO.MouseReleased[gp.InputMap.Select]) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.IsInputLocked() && x_can_change) { + const double p1 = x_axis.PixelsToPlot(plot.SelectStart.x); + const double p2 = x_axis.PixelsToPlot(IO.MousePos.x); + x_axis.SetMin(ImMin(p1, p2)); + x_axis.SetMax(ImMax(p1, p2)); + changed = true; + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.IsInputLocked() && y_can_change) { + const double p1 = y_axis.PixelsToPlot(plot.SelectStart.y); + const double p2 = y_axis.PixelsToPlot(IO.MousePos.y); + y_axis.SetMin(ImMin(p1, p2)); + y_axis.SetMax(ImMax(p1, p2)); + changed = true; + } + } + if (x_can_change || y_can_change || (ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod))) + gp.OpenContextThisFrame = false; + plot.Selected = plot.Selecting = false; + } + // cancel + else if (IO.MouseReleased[gp.InputMap.SelectCancel]) { + plot.Selected = plot.Selecting = false; + gp.OpenContextThisFrame = false; + } + else if (ImLengthSqr(d) > BOX_SELECT_DRAG_THRESHOLD) { + // bad selection + if (plot.IsInputLocked()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); + gp.OpenContextThisFrame = false; + plot.Selected = false; + } + else { + // TODO: Handle only min or max locked cases + const bool full_width = ImHasFlag(IO.KeyMods, gp.InputMap.SelectHorzMod) || AllAxesInputLocked(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool full_height = ImHasFlag(IO.KeyMods, gp.InputMap.SelectVertMod) || AllAxesInputLocked(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + plot.SelectRect.Min.x = full_width ? plot.PlotRect.Min.x : ImMin(plot.SelectStart.x, IO.MousePos.x); + plot.SelectRect.Max.x = full_width ? plot.PlotRect.Max.x : ImMax(plot.SelectStart.x, IO.MousePos.x); + plot.SelectRect.Min.y = full_height ? plot.PlotRect.Min.y : ImMin(plot.SelectStart.y, IO.MousePos.y); + plot.SelectRect.Max.y = full_height ? plot.PlotRect.Max.y : ImMax(plot.SelectStart.y, IO.MousePos.y); + plot.SelectRect.Min -= plot.PlotRect.Min; + plot.SelectRect.Max -= plot.PlotRect.Min; + plot.Selected = true; + } + } + else { + plot.Selected = false; + } + } + return changed; +} + +//----------------------------------------------------------------------------- +// Next Plot Data (Legacy) +//----------------------------------------------------------------------------- + +void ApplyNextPlotData(ImAxis idx) { + ImPlotContext& gp = *GImPlot; + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + if (!axis.Enabled) + return; + double* npd_lmin = gp.NextPlotData.LinkedMin[idx]; + double* npd_lmax = gp.NextPlotData.LinkedMax[idx]; + bool npd_rngh = gp.NextPlotData.HasRange[idx]; + ImPlotCond npd_rngc = gp.NextPlotData.RangeCond[idx]; + ImPlotRange npd_rngv = gp.NextPlotData.Range[idx]; + axis.LinkedMin = npd_lmin; + axis.LinkedMax = npd_lmax; + axis.PullLinks(); + if (npd_rngh) { + if (!plot.Initialized || npd_rngc == ImPlotCond_Always) + axis.SetRange(npd_rngv); + } + axis.HasRange = npd_rngh; + axis.RangeCond = npd_rngc; +} + +//----------------------------------------------------------------------------- +// Setup +//----------------------------------------------------------------------------- + +void SetupAxis(ImAxis idx, const char* label, ImPlotAxisFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + // get plot and axis + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + // set ID + axis.ID = plot.ID + idx + 1; + // check and set flags + if (plot.JustCreated || flags != axis.PreviousFlags) + axis.Flags = flags; + axis.PreviousFlags = flags; + // enable axis + axis.Enabled = true; + // set label + plot.SetAxisLabel(axis,label); + // cache colors + UpdateAxisColors(axis); +} + +void SetupAxisLimits(ImAxis idx, double min_lim, double max_lim, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + if (!plot.Initialized || cond == ImPlotCond_Always) + axis.SetRange(min_lim, max_lim); + axis.HasRange = true; + axis.RangeCond = cond; +} + +void SetupAxisFormat(ImAxis idx, const char* fmt) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.HasFormatSpec = fmt != nullptr; + if (fmt != nullptr) + ImStrncpy(axis.FormatSpec,fmt,sizeof(axis.FormatSpec)); +} + +void SetupAxisLinks(ImAxis idx, double* min_lnk, double* max_lnk) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.LinkedMin = min_lnk; + axis.LinkedMax = max_lnk; + axis.PullLinks(); +} + +void SetupAxisFormat(ImAxis idx, ImPlotFormatter formatter, void* data) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Formatter = formatter; + axis.FormatterData = data; +} + +void SetupAxisTicks(ImAxis idx, const double* values, int n_ticks, const char* const labels[], bool show_default) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ShowDefaultTicks = show_default; + AddTicksCustom(values, + labels, + n_ticks, + axis.Ticker, + axis.Formatter ? axis.Formatter : Formatter_Default, + (axis.Formatter && axis.FormatterData) ? axis.FormatterData : axis.HasFormatSpec ? axis.FormatSpec : (void*)IMPLOT_LABEL_FORMAT); +} + +void SetupAxisTicks(ImAxis idx, double v_min, double v_max, int n_ticks, const char* const labels[], bool show_default) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + n_ticks = n_ticks < 2 ? 2 : n_ticks; + FillRange(gp.TempDouble1, n_ticks, v_min, v_max); + SetupAxisTicks(idx, gp.TempDouble1.Data, n_ticks, labels, show_default); +} + +void SetupAxisScale(ImAxis idx, ImPlotScale scale) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Scale = scale; + switch (scale) + { + case ImPlotScale_Time: + axis.TransformForward = nullptr; + axis.TransformInverse = nullptr; + axis.TransformData = nullptr; + axis.Locator = Locator_Time; + axis.ConstraintRange = ImPlotRange(IMPLOT_MIN_TIME, IMPLOT_MAX_TIME); + axis.Ticker.Levels = 2; + break; + case ImPlotScale_Log10: + axis.TransformForward = TransformForward_Log10; + axis.TransformInverse = TransformInverse_Log10; + axis.TransformData = nullptr; + axis.Locator = Locator_Log10; + axis.ConstraintRange = ImPlotRange(DBL_MIN, INFINITY); + break; + case ImPlotScale_SymLog: + axis.TransformForward = TransformForward_SymLog; + axis.TransformInverse = TransformInverse_SymLog; + axis.TransformData = nullptr; + axis.Locator = Locator_SymLog; + axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY); + break; + default: + axis.TransformForward = nullptr; + axis.TransformInverse = nullptr; + axis.TransformData = nullptr; + axis.Locator = nullptr; + axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY); + break; + } +} + +void SetupAxisScale(ImAxis idx, ImPlotTransform fwd, ImPlotTransform inv, void* data) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Scale = IMPLOT_AUTO; + axis.TransformForward = fwd; + axis.TransformInverse = inv; + axis.TransformData = data; +} + +void SetupAxisLimitsConstraints(ImAxis idx, double v_min, double v_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ConstraintRange.Min = v_min; + axis.ConstraintRange.Max = v_max; +} + +void SetupAxisZoomConstraints(ImAxis idx, double z_min, double z_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ConstraintZoom.Min = z_min; + axis.ConstraintZoom.Max = z_max; +} + +void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags) { + SetupAxis(ImAxis_X1, x_label, x_flags); + SetupAxis(ImAxis_Y1, y_label, y_flags); +} + +void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) { + SetupAxisLimits(ImAxis_X1, x_min, x_max, cond); + SetupAxisLimits(ImAxis_Y1, y_min, y_max, cond); +} + +void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, + "SetupLegend() needs to be called within an itemized context!"); + ImPlotLegend& legend = gp.CurrentItems->Legend; + // check and set location + if (location != legend.PreviousLocation) + legend.Location = location; + legend.PreviousLocation = location; + // check and set flags + if (flags != legend.PreviousFlags) + legend.Flags = flags; + legend.PreviousFlags = flags; +} + +void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + gp.CurrentPlot->MouseTextLocation = location; + gp.CurrentPlot->MouseTextFlags = flags; +} + +//----------------------------------------------------------------------------- +// SetNext +//----------------------------------------------------------------------------- + +void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLimits() needs to be called before BeginPlot()!"); + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + gp.NextPlotData.HasRange[axis] = true; + gp.NextPlotData.RangeCond[axis] = cond; + gp.NextPlotData.Range[axis].Min = v_min; + gp.NextPlotData.Range[axis].Max = v_max; +} + +void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLinks() needs to be called before BeginPlot()!"); + gp.NextPlotData.LinkedMin[axis] = link_min; + gp.NextPlotData.LinkedMax[axis] = link_max; +} + +void SetNextAxisToFit(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisToFit() needs to be called before BeginPlot()!"); + gp.NextPlotData.Fit[axis] = true; +} + +void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) { + SetNextAxisLimits(ImAxis_X1, x_min, x_max, cond); + SetNextAxisLimits(ImAxis_Y1, y_min, y_max, cond); +} + +void SetNextAxesToFit() { + for (int i = 0; i < ImAxis_COUNT; ++i) + SetNextAxisToFit(i); +} + +//----------------------------------------------------------------------------- +// BeginPlot +//----------------------------------------------------------------------------- + +bool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Mismatched BeginPlot()/EndPlot()!"); + + // FRONT MATTER ----------------------------------------------------------- + + if (gp.CurrentSubplot != nullptr) + ImGui::PushID(gp.CurrentSubplot->CurrentIdx); + + // get globals + ImGuiContext &G = *GImGui; + ImGuiWindow* Window = G.CurrentWindow; + + // skip if needed + if (Window->SkipItems && !gp.CurrentSubplot) { + ResetCtxForNextPlot(GImPlot); + return false; + } + + // ID and age (TODO: keep track of plot age in frames) + const ImGuiID ID = Window->GetID(title_id); + const bool just_created = gp.Plots.GetByKey(ID) == nullptr; + gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID); + + ImPlotPlot &plot = *gp.CurrentPlot; + plot.ID = ID; + plot.Items.ID = ID - 1; + plot.JustCreated = just_created; + plot.SetupLocked = false; + + // check flags + if (plot.JustCreated) + plot.Flags = flags; + else if (flags != plot.PreviousFlags) + plot.Flags = flags; + plot.PreviousFlags = flags; + + // setup default axes + if (plot.JustCreated) { + SetupAxis(ImAxis_X1); + SetupAxis(ImAxis_Y1); + } + + // reset axes + for (int i = 0; i < ImAxis_COUNT; ++i) { + plot.Axes[i].Reset(); + UpdateAxisColors(plot.Axes[i]); + } + // ensure first axes enabled + plot.Axes[ImAxis_X1].Enabled = true; + plot.Axes[ImAxis_Y1].Enabled = true; + // set initial axes + plot.CurrentX = ImAxis_X1; + plot.CurrentY = ImAxis_Y1; + + // process next plot data (legacy) + for (int i = 0; i < ImAxis_COUNT; ++i) + ApplyNextPlotData(i); + + // capture scroll with a child region + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoChild)) { + ImVec2 child_size; + if (gp.CurrentSubplot != nullptr) + child_size = gp.CurrentSubplot->CellSize; + else + child_size = ImVec2(size.x == 0 ? gp.Style.PlotDefaultSize.x : size.x, size.y == 0 ? gp.Style.PlotDefaultSize.y : size.y); + ImGui::BeginChild(title_id, child_size, false, ImGuiWindowFlags_NoScrollbar); + Window = ImGui::GetCurrentWindow(); + Window->ScrollMax.y = 1.0f; + gp.ChildWindowMade = true; + } + else { + gp.ChildWindowMade = false; + } + + // clear text buffers + plot.ClearTextBuffer(); + plot.SetTitle(title_id); + + // set frame size + ImVec2 frame_size; + if (gp.CurrentSubplot != nullptr) + frame_size = gp.CurrentSubplot->CellSize; + else + frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y); + + if (frame_size.x < gp.Style.PlotMinSize.x && (size.x < 0.0f || gp.CurrentSubplot != nullptr)) + frame_size.x = gp.Style.PlotMinSize.x; + if (frame_size.y < gp.Style.PlotMinSize.y && (size.y < 0.0f || gp.CurrentSubplot != nullptr)) + frame_size.y = gp.Style.PlotMinSize.y; + + plot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(plot.FrameRect); + if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect) && !gp.CurrentSubplot) { + ResetCtxForNextPlot(GImPlot); + return false; + } + + // setup items (or dont) + if (gp.CurrentItems == nullptr) + gp.CurrentItems = &plot.Items; + + return true; +} + +//----------------------------------------------------------------------------- +// SetupFinish +//----------------------------------------------------------------------------- + +void SetupFinish() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetupFinish needs to be called after BeginPlot!"); + + ImGuiContext& G = *GImGui; + ImDrawList& DrawList = *G.CurrentWindow->DrawList; + const ImGuiStyle& Style = G.Style; + + ImPlotPlot &plot = *gp.CurrentPlot; + + // lock setup + plot.SetupLocked = true; + + // finalize axes and set default formatter/locator + for (int i = 0; i < ImAxis_COUNT; ++i) { + ImPlotAxis& axis = plot.Axes[i]; + if (axis.Enabled) { + axis.Constrain(); + if (!plot.Initialized && axis.CanInitFit()) + plot.FitThisFrame = axis.FitThisFrame = true; + } + if (axis.Formatter == nullptr) { + axis.Formatter = Formatter_Default; + if (axis.HasFormatSpec) + axis.FormatterData = axis.FormatSpec; + else + axis.FormatterData = (void*)IMPLOT_LABEL_FORMAT; + } + if (axis.Locator == nullptr) { + axis.Locator = Locator_Default; + } + } + + // setup nullptr orthogonal axes + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + for (int ix = ImAxis_X1, iy = ImAxis_Y1; ix < ImAxis_Y1 || iy < ImAxis_COUNT; ++ix, ++iy) { + ImPlotAxis& x_axis = plot.Axes[ix]; + ImPlotAxis& y_axis = plot.Axes[iy]; + if (x_axis.Enabled && y_axis.Enabled) { + if (x_axis.OrthoAxis == nullptr) + x_axis.OrthoAxis = &y_axis; + if (y_axis.OrthoAxis == nullptr) + y_axis.OrthoAxis = &x_axis; + } + else if (x_axis.Enabled) + { + if (x_axis.OrthoAxis == nullptr && !axis_equal) + x_axis.OrthoAxis = &plot.Axes[ImAxis_Y1]; + } + else if (y_axis.Enabled) { + if (y_axis.OrthoAxis == nullptr && !axis_equal) + y_axis.OrthoAxis = &plot.Axes[ImAxis_X1]; + } + } + + // canvas/axes bb + plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding); + plot.AxesRect = plot.FrameRect; + + // outside legend adjustments + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0 && ImHasFlag(plot.Items.Legend.Flags, ImPlotLegendFlags_Outside)) { + ImPlotLegend& legend = plot.Items.Legend; + const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz); + const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East); + const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West); + const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South); + const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North); + if ((west && !horz) || (west && horz && !north && !south)) { + plot.CanvasRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x); + plot.AxesRect.Min.x += (legend_size.x + gp.Style.PlotPadding.x); + } + if ((east && !horz) || (east && horz && !north && !south)) { + plot.CanvasRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x); + plot.AxesRect.Max.x -= (legend_size.x + gp.Style.PlotPadding.x); + } + if ((north && horz) || (north && !horz && !west && !east)) { + plot.CanvasRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y); + plot.AxesRect.Min.y += (legend_size.y + gp.Style.PlotPadding.y); + } + if ((south && horz) || (south && !horz && !west && !east)) { + plot.CanvasRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y); + plot.AxesRect.Max.y -= (legend_size.y + gp.Style.PlotPadding.y); + } + } + + // plot bb + float pad_top = 0, pad_bot = 0, pad_left = 0, pad_right = 0; + + // (0) calc top padding form title + ImVec2 title_size(0.0f, 0.0f); + if (plot.HasTitle()) + title_size = ImGui::CalcTextSize(plot.GetTitle(), nullptr, true); + if (title_size.x > 0) { + pad_top += title_size.y + gp.Style.LabelPadding.y; + plot.AxesRect.Min.y += gp.Style.PlotPadding.y + pad_top; + } + + // (1) calc addition top padding and bot padding + PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH); + + const float plot_height = plot.CanvasRect.GetHeight() - pad_top - pad_bot; + + // (2) get y tick labels (needed for left/right pad) + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& axis = plot.YAxis(i); + if (axis.WillRender() && axis.ShowDefaultTicks) { + axis.Locator(axis.Ticker, axis.Range, plot_height, true, axis.Formatter, axis.FormatterData); + } + } + + // (3) calc left/right pad + PadAndDatumAxesY(plot,pad_left,pad_right,gp.CurrentAlignmentV); + + const float plot_width = plot.CanvasRect.GetWidth() - pad_left - pad_right; + + // (4) get x ticks + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& axis = plot.XAxis(i); + if (axis.WillRender() && axis.ShowDefaultTicks) { + axis.Locator(axis.Ticker, axis.Range, plot_width, false, axis.Formatter, axis.FormatterData); + } + } + + // (5) calc plot bb + plot.PlotRect = ImRect(plot.CanvasRect.Min + ImVec2(pad_left, pad_top), plot.CanvasRect.Max - ImVec2(pad_right, pad_bot)); + + // HOVER------------------------------------------------------------ + + // axes hover rect, pixel ranges + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& xax = plot.XAxis(i); + xax.HoverRect = ImRect(ImVec2(plot.PlotRect.Min.x, ImMin(xax.Datum1,xax.Datum2)), + ImVec2(plot.PlotRect.Max.x, ImMax(xax.Datum1,xax.Datum2))); + xax.PixelMin = xax.IsInverted() ? plot.PlotRect.Max.x : plot.PlotRect.Min.x; + xax.PixelMax = xax.IsInverted() ? plot.PlotRect.Min.x : plot.PlotRect.Max.x; + xax.UpdateTransformCache(); + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& yax = plot.YAxis(i); + yax.HoverRect = ImRect(ImVec2(ImMin(yax.Datum1,yax.Datum2),plot.PlotRect.Min.y), + ImVec2(ImMax(yax.Datum1,yax.Datum2),plot.PlotRect.Max.y)); + yax.PixelMin = yax.IsInverted() ? plot.PlotRect.Min.y : plot.PlotRect.Max.y; + yax.PixelMax = yax.IsInverted() ? plot.PlotRect.Max.y : plot.PlotRect.Min.y; + yax.UpdateTransformCache(); + } + // Equal axis constraint. Must happen after we set Pixels + // constrain equal axes for primary x and y if not approximately equal + // constrains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case + if (axis_equal) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.OrthoAxis == nullptr) + continue; + double xar = x_axis.GetAspect(); + double yar = x_axis.OrthoAxis->GetAspect(); + // edge case: user has set x range this frame, so fit y to x so that we honor their request for x range + // NB: because of feedback across several frames, the user's x request may not be perfectly honored + if (x_axis.HasRange) + x_axis.OrthoAxis->SetAspect(xar); + else if (!ImAlmostEqual(xar,yar) && !x_axis.OrthoAxis->IsInputLocked()) + x_axis.SetAspect(yar); + } + } + + // INPUT ------------------------------------------------------------------ + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoInputs)) + UpdateInput(plot); + + // fit from FitNextPlotAxes or auto fit + for (int i = 0; i < ImAxis_COUNT; ++i) { + if (gp.NextPlotData.Fit[i] || plot.Axes[i].IsAutoFitting()) { + plot.FitThisFrame = true; + plot.Axes[i].FitThisFrame = true; + } + } + + // RENDER ----------------------------------------------------------------- + + const float txt_height = ImGui::GetTextLineHeight(); + + // render frame + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoFrame)) + ImGui::RenderFrame(plot.FrameRect.Min, plot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, Style.FrameRounding); + + // grid bg + DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBg)); + + // transform ticks + for (int i = 0; i < ImAxis_COUNT; i++) { + ImPlotAxis& axis = plot.Axes[i]; + if (axis.WillRender()) { + for (int t = 0; t < axis.Ticker.TickCount(); t++) { + ImPlotTick& tk = axis.Ticker.Ticks[t]; + tk.PixelPos = IM_ROUND(axis.PlotToPixels(tk.PlotPos)); + } + } + } + + // render grid (background) + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.Enabled && x_axis.HasGridLines() && !x_axis.IsForeground()) + RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.Enabled && y_axis.HasGridLines() && !y_axis.IsForeground()) + RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); + } + + // render x axis button, label, tick labels + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& ax = plot.XAxis(i); + if (!ax.Enabled) + continue; + if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight)) + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov); + else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi); + ax.ColorHiLi = IM_COL32_BLACK_TRANS; + } + else if (ax.ColorBg != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg); + } + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + if (ax.HasLabel()) { + const char* label = plot.GetAxisLabel(ax); + const ImVec2 label_size = ImGui::CalcTextSize(label); + const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f) + + (tkr.Levels - 1) * (txt_height + gp.Style.LabelPadding.y) + + gp.Style.LabelPadding.y; + const ImVec2 label_pos(plot.PlotRect.GetCenter().x - label_size.x * 0.5f, + opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset); + DrawList.AddText(label_pos, ax.ColorTxt, label); + } + if (ax.HasTickLabels()) { + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + const float datum = ax.Datum1 + (opp ? (-gp.Style.LabelPadding.y -txt_height -tk.Level * (txt_height + gp.Style.LabelPadding.y)) + : gp.Style.LabelPadding.y + tk.Level * (txt_height + gp.Style.LabelPadding.y)); + if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.x - 1 && tk.PixelPos <= plot.PlotRect.Max.x + 1) { + ImVec2 start(tk.PixelPos - 0.5f * tk.LabelSize.x, datum); + DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); + } + } + } + } + + // render y axis button, label, tick labels + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& ax = plot.YAxis(i); + if (!ax.Enabled) + continue; + if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight)) + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov); + else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi); + ax.ColorHiLi = IM_COL32_BLACK_TRANS; + } + else if (ax.ColorBg != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg); + } + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + if (ax.HasLabel()) { + const char* label = plot.GetAxisLabel(ax); + const ImVec2 label_size = CalcTextSizeVertical(label); + const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f) + + gp.Style.LabelPadding.x; + const ImVec2 label_pos(opp ? ax.Datum1 + label_offset : ax.Datum1 - label_offset - label_size.x, + plot.PlotRect.GetCenter().y + label_size.y * 0.5f); + AddTextVertical(&DrawList, label_pos, ax.ColorTxt, label); + } + if (ax.HasTickLabels()) { + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + const float datum = ax.Datum1 + (opp ? gp.Style.LabelPadding.x : (-gp.Style.LabelPadding.x - tk.LabelSize.x)); + if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.y - 1 && tk.PixelPos <= plot.PlotRect.Max.y + 1) { + ImVec2 start(datum, tk.PixelPos - 0.5f * tk.LabelSize.y); + DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); + } + } + } + } + + + // clear legend (TODO: put elsewhere) + plot.Items.Legend.Reset(); + // push ID to set item hashes (NB: !!!THIS PROBABLY NEEDS TO BE IN BEGIN PLOT!!!!) + ImGui::PushOverrideID(gp.CurrentItems->ID); +} + +//----------------------------------------------------------------------------- +// EndPlot() +//----------------------------------------------------------------------------- + +void EndPlot() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!"); + + SetupLock(); + + ImGuiContext &G = *GImGui; + ImPlotPlot &plot = *gp.CurrentPlot; + ImGuiWindow * Window = G.CurrentWindow; + ImDrawList & DrawList = *Window->DrawList; + const ImGuiIO & IO = ImGui::GetIO(); + + // FINAL RENDER ----------------------------------------------------------- + + const bool render_border = gp.Style.PlotBorderSize > 0 && gp.Style.Colors[ImPlotCol_PlotBorder].w > 0; + const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + + ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true); + + // render grid (foreground) + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.Enabled && x_axis.HasGridLines() && x_axis.IsForeground()) + RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.Enabled && y_axis.HasGridLines() && y_axis.IsForeground()) + RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); + } + + + // render title + if (plot.HasTitle()) { + ImU32 col = GetStyleColorU32(ImPlotCol_TitleText); + AddTextCentered(&DrawList,ImVec2(plot.PlotRect.GetCenter().x, plot.CanvasRect.Min.y),col,plot.GetTitle()); + } + + // render x ticks + int count_B = 0, count_T = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + const ImPlotAxis& ax = plot.XAxis(i); + if (!ax.Enabled) + continue; + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + const bool aux = ((opp && count_T > 0)||(!opp && count_B > 0)); + if (ax.HasTickMarks()) { + const float direction = opp ? 1.0f : -1.0f; + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.x || tk.PixelPos > plot.PlotRect.Max.x) + continue; + const ImVec2 start(tk.PixelPos, ax.Datum1); + const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x; + const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x; + DrawList.AddLine(start, start + ImVec2(0,direction*len), ax.ColorTick, thk); + } + if (aux || !render_border) + DrawList.AddLine(ImVec2(plot.PlotRect.Min.x,ax.Datum1), ImVec2(plot.PlotRect.Max.x,ax.Datum1), ax.ColorTick, gp.Style.MinorTickSize.x); + } + count_B += !opp; + count_T += opp; + } + + // render y ticks + int count_L = 0, count_R = 0; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + const ImPlotAxis& ax = plot.YAxis(i); + if (!ax.Enabled) + continue; + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + const bool aux = ((opp && count_R > 0)||(!opp && count_L > 0)); + if (ax.HasTickMarks()) { + const float direction = opp ? -1.0f : 1.0f; + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.y || tk.PixelPos > plot.PlotRect.Max.y) + continue; + const ImVec2 start(ax.Datum1, tk.PixelPos); + const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y; + const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y; + DrawList.AddLine(start, start + ImVec2(direction*len,0), ax.ColorTick, thk); + } + if (aux || !render_border) + DrawList.AddLine(ImVec2(ax.Datum1, plot.PlotRect.Min.y), ImVec2(ax.Datum1, plot.PlotRect.Max.y), ax.ColorTick, gp.Style.MinorTickSize.y); + } + count_L += !opp; + count_R += opp; + } + ImGui::PopClipRect(); + + // render annotations + PushPlotClipRect(); + for (int i = 0; i < gp.Annotations.Size; ++i) { + const char* txt = gp.Annotations.GetText(i); + ImPlotAnnotation& an = gp.Annotations.Annotations[i]; + const ImVec2 txt_size = ImGui::CalcTextSize(txt); + const ImVec2 size = txt_size + gp.Style.AnnotationPadding * 2; + ImVec2 pos = an.Pos; + if (an.Offset.x == 0) + pos.x -= size.x / 2; + else if (an.Offset.x > 0) + pos.x += an.Offset.x; + else + pos.x -= size.x - an.Offset.x; + if (an.Offset.y == 0) + pos.y -= size.y / 2; + else if (an.Offset.y > 0) + pos.y += an.Offset.y; + else + pos.y -= size.y - an.Offset.y; + if (an.Clamp) + pos = ClampLabelPos(pos, size, plot.PlotRect.Min, plot.PlotRect.Max); + ImRect rect(pos,pos+size); + if (an.Offset.x != 0 || an.Offset.y != 0) { + ImVec2 corners[4] = {rect.GetTL(), rect.GetTR(), rect.GetBR(), rect.GetBL()}; + int min_corner = 0; + float min_len = FLT_MAX; + for (int c = 0; c < 4; ++c) { + float len = ImLengthSqr(an.Pos - corners[c]); + if (len < min_len) { + min_corner = c; + min_len = len; + } + } + DrawList.AddLine(an.Pos, corners[min_corner], an.ColorBg); + } + DrawList.AddRectFilled(rect.Min, rect.Max, an.ColorBg); + DrawList.AddText(pos + gp.Style.AnnotationPadding, an.ColorFg, txt); + } + + // render selection + if (plot.Selected) + RenderSelectionRect(DrawList, plot.SelectRect.Min + plot.PlotRect.Min, plot.SelectRect.Max + plot.PlotRect.Min, GetStyleColorVec4(ImPlotCol_Selection)); + + // render crosshairs + if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && plot.Hovered && !(any_x_held || any_y_held) && !plot.Selecting && !plot.Items.Legend.Hovered) { + ImGui::SetMouseCursor(ImGuiMouseCursor_None); + ImVec2 xy = IO.MousePos; + ImVec2 h1(plot.PlotRect.Min.x, xy.y); + ImVec2 h2(xy.x - 5, xy.y); + ImVec2 h3(xy.x + 5, xy.y); + ImVec2 h4(plot.PlotRect.Max.x, xy.y); + ImVec2 v1(xy.x, plot.PlotRect.Min.y); + ImVec2 v2(xy.x, xy.y - 5); + ImVec2 v3(xy.x, xy.y + 5); + ImVec2 v4(xy.x, plot.PlotRect.Max.y); + ImU32 col = GetStyleColorU32(ImPlotCol_Crosshairs); + DrawList.AddLine(h1, h2, col); + DrawList.AddLine(h3, h4, col); + DrawList.AddLine(v1, v2, col); + DrawList.AddLine(v3, v4, col); + } + + // render mouse pos + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText) && (plot.Hovered || ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_ShowAlways))) { + + const bool no_aux = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoAuxAxes); + const bool no_fmt = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoFormat); + + ImGuiTextBuffer& builder = gp.MousePosStringBuilder; + builder.Buf.shrink(0); + char buff[IMPLOT_LABEL_MAX_SIZE]; + + const int num_x = no_aux ? 1 : IMPLOT_NUM_X_AXES; + for (int i = 0; i < num_x; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.Enabled) + continue; + if (i > 0) + builder.append(", ("); + double v = x_axis.PixelsToPlot(IO.MousePos.x); + if (no_fmt) + Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT); + else + LabelAxisValue(x_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true); + builder.append(buff); + if (i > 0) + builder.append(")"); + } + builder.append(", "); + const int num_y = no_aux ? 1 : IMPLOT_NUM_Y_AXES; + for (int i = 0; i < num_y; ++i) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.Enabled) + continue; + if (i > 0) + builder.append(", ("); + double v = y_axis.PixelsToPlot(IO.MousePos.y); + if (no_fmt) + Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT); + else + LabelAxisValue(y_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true); + builder.append(buff); + if (i > 0) + builder.append(")"); + } + + if (!builder.empty()) { + const ImVec2 size = ImGui::CalcTextSize(builder.c_str()); + const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MouseTextLocation, gp.Style.MousePosPadding); + DrawList.AddText(pos, GetStyleColorU32(ImPlotCol_InlayText), builder.c_str()); + } + } + PopPlotClipRect(); + + // axis side switch + if (!plot.Held) { + ImVec2 mouse_pos = ImGui::GetIO().MousePos; + ImRect trigger_rect = plot.PlotRect; + trigger_rect.Expand(-10); + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (ImHasFlag(x_axis.Flags, ImPlotAxisFlags_NoSideSwitch)) + continue; + if (x_axis.Held && plot.PlotRect.Contains(mouse_pos)) { + const bool opp = ImHasFlag(x_axis.Flags, ImPlotAxisFlags_Opposite); + if (!opp) { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Min.y + 5); + if (mouse_pos.y < plot.PlotRect.Max.y - 10) + DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov); + if (rect.Contains(mouse_pos)) + x_axis.Flags |= ImPlotAxisFlags_Opposite; + } + else { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Max.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.y > plot.PlotRect.Min.y + 10) + DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov); + if (rect.Contains(mouse_pos)) + x_axis.Flags &= ~ImPlotAxisFlags_Opposite; + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (ImHasFlag(y_axis.Flags, ImPlotAxisFlags_NoSideSwitch)) + continue; + if (y_axis.Held && plot.PlotRect.Contains(mouse_pos)) { + const bool opp = ImHasFlag(y_axis.Flags, ImPlotAxisFlags_Opposite); + if (!opp) { + ImRect rect(plot.PlotRect.Max.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.x > plot.PlotRect.Min.x + 10) + DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov); + if (rect.Contains(mouse_pos)) + y_axis.Flags |= ImPlotAxisFlags_Opposite; + } + else { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Min.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.x < plot.PlotRect.Max.x - 10) + DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov); + if (rect.Contains(mouse_pos)) + y_axis.Flags &= ~ImPlotAxisFlags_Opposite; + } + } + } + } + + // reset legend hovers + plot.Items.Legend.Hovered = false; + for (int i = 0; i < plot.Items.GetItemCount(); ++i) + plot.Items.GetItemByIndex(i)->LegendHovered = false; + // render legend + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0) { + ImPlotLegend& legend = plot.Items.Legend; + const bool legend_out = ImHasFlag(legend.Flags, ImPlotLegendFlags_Outside); + const bool legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz); + const ImVec2 legend_pos = GetLocationPos(legend_out ? plot.FrameRect : plot.PlotRect, + legend_size, + legend.Location, + legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding); + legend.Rect = ImRect(legend_pos, legend_pos + legend_size); + // test hover + legend.Hovered = ImGui::IsWindowHovered() && legend.Rect.Contains(IO.MousePos); + + if (legend_out) + ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true); + else + PushPlotClipRect(); + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(legend.Rect.Min, legend.Rect.Max, col_bg); + DrawList.AddRect(legend.Rect.Min, legend.Rect.Max, col_bd); + bool legend_contextable = ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList) + && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus); + + // main ctx menu + if (gp.OpenContextThisFrame && legend_contextable && !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus)) + ImGui::OpenPopup("##LegendContext"); + ImGui::PopClipRect(); + if (ImGui::BeginPopup("##LegendContext")) { + ImGui::Text("Legend"); ImGui::Separator(); + if (ShowLegendContextMenu(legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend); + ImGui::EndPopup(); + } + } + else { + plot.Items.Legend.Rect = ImRect(); + } + + // render border + if (render_border) + DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_RoundCornersAll, gp.Style.PlotBorderSize); + + // render tags + for (int i = 0; i < gp.Tags.Size; ++i) { + ImPlotTag& tag = gp.Tags.Tags[i]; + ImPlotAxis& axis = plot.Axes[tag.Axis]; + if (!axis.Enabled || !axis.Range.Contains(tag.Value)) + continue; + const char* txt = gp.Tags.GetText(i); + ImVec2 text_size = ImGui::CalcTextSize(txt); + ImVec2 size = text_size + gp.Style.AnnotationPadding * 2; + ImVec2 pos; + axis.Ticker.OverrideSizeLate(size); + float pix = IM_ROUND(axis.PlotToPixels(tag.Value)); + if (axis.Vertical) { + if (axis.IsOpposite()) { + pos = ImVec2(axis.Datum1 + gp.Style.LabelPadding.x, pix - size.y * 0.5f); + DrawList.AddTriangleFilled(ImVec2(axis.Datum1,pix), pos, pos + ImVec2(0,size.y), tag.ColorBg); + } + else { + pos = ImVec2(axis.Datum1 - size.x - gp.Style.LabelPadding.x, pix - size.y * 0.5f); + DrawList.AddTriangleFilled(pos + ImVec2(size.x,0), ImVec2(axis.Datum1,pix), pos+size, tag.ColorBg); + } + } + else { + if (axis.IsOpposite()) { + pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 - size.y - gp.Style.LabelPadding.y ); + DrawList.AddTriangleFilled(pos + ImVec2(0,size.y), pos + size, ImVec2(pix,axis.Datum1), tag.ColorBg); + } + else { + pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 + gp.Style.LabelPadding.y); + DrawList.AddTriangleFilled(pos, ImVec2(pix,axis.Datum1), pos + ImVec2(size.x, 0), tag.ColorBg); + } + } + DrawList.AddRectFilled(pos,pos+size,tag.ColorBg); + DrawList.AddText(pos+gp.Style.AnnotationPadding,tag.ColorFg,txt); + } + + // FIT DATA -------------------------------------------------------------- + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + if (plot.FitThisFrame) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.FitThisFrame) { + x_axis.ApplyFit(gp.Style.FitPadding.x); + if (axis_equal && x_axis.OrthoAxis != nullptr) { + double aspect = x_axis.GetAspect(); + ImPlotAxis& y_axis = *x_axis.OrthoAxis; + if (y_axis.FitThisFrame) { + y_axis.ApplyFit(gp.Style.FitPadding.y); + y_axis.FitThisFrame = false; + aspect = ImMax(aspect, y_axis.GetAspect()); + } + x_axis.SetAspect(aspect); + y_axis.SetAspect(aspect); + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.FitThisFrame) { + y_axis.ApplyFit(gp.Style.FitPadding.y); + if (axis_equal && y_axis.OrthoAxis != nullptr) { + double aspect = y_axis.GetAspect(); + ImPlotAxis& x_axis = *y_axis.OrthoAxis; + if (x_axis.FitThisFrame) { + x_axis.ApplyFit(gp.Style.FitPadding.x); + x_axis.FitThisFrame = false; + aspect = ImMax(x_axis.GetAspect(), aspect); + } + x_axis.SetAspect(aspect); + y_axis.SetAspect(aspect); + } + } + } + plot.FitThisFrame = false; + } + + // CONTEXT MENUS ----------------------------------------------------------- + + ImGui::PushOverrideID(plot.ID); + + const bool can_ctx = gp.OpenContextThisFrame && + !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && + !plot.Items.Legend.Hovered; + + + + // main ctx menu + if (can_ctx && plot.Hovered) + ImGui::OpenPopup("##PlotContext"); + if (ImGui::BeginPopup("##PlotContext")) { + ShowPlotContextMenu(plot); + ImGui::EndPopup(); + } + + // axes ctx menus + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImGui::PushID(i); + ImPlotAxis& x_axis = plot.XAxis(i); + if (can_ctx && x_axis.Hovered && x_axis.HasMenus()) + ImGui::OpenPopup("##XContext"); + if (ImGui::BeginPopup("##XContext")) { + ImGui::Text(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : i == 0 ? "X-Axis" : "X-Axis %d", i + 1); + ImGui::Separator(); + ShowAxisContextMenu(x_axis, axis_equal ? x_axis.OrthoAxis : nullptr, true); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImGui::PushID(i); + ImPlotAxis& y_axis = plot.YAxis(i); + if (can_ctx && y_axis.Hovered && y_axis.HasMenus()) + ImGui::OpenPopup("##YContext"); + if (ImGui::BeginPopup("##YContext")) { + ImGui::Text(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1); + ImGui::Separator(); + ShowAxisContextMenu(y_axis, axis_equal ? y_axis.OrthoAxis : nullptr, false); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + ImGui::PopID(); + + // LINKED AXES ------------------------------------------------------------ + + for (int i = 0; i < ImAxis_COUNT; ++i) + plot.Axes[i].PushLinks(); + + + // CLEANUP ---------------------------------------------------------------- + + // remove items + if (gp.CurrentItems == &plot.Items) + gp.CurrentItems = nullptr; + // reset the plot items for the next frame + for (int i = 0; i < plot.Items.GetItemCount(); ++i) { + plot.Items.GetItemByIndex(i)->SeenThisFrame = false; + } + + // mark the plot as initialized, i.e. having made it through one frame completely + plot.Initialized = true; + // Pop ImGui::PushID at the end of BeginPlot + ImGui::PopID(); + // Reset context for next plot + ResetCtxForNextPlot(GImPlot); + + // setup next subplot + if (gp.CurrentSubplot != nullptr) { + ImGui::PopID(); + SubplotNextCell(); + } +} + +//----------------------------------------------------------------------------- +// BEGIN/END SUBPLOT +//----------------------------------------------------------------------------- + +static const float SUBPLOT_BORDER_SIZE = 1.0f; +static const float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f; +static const float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f; + +void SubplotSetCell(int row, int col) { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + if (row >= subplot.Rows || col >= subplot.Cols) + return; + float xoff = 0; + float yoff = 0; + for (int c = 0; c < col; ++c) + xoff += subplot.ColRatios[c]; + for (int r = 0; r < row; ++r) + yoff += subplot.RowRatios[r]; + const ImVec2 grid_size = subplot.GridRect.GetSize(); + ImVec2 cpos = subplot.GridRect.Min + ImVec2(xoff*grid_size.x,yoff*grid_size.y); + cpos.x = IM_ROUND(cpos.x); + cpos.y = IM_ROUND(cpos.y); + ImGui::GetCurrentWindow()->DC.CursorPos = cpos; + // set cell size + subplot.CellSize.x = IM_ROUND(subplot.GridRect.GetWidth() * subplot.ColRatios[col]); + subplot.CellSize.y = IM_ROUND(subplot.GridRect.GetHeight() * subplot.RowRatios[row]); + // setup links + const bool lx = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX); + const bool ly = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY); + const bool lr = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows); + const bool lc = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols); + + SetNextAxisLinks(ImAxis_X1, lx ? &subplot.ColLinkData[0].Min : lc ? &subplot.ColLinkData[col].Min : nullptr, + lx ? &subplot.ColLinkData[0].Max : lc ? &subplot.ColLinkData[col].Max : nullptr); + SetNextAxisLinks(ImAxis_Y1, ly ? &subplot.RowLinkData[0].Min : lr ? &subplot.RowLinkData[row].Min : nullptr, + ly ? &subplot.RowLinkData[0].Max : lr ? &subplot.RowLinkData[row].Max : nullptr); + // setup alignment + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)) { + gp.CurrentAlignmentH = &subplot.RowAlignmentData[row]; + gp.CurrentAlignmentV = &subplot.ColAlignmentData[col]; + } + // set idx + if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) + subplot.CurrentIdx = col * subplot.Rows + row; + else + subplot.CurrentIdx = row * subplot.Cols + col; +} + +void SubplotSetCell(int idx) { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + if (idx >= subplot.Rows * subplot.Cols) + return; + int row = 0, col = 0; + if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) { + row = idx % subplot.Rows; + col = idx / subplot.Rows; + } + else { + row = idx / subplot.Cols; + col = idx % subplot.Cols; + } + return SubplotSetCell(row, col); +} + +void SubplotNextCell() { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + SubplotSetCell(++subplot.CurrentIdx); +} + +bool BeginSubplots(const char* title, int rows, int cols, const ImVec2& size, ImPlotSubplotFlags flags, float* row_sizes, float* col_sizes) { + IM_ASSERT_USER_ERROR(rows > 0 && cols > 0, "Invalid sizing arguments!"); + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot == nullptr, "Mismatched BeginSubplots()/EndSubplots()!"); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + const ImGuiID ID = Window->GetID(title); + bool just_created = gp.Subplots.GetByKey(ID) == nullptr; + gp.CurrentSubplot = gp.Subplots.GetOrAddByKey(ID); + ImPlotSubplot& subplot = *gp.CurrentSubplot; + subplot.ID = ID; + subplot.Items.ID = ID - 1; + subplot.HasTitle = ImGui::FindRenderedTextEnd(title, nullptr) != title; + // push ID + ImGui::PushID(ID); + + if (just_created) + subplot.Flags = flags; + else if (flags != subplot.PreviousFlags) + subplot.Flags = flags; + subplot.PreviousFlags = flags; + + // check for change in rows and cols + if (subplot.Rows != rows || subplot.Cols != cols) { + subplot.RowAlignmentData.resize(rows); + subplot.RowLinkData.resize(rows); + subplot.RowRatios.resize(rows); + for (int r = 0; r < rows; ++r) { + subplot.RowAlignmentData[r].Reset(); + subplot.RowLinkData[r] = ImPlotRange(0,1); + subplot.RowRatios[r] = 1.0f / rows; + } + subplot.ColAlignmentData.resize(cols); + subplot.ColLinkData.resize(cols); + subplot.ColRatios.resize(cols); + for (int c = 0; c < cols; ++c) { + subplot.ColAlignmentData[c].Reset(); + subplot.ColLinkData[c] = ImPlotRange(0,1); + subplot.ColRatios[c] = 1.0f / cols; + } + } + // check incoming size requests + float row_sum = 0, col_sum = 0; + if (row_sizes != nullptr) { + row_sum = ImSum(row_sizes, rows); + for (int r = 0; r < rows; ++r) + subplot.RowRatios[r] = row_sizes[r] / row_sum; + } + if (col_sizes != nullptr) { + col_sum = ImSum(col_sizes, cols); + for (int c = 0; c < cols; ++c) + subplot.ColRatios[c] = col_sizes[c] / col_sum; + } + subplot.Rows = rows; + subplot.Cols = cols; + + // calc plot frame sizes + ImVec2 title_size(0.0f, 0.0f); + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle)) + title_size = ImGui::CalcTextSize(title, nullptr, true); + const float pad_top = title_size.x > 0.0f ? title_size.y + gp.Style.LabelPadding.y : 0; + const ImVec2 half_pad = gp.Style.PlotPadding/2; + const ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y); + subplot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + subplot.GridRect.Min = subplot.FrameRect.Min + half_pad + ImVec2(0,pad_top); + subplot.GridRect.Max = subplot.FrameRect.Max - half_pad; + subplot.FrameHovered = subplot.FrameRect.Contains(ImGui::GetMousePos()) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows); + + // outside legend adjustments (TODO: make function) + const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + if (share_items) + gp.CurrentItems = &subplot.Items; + if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) { + ImPlotLegend& legend = subplot.Items.Legend; + const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz); + const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East); + const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West); + const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South); + const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North); + if ((west && !horz) || (west && horz && !north && !south)) + subplot.GridRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x); + if ((east && !horz) || (east && horz && !north && !south)) + subplot.GridRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x); + if ((north && horz) || (north && !horz && !west && !east)) + subplot.GridRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y); + if ((south && horz) || (south && !horz && !west && !east)) + subplot.GridRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y); + } + + // render single background frame + ImGui::RenderFrame(subplot.FrameRect.Min, subplot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, ImGui::GetStyle().FrameRounding); + // render title + if (title_size.x > 0.0f && !ImHasFlag(subplot.Flags, ImPlotFlags_NoTitle)) { + const ImU32 col = GetStyleColorU32(ImPlotCol_TitleText); + AddTextCentered(ImGui::GetWindowDrawList(),ImVec2(subplot.GridRect.GetCenter().x, subplot.GridRect.Min.y - pad_top + half_pad.y),col,title); + } + + // render splitters + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)) { + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + const ImU32 hov_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorHovered]); + const ImU32 act_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorActive]); + float xpos = subplot.GridRect.Min.x; + float ypos = subplot.GridRect.Min.y; + int separator = 1; + // bool pass = false; + for (int r = 0; r < subplot.Rows-1; ++r) { + ypos += subplot.RowRatios[r] * subplot.GridRect.GetHeight(); + const ImGuiID sep_id = subplot.ID + separator; + ImGui::KeepAliveID(sep_id); + const ImRect sep_bb = ImRect(subplot.GridRect.Min.x, ypos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.x, ypos+SUBPLOT_SPLITTER_HALF_THICKNESS); + bool sep_hov = false, sep_hld = false; + const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) { + if (sep_clk && ImGui::IsMouseDoubleClicked(0)) { + float p = (subplot.RowRatios[r] + subplot.RowRatios[r+1])/2; + subplot.RowRatios[r] = subplot.RowRatios[r+1] = p; + } + if (sep_clk) { + subplot.TempSizes[0] = subplot.RowRatios[r]; + subplot.TempSizes[1] = subplot.RowRatios[r+1]; + } + if (sep_hld) { + float dp = ImGui::GetMouseDragDelta(0).y / subplot.GridRect.GetHeight(); + if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) { + subplot.RowRatios[r] = subplot.TempSizes[0] + dp; + subplot.RowRatios[r+1] = subplot.TempSizes[1] - dp; + } + } + DrawList.AddLine(ImVec2(IM_ROUND(subplot.GridRect.Min.x),IM_ROUND(ypos)), + ImVec2(IM_ROUND(subplot.GridRect.Max.x),IM_ROUND(ypos)), + sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE); + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } + separator++; + } + for (int c = 0; c < subplot.Cols-1; ++c) { + xpos += subplot.ColRatios[c] * subplot.GridRect.GetWidth(); + const ImGuiID sep_id = subplot.ID + separator; + ImGui::KeepAliveID(sep_id); + const ImRect sep_bb = ImRect(xpos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Min.y, xpos+SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.y); + bool sep_hov = false, sep_hld = false; + const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) { + if (sep_clk && ImGui::IsMouseDoubleClicked(0)) { + float p = (subplot.ColRatios[c] + subplot.ColRatios[c+1])/2; + subplot.ColRatios[c] = subplot.ColRatios[c+1] = p; + } + if (sep_clk) { + subplot.TempSizes[0] = subplot.ColRatios[c]; + subplot.TempSizes[1] = subplot.ColRatios[c+1]; + } + if (sep_hld) { + float dp = ImGui::GetMouseDragDelta(0).x / subplot.GridRect.GetWidth(); + if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) { + subplot.ColRatios[c] = subplot.TempSizes[0] + dp; + subplot.ColRatios[c+1] = subplot.TempSizes[1] - dp; + } + } + DrawList.AddLine(ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Min.y)), + ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Max.y)), + sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE); + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + separator++; + } + } + + // set outgoing sizes + if (row_sizes != nullptr) { + for (int r = 0; r < rows; ++r) + row_sizes[r] = subplot.RowRatios[r] * row_sum; + } + if (col_sizes != nullptr) { + for (int c = 0; c < cols; ++c) + col_sizes[c] = subplot.ColRatios[c] * col_sum; + } + + // push styling + PushStyleColor(ImPlotCol_FrameBg, IM_COL32_BLACK_TRANS); + PushStyleVar(ImPlotStyleVar_PlotPadding, half_pad); + PushStyleVar(ImPlotStyleVar_PlotMinSize, ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize,0); + + // set initial cursor pos + Window->DC.CursorPos = subplot.GridRect.Min; + // begin alignments + for (int r = 0; r < subplot.Rows; ++r) + subplot.RowAlignmentData[r].Begin(); + for (int c = 0; c < subplot.Cols; ++c) + subplot.ColAlignmentData[c].Begin(); + // clear legend data + subplot.Items.Legend.Reset(); + // Setup first subplot + SubplotSetCell(0,0); + return true; +} + +void EndSubplots() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "Mismatched BeginSubplots()/EndSubplots()!"); + ImPlotSubplot& subplot = *gp.CurrentSubplot; + // set alignments + for (int r = 0; r < subplot.Rows; ++r) + subplot.RowAlignmentData[r].End(); + for (int c = 0; c < subplot.Cols; ++c) + subplot.ColAlignmentData[c].End(); + // pop styling + PopStyleColor(); + PopStyleVar(); + PopStyleVar(); + ImGui::PopStyleVar(); + // legend + subplot.Items.Legend.Hovered = false; + for (int i = 0; i < subplot.Items.GetItemCount(); ++i) + subplot.Items.GetItemByIndex(i)->LegendHovered = false; + // render legend + const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) { + const bool legend_horz = ImHasFlag(subplot.Items.Legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz); + const ImVec2 legend_pos = GetLocationPos(subplot.FrameRect, legend_size, subplot.Items.Legend.Location, gp.Style.PlotPadding); + subplot.Items.Legend.Rect = ImRect(legend_pos, legend_pos + legend_size); + subplot.Items.Legend.Hovered = subplot.FrameHovered && subplot.Items.Legend.Rect.Contains(ImGui::GetIO().MousePos); + ImGui::PushClipRect(subplot.FrameRect.Min, subplot.FrameRect.Max, true); + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(subplot.Items.Legend.Rect.Min, subplot.Items.Legend.Rect.Max, col_bg); + DrawList.AddRect(subplot.Items.Legend.Rect.Min, subplot.Items.Legend.Rect.Max, col_bd); + bool legend_contextable = ShowLegendEntries(subplot.Items, subplot.Items.Legend.Rect, subplot.Items.Legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList) + && !ImHasFlag(subplot.Items.Legend.Flags, ImPlotLegendFlags_NoMenus); + if (legend_contextable && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoMenus) && ImGui::GetIO().MouseReleased[gp.InputMap.Menu]) + ImGui::OpenPopup("##LegendContext"); + ImGui::PopClipRect(); + if (ImGui::BeginPopup("##LegendContext")) { + ImGui::Text("Legend"); ImGui::Separator(); + if (ShowLegendContextMenu(subplot.Items.Legend, !ImHasFlag(subplot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(subplot.Flags, ImPlotFlags_NoLegend); + ImGui::EndPopup(); + } + } + else { + subplot.Items.Legend.Rect = ImRect(); + } + // remove items + if (gp.CurrentItems == &subplot.Items) + gp.CurrentItems = nullptr; + // reset the plot items for the next frame (TODO: put this elswhere) + for (int i = 0; i < subplot.Items.GetItemCount(); ++i) { + subplot.Items.GetItemByIndex(i)->SeenThisFrame = false; + } + // pop id + ImGui::PopID(); + // set DC back correctly + GImGui->CurrentWindow->DC.CursorPos = subplot.FrameRect.Min; + ImGui::Dummy(subplot.FrameRect.GetSize()); + ResetCtxForNextSubplot(GImPlot); + +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +void SetAxis(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxis() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(axis >= ImAxis_X1 && axis < ImAxis_COUNT, "Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[axis].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + SetupLock(); + if (axis < ImAxis_Y1) + gp.CurrentPlot->CurrentX = axis; + else + gp.CurrentPlot->CurrentY = axis; +} + +void SetAxes(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxes() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1, "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT, "Y-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[x_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[y_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + SetupLock(); + gp.CurrentPlot->CurrentX = x_idx; + gp.CurrentPlot->CurrentY = y_idx; +} + +ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlot() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + return ImPlotPoint( x_axis.PixelsToPlot(x), y_axis.PixelsToPlot(y) ); +} + +ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_idx, ImAxis y_idx) { + return PixelsToPlot(pix.x, pix.y, x_idx, y_idx); +} + +ImVec2 PlotToPixels(double x, double y, ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + return ImVec2( x_axis.PlotToPixels(x), y_axis.PlotToPixels(y) ); +} + +ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_idx, ImAxis y_idx) { + return PlotToPixels(plt.x, plt.y, x_idx, y_idx); +} + +ImVec2 GetPlotPos() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->PlotRect.Min; +} + +ImVec2 GetPlotSize() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->PlotRect.GetSize(); +} + +ImPlotPoint GetPlotMousePos(ImAxis x_idx, ImAxis y_idx) { + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "GetPlotMousePos() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return PixelsToPlot(ImGui::GetMousePos(), x_idx, y_idx); +} + +ImPlotRect GetPlotLimits(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotLimits() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + ImPlotRect limits; + limits.X = x_axis.Range; + limits.Y = y_axis.Range; + return limits; +} + +bool IsPlotHovered() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotHovered() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Hovered; +} + +bool IsAxisHovered(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotXAxisHovered() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Axes[axis].Hovered; +} + +bool IsSubplotsHovered() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "IsSubplotsHovered() needs to be called between BeginSubplots() and EndSubplots()!"); + return gp.CurrentSubplot->FrameHovered; +} + +bool IsPlotSelected() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotSelected() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Selected; +} + +ImPlotRect GetPlotSelection(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSelection() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + if (!plot.Selected) + return ImPlotRect(0,0,0,0); + ImPlotPoint p1 = PixelsToPlot(plot.SelectRect.Min + plot.PlotRect.Min, x_idx, y_idx); + ImPlotPoint p2 = PixelsToPlot(plot.SelectRect.Max + plot.PlotRect.Min, x_idx, y_idx); + ImPlotRect result; + result.X.Min = ImMin(p1.x, p2.x); + result.X.Max = ImMax(p1.x, p2.x); + result.Y.Min = ImMin(p1.y, p2.y); + result.Y.Max = ImMax(p1.y, p2.y); + return result; +} + +void CancelPlotSelection() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "CancelPlotSelection() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + if (plot.Selected) + plot.Selected = plot.Selecting = false; +} + +void HideNextItem(bool hidden, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.HasHidden = true; + gp.NextItemData.Hidden = hidden; + gp.NextItemData.HiddenCond = cond; +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot Tools +//----------------------------------------------------------------------------- + +void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + char x_buff[IMPLOT_LABEL_MAX_SIZE]; + char y_buff[IMPLOT_LABEL_MAX_SIZE]; + ImPlotAxis& x_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX]; + ImPlotAxis& y_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY]; + LabelAxisValue(x_axis, x, x_buff, sizeof(x_buff), round); + LabelAxisValue(y_axis, y, y_buff, sizeof(y_buff), round); + Annotation(x,y,col,offset,clamp,"%s, %s",x_buff,y_buff); +} + +void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImVec2 pos = PlotToPixels(x,y,IMPLOT_AUTO,IMPLOT_AUTO); + ImU32 bg = ImGui::GetColorU32(col); + ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_InlayText) : CalcTextColor(col); + gp.Annotations.AppendV(pos, offset, bg, fg, clamp, fmt, args); +} + +void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + AnnotationV(x,y,col,offset,clamp,fmt,args); + va_end(args); +} + +void TagV(ImAxis axis, double v, const ImVec4& col, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + SetupLock(); + ImU32 bg = ImGui::GetColorU32(col); + ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_AxisText) : CalcTextColor(col); + gp.Tags.AppendV(axis,v,bg,fg,fmt,args); +} + +void Tag(ImAxis axis, double v, const ImVec4& col, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + TagV(axis,v,col,fmt,args); + va_end(args); +} + +void Tag(ImAxis axis, double v, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + SetupLock(); + char buff[IMPLOT_LABEL_MAX_SIZE]; + ImPlotAxis& ax = gp.CurrentPlot->Axes[axis]; + LabelAxisValue(ax, v, buff, sizeof(buff), round); + Tag(axis,v,color,"%s",buff); +} + +IMPLOT_API void TagX(double x, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + Tag(gp.CurrentPlot->CurrentX, x, color, round); +} + +IMPLOT_API void TagX(double x, const ImVec4& color, const char* fmt, ...) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + va_list args; + va_start(args, fmt); + TagV(gp.CurrentPlot->CurrentX,x,color,fmt,args); + va_end(args); +} + +IMPLOT_API void TagXV(double x, const ImVec4& color, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + TagV(gp.CurrentPlot->CurrentX, x, color, fmt, args); +} + +IMPLOT_API void TagY(double y, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + Tag(gp.CurrentPlot->CurrentY, y, color, round); +} + +IMPLOT_API void TagY(double y, const ImVec4& color, const char* fmt, ...) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + va_list args; + va_start(args, fmt); + TagV(gp.CurrentPlot->CurrentY,y,color,fmt,args); + va_end(args); +} + +IMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + TagV(gp.CurrentPlot->CurrentY, y, color, fmt, args); +} + +static const float DRAG_GRAB_HALF_SIZE = 4.0f; + +bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags) { + ImGui::PushID("#IMPLOT_DRAG_POINT"); + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragPoint() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPoint(ImPlotPoint(*x,*y)); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, radius); + const ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + const ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + ImVec2 pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(pos.x-grab_half_size,pos.y-grab_half_size,pos.x+grab_half_size,pos.y+grab_half_size); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) + ImGui::ButtonBehavior(rect,id,&hovered,&held); + + bool dragging = false; + if (held && ImGui::IsMouseDragging(0)) { + *x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + *y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + dragging = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (dragging && no_delay) + pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO); + DrawList.AddCircleFilled(pos, radius, col32); + PopPlotClipRect(); + + ImGui::PopID(); + return dragging; +} + +bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) { + // ImGui::PushID("#IMPLOT_DRAG_LINE_X"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineX() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPointX(*value); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); + float yt = gp.CurrentPlot->PlotRect.Min.y; + float yb = gp.CurrentPlot->PlotRect.Max.y; + float x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(x-grab_half_size,yt,x+grab_half_size,yb); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) + ImGui::ButtonBehavior(rect,id,&hovered,&held); + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + + float len = gp.Style.MajorTickLen.x; + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + bool dragging = false; + if (held && ImGui::IsMouseDragging(0)) { + *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + dragging = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (dragging && no_delay) + x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x); + DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yb), col32, thickness); + DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yt+len), col32, 3*thickness); + DrawList.AddLine(ImVec2(x,yb), ImVec2(x,yb-len), col32, 3*thickness); + PopPlotClipRect(); + + // ImGui::PopID(); + return dragging; +} + +bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) { + ImGui::PushID("#IMPLOT_DRAG_LINE_Y"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineY() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPointY(*value); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); + float xl = gp.CurrentPlot->PlotRect.Min.x; + float xr = gp.CurrentPlot->PlotRect.Max.x; + float y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y); + + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(xl,y-grab_half_size,xr,y+grab_half_size); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) + ImGui::ButtonBehavior(rect,id,&hovered,&held); + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + + float len = gp.Style.MajorTickLen.y; + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + bool dragging = false; + if (held && ImGui::IsMouseDragging(0)) { + *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + dragging = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (dragging && no_delay) + y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y); + DrawList.AddLine(ImVec2(xl,y), ImVec2(xr,y), col32, thickness); + DrawList.AddLine(ImVec2(xl,y), ImVec2(xl+len,y), col32, 3*thickness); + DrawList.AddLine(ImVec2(xr,y), ImVec2(xr-len,y), col32, 3*thickness); + PopPlotClipRect(); + + ImGui::PopID(); + return dragging; +} + +bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags) { + ImGui::PushID("#IMPLOT_DRAG_RECT"); + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragRect() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPoint(ImPlotPoint(*x_min,*y_min)); + FitPoint(ImPlotPoint(*x_max,*y_max)); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + bool h[] = {true,false,true,false}; + double* x[] = {x_min,x_max,x_max,x_min}; + double* y[] = {y_min,y_min,y_max,y_max}; + ImVec2 p[4]; + for (int i = 0; i < 4; ++i) + p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO); + ImVec2 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO); + ImRect rect(ImMin(p[0],p[2]),ImMax(p[0],p[2])); + ImRect rect_grab = rect; rect_grab.Expand(DRAG_GRAB_HALF_SIZE); + + ImGuiMouseCursor cur[4]; + if (show_curs) { + cur[0] = (rect.Min.x == p[0].x && rect.Min.y == p[0].y) || (rect.Max.x == p[0].x && rect.Max.y == p[0].y) ? ImGuiMouseCursor_ResizeNWSE : ImGuiMouseCursor_ResizeNESW; + cur[1] = cur[0] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + cur[2] = cur[1] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + cur[3] = cur[2] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + } + + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + color.w *= 0.25f; + ImU32 col32_a = ImGui::ColorConvertFloat4ToU32(color); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + + bool dragging = false; + bool hovered = false, held = false; + ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE); + + ImGui::KeepAliveID(id); + if (input) + ImGui::ButtonBehavior(b_rect,id,&hovered,&held); + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + if (held && ImGui::IsMouseDragging(0)) { + for (int i = 0; i < 4; ++i) { + ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO); + *y[i] = pp.y; + *x[i] = pp.x; + } + dragging = true; + } + + for (int i = 0; i < 4; ++i) { + // points + b_rect = ImRect(p[i].x-DRAG_GRAB_HALF_SIZE,p[i].y-DRAG_GRAB_HALF_SIZE,p[i].x+DRAG_GRAB_HALF_SIZE,p[i].y+DRAG_GRAB_HALF_SIZE); + ImGuiID p_id = id + i + 1; + ImGui::KeepAliveID(p_id); + if (input) + ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held); + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(cur[i]); + + if (held && ImGui::IsMouseDragging(0)) { + *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + dragging = true; + } + + // edges + ImVec2 e_min = ImMin(p[i],p[(i+1)%4]); + ImVec2 e_max = ImMax(p[i],p[(i+1)%4]); + b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE) + : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE); + ImGuiID e_id = id + i + 5; + ImGui::KeepAliveID(e_id); + if (input) + ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held); + if ((hovered || held) && show_curs) + h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + if (held && ImGui::IsMouseDragging(0)) { + if (h[i]) + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + else + *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + dragging = true; + } + if (hovered && ImGui::IsMouseDoubleClicked(0)) + { + ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO); + if (h[i]) + *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max; + else + *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max; + dragging = true; + } + } + + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (dragging && no_delay) { + for (int i = 0; i < 4; ++i) + p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO); + pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO); + rect = ImRect(ImMin(p[0],p[2]),ImMax(p[0],p[2])); + } + DrawList.AddRectFilled(rect.Min, rect.Max, col32_a); + DrawList.AddRect(rect.Min, rect.Max, col32); + if (input && (dragging || rect_grab.Contains(ImGui::GetMousePos()))) { + DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32); + for (int i = 0; i < 4; ++i) + DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32); + } + PopPlotClipRect(); + ImGui::PopID(); + return dragging; +} + +bool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags) { + return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags); +} + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils and Tools +//----------------------------------------------------------------------------- + +bool IsLegendEntryHovered(const char* label_id) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "IsPlotItemHighlight() needs to be called within an itemized context!"); + SetupLock(); + ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + ImPlotItem* item = gp.CurrentItems->GetItem(id); + return item && item->LegendHovered; +} + +bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginLegendPopup() needs to be called within an itemized context!"); + SetupLock(); + ImGuiWindow* window = GImGui->CurrentWindow; + if (window->SkipItems) + return false; + ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + if (ImGui::IsMouseReleased(mouse_button)) { + ImPlotItem* item = gp.CurrentItems->GetItem(id); + if (item && item->LegendHovered) + ImGui::OpenPopupEx(id); + } + return ImGui::BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); +} + +void EndLegendPopup() { + SetupLock(); + ImGui::EndPopup(); +} + +void ShowAltLegend(const char* title_id, bool vertical, const ImVec2 size, bool interactable) { + ImPlotContext& gp = *GImPlot; + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return; + ImDrawList &DrawList = *Window->DrawList; + ImPlotPlot* plot = GetPlot(title_id); + ImVec2 legend_size; + ImVec2 default_size = gp.Style.LegendPadding * 2; + if (plot != nullptr) { + legend_size = CalcLegendSize(plot->Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical); + default_size = legend_size + gp.Style.LegendPadding * 2; + } + ImVec2 frame_size = ImGui::CalcItemSize(size, default_size.x, default_size.y); + ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(bb_frame); + if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame)) + return; + ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); + DrawList.PushClipRect(bb_frame.Min, bb_frame.Max, true); + if (plot != nullptr) { + const ImVec2 legend_pos = GetLocationPos(bb_frame, legend_size, 0, gp.Style.LegendPadding); + const ImRect legend_bb(legend_pos, legend_pos + legend_size); + interactable = interactable && bb_frame.Contains(ImGui::GetIO().MousePos); + // render legend box + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg); + DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd); + // render entries + ShowLegendEntries(plot->Items, legend_bb, interactable, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical, DrawList); + } + DrawList.PopClipRect(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Drag and Drop Utils +//----------------------------------------------------------------------------- + +bool BeginDragDropTargetPlot() { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImRect rect = gp.CurrentPlot->PlotRect; + return ImGui::BeginDragDropTargetCustom(rect, gp.CurrentPlot->ID); +} + +bool BeginDragDropTargetAxis(ImAxis axis) { + SetupLock(); + ImPlotPlot& plot = *GImPlot->CurrentPlot; + ImPlotAxis& ax = plot.Axes[axis]; + ImRect rect = ax.HoverRect; + rect.Expand(-3.5f); + return ImGui::BeginDragDropTargetCustom(rect, ax.ID); +} + +bool BeginDragDropTargetLegend() { + SetupLock(); + ImPlotItemGroup& items = *GImPlot->CurrentItems; + ImRect rect = items.Legend.Rect; + return ImGui::BeginDragDropTargetCustom(rect, items.ID); +} + +void EndDragDropTarget() { + SetupLock(); + ImGui::EndDragDropTarget(); +} + +bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImPlotPlot* plot = gp.CurrentPlot; + if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == plot->ID) + return ImGui::ItemAdd(plot->PlotRect, plot->ID) && ImGui::BeginDragDropSource(flags); + return false; +} + +bool BeginDragDropSourceAxis(ImAxis idx, ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImPlotAxis& axis = gp.CurrentPlot->Axes[idx]; + if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == axis.ID) + return ImGui::ItemAdd(axis.HoverRect, axis.ID) && ImGui::BeginDragDropSource(flags); + return false; +} + +bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginDragDropSourceItem() needs to be called within an itemized context!"); + ImGuiID item_id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + ImPlotItem* item = gp.CurrentItems->GetItem(item_id); + if (item != nullptr) { + return ImGui::ItemAdd(item->LegendHoverRect, item->ID) && ImGui::BeginDragDropSource(flags); + } + return false; +} + +void EndDragDropSource() { + SetupLock(); + ImGui::EndDragDropSource(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Aligned Plots +//----------------------------------------------------------------------------- + +bool BeginAlignedPlots(const char* group_id, bool vertical) { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH == nullptr && gp.CurrentAlignmentV == nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!"); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + const ImGuiID ID = Window->GetID(group_id); + ImPlotAlignmentData* alignment = gp.AlignmentData.GetOrAddByKey(ID); + if (vertical) + gp.CurrentAlignmentV = alignment; + else + gp.CurrentAlignmentH = alignment; + if (alignment->Vertical != vertical) + alignment->Reset(); + alignment->Vertical = vertical; + alignment->Begin(); + return true; +} + +void EndAlignedPlots() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH != nullptr || gp.CurrentAlignmentV != nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!"); + ImPlotAlignmentData* alignment = gp.CurrentAlignmentH != nullptr ? gp.CurrentAlignmentH : (gp.CurrentAlignmentV != nullptr ? gp.CurrentAlignmentV : nullptr); + if (alignment) + alignment->End(); + ResetCtxForNextAlignedPlots(GImPlot); +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot and Item Styling +//----------------------------------------------------------------------------- + +ImPlotStyle& GetStyle() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + return gp.Style; +} + +void PushStyleColor(ImPlotCol idx, ImU32 col) { + ImPlotContext& gp = *GImPlot; + ImGuiColorMod backup; + backup.Col = (ImGuiCol)idx; + backup.BackupValue = gp.Style.Colors[idx]; + gp.ColorModifiers.push_back(backup); + gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col); +} + +void PushStyleColor(ImPlotCol idx, const ImVec4& col) { + ImPlotContext& gp = *GImPlot; + ImGuiColorMod backup; + backup.Col = (ImGuiCol)idx; + backup.BackupValue = gp.Style.Colors[idx]; + gp.ColorModifiers.push_back(backup); + gp.Style.Colors[idx] = col; +} + +void PopStyleColor(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) + { + ImGuiColorMod& backup = gp.ColorModifiers.back(); + gp.Style.Colors[backup.Col] = backup.BackupValue; + gp.ColorModifiers.pop_back(); + count--; + } +} + +void PushStyleVar(ImPlotStyleVar idx, float val) { + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + float* pvar = (float*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!"); +} + +void PushStyleVar(ImPlotStyleVar idx, int val) { + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) { + int* pvar = (int*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + float* pvar = (float*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = (float)val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() int variant but variable is not a int!"); +} + +void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val) +{ + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) + { + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!"); +} + +void PopStyleVar(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) { + ImGuiStyleMod& backup = gp.StyleModifiers.back(); + const ImPlotStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx); + void* data = info->GetVarPtr(&gp.Style); + if (info->Type == ImGuiDataType_Float && info->Count == 1) { + ((float*)data)[0] = backup.BackupFloat[0]; + } + else if (info->Type == ImGuiDataType_Float && info->Count == 2) { + ((float*)data)[0] = backup.BackupFloat[0]; + ((float*)data)[1] = backup.BackupFloat[1]; + } + else if (info->Type == ImGuiDataType_S32 && info->Count == 1) { + ((int*)data)[0] = backup.BackupInt[0]; + } + gp.StyleModifiers.pop_back(); + count--; + } +} + +//------------------------------------------------------------------------------ +// [Section] Colormaps +//------------------------------------------------------------------------------ + +ImPlotColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!"); + ImVector buffer; + buffer.resize(size); + for (int i = 0; i < size; ++i) + buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]); + return gp.ColormapData.Append(name, buffer.Data, size, qual); +} + +ImPlotColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!"); + return gp.ColormapData.Append(name, colormap, size, qual); +} + +int GetColormapCount() { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.Count; +} + +const char* GetColormapName(ImPlotColormap colormap) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetName(colormap); +} + +ImPlotColormap GetColormapIndex(const char* name) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetIndex(name); +} + +void PushColormap(ImPlotColormap colormap) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!"); + gp.ColormapModifiers.push_back(gp.Style.Colormap); + gp.Style.Colormap = colormap; +} + +void PushColormap(const char* name) { + ImPlotContext& gp = *GImPlot; + ImPlotColormap idx = gp.ColormapData.GetIndex(name); + IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!"); + PushColormap(idx); +} + +void PopColormap(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) { + const ImPlotColormap& backup = gp.ColormapModifiers.back(); + gp.Style.Colormap = backup; + gp.ColormapModifiers.pop_back(); + count--; + } +} + +ImU32 NextColormapColorU32() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!"); + int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap); + ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx); + gp.CurrentItems->ColormapIdx++; + return col; +} + +ImVec4 NextColormapColor() { + return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32()); +} + +int GetColormapSize(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.GetKeyCount(cmap); +} + +ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + idx = idx % gp.ColormapData.GetKeyCount(cmap); + return gp.ColormapData.GetKeyColor(cmap, idx); +} + +ImVec4 GetColormapColor(int idx, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx,cmap)); +} + +ImU32 SampleColormapU32(float t, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.LerpTable(cmap, t); +} + +ImVec4 SampleColormap(float t, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t,cmap)); +} + +void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) { + const int n = continuous ? size - 1 : size; + ImU32 col1, col2; + if (vert) { + const float step = bounds.GetHeight() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2); + rect.TranslateY(step); + } + } + else { + const float step = bounds.GetWidth() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1); + rect.TranslateX(step); + } + } +} + +void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, const char* format, ImPlotColormapScaleFlags flags, ImPlotColormap cmap) { + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return; + + const ImGuiID ID = Window->GetID(label); + ImVec2 label_size(0,0); + if (!ImHasFlag(flags, ImPlotColormapScaleFlags_NoLabel)) { + label_size = ImGui::CalcTextSize(label,nullptr,true); + } + + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + + ImVec2 frame_size = ImGui::CalcItemSize(size, 0, gp.Style.PlotDefaultSize.y); + if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f) + frame_size.y = gp.Style.PlotMinSize.y; + + ImPlotRange range(ImMin(scale_min,scale_max), ImMax(scale_min,scale_max)); + gp.CTicker.Reset(); + Locator_Default(gp.CTicker, range, frame_size.y, true, Formatter_Default, (void*)format); + + const bool rend_label = label_size.x > 0; + const float txt_off = gp.Style.LabelPadding.x; + const float pad = txt_off + gp.CTicker.MaxSize.x + (rend_label ? txt_off + label_size.y : 0); + float bar_w = 20; + if (frame_size.x == 0) + frame_size.x = bar_w + pad + 2 * gp.Style.PlotPadding.x; + else { + bar_w = frame_size.x - (pad + 2 * gp.Style.PlotPadding.x); + if (bar_w < gp.Style.MajorTickLen.y) + bar_w = gp.Style.MajorTickLen.y; + } + + ImDrawList &DrawList = *Window->DrawList; + ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(bb_frame); + if (!ImGui::ItemAdd(bb_frame, ID, &bb_frame)) + return; + + ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); + + const bool opposite = ImHasFlag(flags, ImPlotColormapScaleFlags_Opposite); + const bool inverted = ImHasFlag(flags, ImPlotColormapScaleFlags_Invert); + const bool reversed = scale_min > scale_max; + + float bb_grad_shift = opposite ? pad : 0; + ImRect bb_grad(bb_frame.Min + gp.Style.PlotPadding + ImVec2(bb_grad_shift, 0), + bb_frame.Min + ImVec2(bar_w + gp.Style.PlotPadding.x + bb_grad_shift, + frame_size.y - gp.Style.PlotPadding.y)); + + ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true); + const ImU32 col_text = ImGui::GetColorU32(ImGuiCol_Text); + + const bool invert_scale = inverted ? (reversed ? false : true) : (reversed ? true : false); + const float y_min = invert_scale ? bb_grad.Max.y : bb_grad.Min.y; + const float y_max = invert_scale ? bb_grad.Min.y : bb_grad.Max.y; + + RenderColorBar(gp.ColormapData.GetKeys(cmap), gp.ColormapData.GetKeyCount(cmap), DrawList, bb_grad, true, !inverted, !gp.ColormapData.IsQual(cmap)); + for (int i = 0; i < gp.CTicker.TickCount(); ++i) { + const double y_pos_plt = gp.CTicker.Ticks[i].PlotPos; + const float y_pos = ImRemap((float)y_pos_plt, (float)range.Max, (float)range.Min, y_min, y_max); + const float tick_width = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y; + const float tick_thick = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y; + const float tick_t = (float)((y_pos_plt - scale_min) / (scale_max - scale_min)); + const ImU32 tick_col = CalcTextColor(gp.ColormapData.LerpTable(cmap,tick_t)); + if (y_pos < bb_grad.Max.y - 2 && y_pos > bb_grad.Min.y + 2) { + DrawList.AddLine(opposite ? ImVec2(bb_grad.Min.x+1, y_pos) : ImVec2(bb_grad.Max.x-1, y_pos), + opposite ? ImVec2(bb_grad.Min.x + tick_width, y_pos) : ImVec2(bb_grad.Max.x - tick_width, y_pos), + tick_col, + tick_thick); + } + const float txt_x = opposite ? bb_grad.Min.x - txt_off - gp.CTicker.Ticks[i].LabelSize.x : bb_grad.Max.x + txt_off; + const float txt_y = y_pos - gp.CTicker.Ticks[i].LabelSize.y * 0.5f; + DrawList.AddText(ImVec2(txt_x, txt_y), col_text, gp.CTicker.GetText(i)); + } + + if (rend_label) { + const float pos_x = opposite ? bb_frame.Min.x + gp.Style.PlotPadding.x : bb_grad.Max.x + 2 * txt_off + gp.CTicker.MaxSize.x; + const float pos_y = bb_grad.GetCenter().y + label_size.x * 0.5f; + const char* label_end = ImGui::FindRenderedTextEnd(label); + AddTextVertical(&DrawList,ImVec2(pos_x,pos_y),col_text,label,label_end); + } + DrawList.AddRect(bb_grad.Min, bb_grad.Max, GetStyleColorU32(ImPlotCol_PlotBorder)); + ImGui::PopClipRect(); +} + +bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format, ImPlotColormap cmap) { + *t = ImClamp(*t,0.0f,1.0f); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = gp.ColormapData.GetKeys(cmap); + const int count = gp.ColormapData.GetKeyCount(cmap); + const bool qual = gp.ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const float w = ImGui::CalcItemWidth(); + const float h = ImGui::GetFrameHeight(); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+w,pos.y+h); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 grab = CalcTextColor(gp.ColormapData.LerpTable(cmap,*t)); + // const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab,grab); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, grab); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize,2); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool changed = ImGui::SliderFloat(label,t,0,1,format); + ImGui::PopStyleColor(5); + ImGui::PopStyleVar(2); + if (out != nullptr) + *out = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.LerpTable(cmap,*t)); + return changed; +} + +bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlotColormap cmap) { + ImGuiContext &G = *GImGui; + const ImGuiStyle& style = G.Style; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = gp.ColormapData.GetKeys(cmap); + const int count = gp.ColormapData.GetKeyCount(cmap); + const bool qual = gp.ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true); + ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+size.x,pos.y+size.y); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,G.Style.ButtonTextAlign.x)); + ImGui::PushStyleColor(ImGuiCol_Button,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(1,1,1,0.2f)); + ImGui::PushStyleColor(ImGuiCol_Text,text); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool pressed = ImGui::Button(label,size); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); + return pressed; +} + +//----------------------------------------------------------------------------- +// [Section] Miscellaneous +//----------------------------------------------------------------------------- + +ImPlotInputMap& GetInputMap() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + return gp.InputMap; +} + +void MapInputDefault(ImPlotInputMap* dst) { + ImPlotInputMap& map = dst ? *dst : GetInputMap(); + map.Pan = ImGuiMouseButton_Left; + map.PanMod = ImGuiMod_None; + map.Fit = ImGuiMouseButton_Left; + map.Menu = ImGuiMouseButton_Right; + map.Select = ImGuiMouseButton_Right; + map.SelectMod = ImGuiMod_None; + map.SelectCancel = ImGuiMouseButton_Left; + map.SelectHorzMod = ImGuiMod_Alt; + map.SelectVertMod = ImGuiMod_Shift; + map.OverrideMod = ImGuiMod_Ctrl; + map.ZoomMod = ImGuiMod_None; + map.ZoomRate = 0.1f; +} + +void MapInputReverse(ImPlotInputMap* dst) { + ImPlotInputMap& map = dst ? *dst : GetInputMap(); + map.Pan = ImGuiMouseButton_Right; + map.PanMod = ImGuiMod_None; + map.Fit = ImGuiMouseButton_Left; + map.Menu = ImGuiMouseButton_Right; + map.Select = ImGuiMouseButton_Left; + map.SelectMod = ImGuiMod_None; + map.SelectCancel = ImGuiMouseButton_Right; + map.SelectHorzMod = ImGuiMod_Alt; + map.SelectVertMod = ImGuiMod_Shift; + map.OverrideMod = ImGuiMod_Ctrl; + map.ZoomMod = ImGuiMod_None; + map.ZoomRate = 0.1f; +} + +//----------------------------------------------------------------------------- +// [Section] Miscellaneous +//----------------------------------------------------------------------------- + +void ItemIcon(const ImVec4& col) { + ItemIcon(ImGui::ColorConvertFloat4ToU32(col)); +} + +void ItemIcon(ImU32 col) { + const float txt_size = ImGui::GetTextLineHeight(); + ImVec2 size(txt_size-4,txt_size); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 pos = window->DC.CursorPos; + ImGui::GetWindowDrawList()->AddRectFilled(pos + ImVec2(0,2), pos + size - ImVec2(0,2), col); + ImGui::Dummy(size); +} + +void ColormapIcon(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + const float txt_size = ImGui::GetTextLineHeight(); + ImVec2 size(txt_size-4,txt_size); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 pos = window->DC.CursorPos; + ImRect rect(pos+ImVec2(0,2),pos+size-ImVec2(0,2)); + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + RenderColorBar(gp.ColormapData.GetKeys(cmap),gp.ColormapData.GetKeyCount(cmap),DrawList,rect,false,false,!gp.ColormapData.IsQual(cmap)); + ImGui::Dummy(size); +} + +ImDrawList* GetPlotDrawList() { + return ImGui::GetWindowDrawList(); +} + +void PushPlotClipRect(float expand) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PushPlotClipRect() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImRect rect = gp.CurrentPlot->PlotRect; + rect.Expand(expand); + ImGui::PushClipRect(rect.Min, rect.Max, true); +} + +void PopPlotClipRect() { + SetupLock(); + ImGui::PopClipRect(); +} + +static void HelpMarker(const char* desc) { + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +bool ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: StyleColorsAuto(); break; + case 1: StyleColorsClassic(); break; + case 2: StyleColorsDark(); break; + case 3: StyleColorsLight(); break; + } + return true; + } + return false; +} + +bool ShowColormapSelector(const char* label) { + ImPlotContext& gp = *GImPlot; + bool set = false; + if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) { + for (int i = 0; i < gp.ColormapData.Count; ++i) { + const char* name = gp.ColormapData.GetName(i); + if (ImGui::Selectable(name, gp.Style.Colormap == i)) { + gp.Style.Colormap = i; + ImPlot::BustItemCache(); + set = true; + } + } + ImGui::EndCombo(); + } + return set; +} + +bool ShowInputMapSelector(const char* label) { + static int map_idx = -1; + if (ImGui::Combo(label, &map_idx, "Default\0Reversed\0")) + { + switch (map_idx) + { + case 0: MapInputDefault(); break; + case 1: MapInputReverse(); break; + } + return true; + } + return false; +} + + +void ShowStyleEditor(ImPlotStyle* ref) { + ImPlotContext& gp = *GImPlot; + ImPlotStyle& style = GetStyle(); + static ImPlotStyle ref_saved_style; + // Default to using internal storage as reference + static bool init = true; + if (init && ref == nullptr) + ref_saved_style = style; + init = false; + if (ref == nullptr) + ref = &ref_saved_style; + + if (ImPlot::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + HelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. " + "Use \"Export\" below to save them somewhere."); + if (ImGui::BeginTabBar("##StyleEditor")) { + if (ImGui::BeginTabItem("Variables")) { + ImGui::Text("Item Styling"); + ImGui::SliderFloat("LineWeight", &style.LineWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("MarkerSize", &style.MarkerSize, 2.0f, 10.0f, "%.1f"); + ImGui::SliderFloat("MarkerWeight", &style.MarkerWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("FillAlpha", &style.FillAlpha, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("ErrorBarSize", &style.ErrorBarSize, 0.0f, 10.0f, "%.1f"); + ImGui::SliderFloat("ErrorBarWeight", &style.ErrorBarWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("DigitalBitHeight", &style.DigitalBitHeight, 0.0f, 20.0f, "%.1f"); + ImGui::SliderFloat("DigitalBitGap", &style.DigitalBitGap, 0.0f, 20.0f, "%.1f"); + ImGui::Text("Plot Styling"); + ImGui::SliderFloat("PlotBorderSize", &style.PlotBorderSize, 0.0f, 2.0f, "%.0f"); + ImGui::SliderFloat("MinorAlpha", &style.MinorAlpha, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("MajorTickLen", (float*)&style.MajorTickLen, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("MinorTickLen", (float*)&style.MinorTickLen, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("MajorTickSize", (float*)&style.MajorTickSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MinorTickSize", (float*)&style.MinorTickSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MajorGridSize", (float*)&style.MajorGridSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MinorGridSize", (float*)&style.MinorGridSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f"); + ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f"); + ImGui::Text("Plot Padding"); + ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendPadding", (float*)&style.LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&style.LegendSpacing, 0.0f, 5.0f, "%.0f"); + ImGui::SliderFloat2("MousePosPadding", (float*)&style.MousePosPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("AnnotationPadding", (float*)&style.AnnotationPadding, 0.0f, 5.0f, "%.0f"); + ImGui::SliderFloat2("FitPadding", (float*)&style.FitPadding, 0, 0.2f, "%.2f"); + + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Colors")) { + static int output_dest = 0; + static bool output_only_modified = false; + + if (ImGui::Button("Export", ImVec2(75,0))) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImPlot::GetStyle().Colors;\n"); + for (int i = 0; i < ImPlotCol_COUNT; i++) { + const ImVec4& col = style.Colors[i]; + const char* name = ImPlot::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) { + if (IsColorAuto(i)) + ImGui::LogText("colors[ImPlotCol_%s]%*s= IMPLOT_AUTO_COL;\n",name,14 - (int)strlen(name), ""); + else + ImGui::LogText("colors[ImPlotCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\n", + name, 14 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + } + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; + if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); + if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); + HelpMarker( + "In the color list:\n" + "Left-click on colored square to open color picker,\n" + "Right-click to open edit options menu."); + ImGui::Separator(); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImPlotCol_COUNT; i++) { + const char* name = ImPlot::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImVec4 temp = GetStyleColorVec4(i); + const bool is_auto = IsColorAuto(i); + if (!is_auto) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); + if (ImGui::Button("Auto")) { + if (is_auto) + style.Colors[i] = temp; + else + style.Colors[i] = IMPLOT_AUTO_COL; + BustItemCache(); + } + if (!is_auto) + ImGui::PopStyleVar(); + ImGui::SameLine(); + if (ImGui::ColorEdit4(name, &temp.x, ImGuiColorEditFlags_NoInputs | alpha_flags)) { + style.Colors[i] = temp; + BustItemCache(); + } + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { + ImGui::SameLine(175); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } + ImGui::SameLine(); if (ImGui::Button("Revert")) { + style.Colors[i] = ref->Colors[i]; + BustItemCache(); + } + } + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::Separator(); + ImGui::Text("Colors that are set to Auto (i.e. IMPLOT_AUTO_COL) will\n" + "be automatically deduced from your ImGui style or the\n" + "current ImPlot Colormap. If you want to style individual\n" + "plot items, use Push/PopStyleColor around its function."); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Colormaps")) { + static int output_dest = 0; + if (ImGui::Button("Export", ImVec2(75,0))) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + int size = GetColormapSize(); + const char* name = GetColormapName(gp.Style.Colormap); + ImGui::LogText("static const ImU32 %s_Data[%d] = {\n", name, size); + for (int i = 0; i < size; ++i) { + ImU32 col = GetColormapColorU32(i,gp.Style.Colormap); + ImGui::LogText(" %u%s\n", col, i == size - 1 ? "" : ","); + } + ImGui::LogText("};\nImPlotColormap %s = ImPlot::AddColormap(\"%s\", %s_Data, %d);", name, name, name, size); + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); + static bool edit = false; + ImGui::Checkbox("Edit Mode",&edit); + + // built-in/added + ImGui::Separator(); + for (int i = 0; i < gp.ColormapData.Count; ++i) { + ImGui::PushID(i); + int size = gp.ColormapData.GetKeyCount(i); + bool selected = i == gp.Style.Colormap; + + const char* name = GetColormapName(i); + if (!selected) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); + if (ImGui::Button(name, ImVec2(100,0))) { + gp.Style.Colormap = i; + BustItemCache(); + } + if (!selected) + ImGui::PopStyleVar(); + ImGui::SameLine(); + ImGui::BeginGroup(); + if (edit) { + for (int c = 0; c < size; ++c) { + ImGui::PushID(c); + ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i,c)); + if (ImGui::ColorEdit4("",&col4.x,ImGuiColorEditFlags_NoInputs)) { + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4); + gp.ColormapData.SetKeyColor(i,c,col32); + BustItemCache(); + } + if ((c + 1) % 12 != 0 && c != size -1) + ImGui::SameLine(); + ImGui::PopID(); + } + } + else { + if (ImPlot::ColormapButton("##",ImVec2(-1,0),i)) + edit = true; + } + ImGui::EndGroup(); + ImGui::PopID(); + } + + + static ImVector custom; + if (custom.Size == 0) { + custom.push_back(ImVec4(1,0,0,1)); + custom.push_back(ImVec4(0,1,0,1)); + custom.push_back(ImVec4(0,0,1,1)); + } + ImGui::Separator(); + ImGui::BeginGroup(); + static char name[16] = "MyColormap"; + + + if (ImGui::Button("+", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0))) + custom.push_back(ImVec4(0,0,0,1)); + ImGui::SameLine(); + if (ImGui::Button("-", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 2) + custom.pop_back(); + ImGui::SetNextItemWidth(100); + ImGui::InputText("##Name",name,16,ImGuiInputTextFlags_CharsNoBlank); + static bool qual = true; + ImGui::Checkbox("Qualitative",&qual); + if (ImGui::Button("Add", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name)==-1) + AddColormap(name,custom.Data,custom.Size,qual); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + for (int c = 0; c < custom.Size; ++c) { + ImGui::PushID(c); + if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs)) { + + } + if ((c + 1) % 12 != 0) + ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::EndGroup(); + + + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } +} + +void ShowUserGuide() { + ImGui::BulletText("Left-click drag within the plot area to pan X and Y axes."); + ImGui::Indent(); + ImGui::BulletText("Left-click drag on axis labels to pan an individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Scroll in the plot area to zoom both X any Y axes."); + ImGui::Indent(); + ImGui::BulletText("Scroll on axis labels to zoom an individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Right-click drag to box select data."); + ImGui::Indent(); + ImGui::BulletText("Hold Alt to expand box selection horizontally."); + ImGui::BulletText("Hold Shift to expand box selection vertically."); + ImGui::BulletText("Left-click while box selecting to cancel the selection."); + ImGui::Unindent(); + ImGui::BulletText("Double left-click to fit all visible data."); + ImGui::Indent(); + ImGui::BulletText("Double left-click axis labels to fit the individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Right-click open the full plot context menu."); + ImGui::Indent(); + ImGui::BulletText("Right-click axis labels to open an individual axis context menu."); + ImGui::Unindent(); + ImGui::BulletText("Click legend label icons to show/hide plot items."); +} + +void ShowTicksMetrics(const ImPlotTicker& ticker) { + ImGui::BulletText("Size: %d", ticker.TickCount()); + ImGui::BulletText("MaxSize: [%f,%f]", ticker.MaxSize.x, ticker.MaxSize.y); +} + +void ShowAxisMetrics(const ImPlotPlot& plot, const ImPlotAxis& axis) { + ImGui::BulletText("Label: %s", axis.LabelOffset == -1 ? "[none]" : plot.GetAxisLabel(axis)); + ImGui::BulletText("Flags: 0x%08X", axis.Flags); + ImGui::BulletText("Range: [%f,%f]",axis.Range.Min, axis.Range.Max); + ImGui::BulletText("Pixels: %f", axis.PixelSize()); + ImGui::BulletText("Aspect: %f", axis.GetAspect()); + ImGui::BulletText(axis.OrthoAxis == nullptr ? "OrtherAxis: NULL" : "OrthoAxis: 0x%08X", axis.OrthoAxis->ID); + ImGui::BulletText("LinkedMin: %p", (void*)axis.LinkedMin); + ImGui::BulletText("LinkedMax: %p", (void*)axis.LinkedMax); + ImGui::BulletText("HasRange: %s", axis.HasRange ? "true" : "false"); + ImGui::BulletText("Hovered: %s", axis.Hovered ? "true" : "false"); + ImGui::BulletText("Held: %s", axis.Held ? "true" : "false"); + + if (ImGui::TreeNode("Transform")) { + ImGui::BulletText("PixelMin: %f", axis.PixelMin); + ImGui::BulletText("PixelMax: %f", axis.PixelMax); + ImGui::BulletText("ScaleToPixel: %f", axis.ScaleToPixel); + ImGui::BulletText("ScaleMax: %f", axis.ScaleMax); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Ticks")) { + ShowTicksMetrics(axis.Ticker); + ImGui::TreePop(); + } +} + +void ShowMetricsWindow(bool* p_popen) { + + static bool show_plot_rects = false; + static bool show_axes_rects = false; + static bool show_axis_rects = false; + static bool show_canvas_rects = false; + static bool show_frame_rects = false; + static bool show_subplot_frame_rects = false; + static bool show_subplot_grid_rects = false; + + ImDrawList& fg = *ImGui::GetForegroundDrawList(); + + ImPlotContext& gp = *GImPlot; + // ImGuiContext& g = *GImGui; + ImGuiIO& io = ImGui::GetIO(); + ImGui::Begin("ImPlot Metrics", p_popen); + ImGui::Text("ImPlot " IMPLOT_VERSION); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::Text("Mouse Position: [%.0f,%.0f]", io.MousePos.x, io.MousePos.y); + ImGui::Separator(); + if (ImGui::TreeNode("Tools")) { + if (ImGui::Button("Bust Plot Cache")) + BustPlotCache(); + ImGui::SameLine(); + if (ImGui::Button("Bust Item Cache")) + BustItemCache(); + ImGui::Checkbox("Show Frame Rects", &show_frame_rects); + ImGui::Checkbox("Show Canvas Rects",&show_canvas_rects); + ImGui::Checkbox("Show Plot Rects", &show_plot_rects); + ImGui::Checkbox("Show Axes Rects", &show_axes_rects); + ImGui::Checkbox("Show Axis Rects", &show_axis_rects); + ImGui::Checkbox("Show Subplot Frame Rects", &show_subplot_frame_rects); + ImGui::Checkbox("Show Subplot Grid Rects", &show_subplot_grid_rects); + ImGui::TreePop(); + } + const int n_plots = gp.Plots.GetBufSize(); + const int n_subplots = gp.Subplots.GetBufSize(); + // render rects + for (int p = 0; p < n_plots; ++p) { + ImPlotPlot* plot = gp.Plots.GetByIndex(p); + if (show_frame_rects) + fg.AddRect(plot->FrameRect.Min, plot->FrameRect.Max, IM_COL32(255,0,255,255)); + if (show_canvas_rects) + fg.AddRect(plot->CanvasRect.Min, plot->CanvasRect.Max, IM_COL32(0,255,255,255)); + if (show_plot_rects) + fg.AddRect(plot->PlotRect.Min, plot->PlotRect.Max, IM_COL32(255,255,0,255)); + if (show_axes_rects) + fg.AddRect(plot->AxesRect.Min, plot->AxesRect.Max, IM_COL32(0,255,128,255)); + if (show_axis_rects) { + for (int i = 0; i < ImAxis_COUNT; ++i) { + if (plot->Axes[i].Enabled) + fg.AddRect(plot->Axes[i].HoverRect.Min, plot->Axes[i].HoverRect.Max, IM_COL32(0,255,0,255)); + } + } + } + for (int p = 0; p < n_subplots; ++p) { + ImPlotSubplot* subplot = gp.Subplots.GetByIndex(p); + if (show_subplot_frame_rects) + fg.AddRect(subplot->FrameRect.Min, subplot->FrameRect.Max, IM_COL32(255,0,0,255)); + if (show_subplot_grid_rects) + fg.AddRect(subplot->GridRect.Min, subplot->GridRect.Max, IM_COL32(0,0,255,255)); + } + if (ImGui::TreeNode("Plots","Plots (%d)", n_plots)) { + for (int p = 0; p < n_plots; ++p) { + // plot + ImPlotPlot& plot = *gp.Plots.GetByIndex(p); + ImGui::PushID(p); + if (ImGui::TreeNode("Plot", "Plot [0x%08X]", plot.ID)) { + int n_items = plot.Items.GetItemCount(); + if (ImGui::TreeNode("Items", "Items (%d)", n_items)) { + for (int i = 0; i < n_items; ++i) { + ImPlotItem* item = plot.Items.GetItemByIndex(i); + ImGui::PushID(i); + if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) { + ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show); + ImGui::Bullet(); + ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); + if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) + item->Color = ImGui::ColorConvertFloat4ToU32(temp); + + ImGui::BulletText("NameOffset: %d",item->NameOffset); + ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A"); + ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + char buff[16]; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImFormatString(buff,16,"X-Axis %d", i+1); + if (plot.XAxis(i).Enabled && ImGui::TreeNode(buff, "X-Axis %d [0x%08X]", i+1, plot.XAxis(i).ID)) { + ShowAxisMetrics(plot, plot.XAxis(i)); + ImGui::TreePop(); + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImFormatString(buff,16,"Y-Axis %d", i+1); + if (plot.YAxis(i).Enabled && ImGui::TreeNode(buff, "Y-Axis %d [0x%08X]", i+1, plot.YAxis(i).ID)) { + ShowAxisMetrics(plot, plot.YAxis(i)); + ImGui::TreePop(); + } + } + ImGui::BulletText("Title: %s", plot.HasTitle() ? plot.GetTitle() : "none"); + ImGui::BulletText("Flags: 0x%08X", plot.Flags); + ImGui::BulletText("Initialized: %s", plot.Initialized ? "true" : "false"); + ImGui::BulletText("Selecting: %s", plot.Selecting ? "true" : "false"); + ImGui::BulletText("Selected: %s", plot.Selected ? "true" : "false"); + ImGui::BulletText("Hovered: %s", plot.Hovered ? "true" : "false"); + ImGui::BulletText("Held: %s", plot.Held ? "true" : "false"); + ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false"); + ImGui::BulletText("ContextLocked: %s", plot.ContextLocked ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Subplots","Subplots (%d)", n_subplots)) { + for (int p = 0; p < n_subplots; ++p) { + // plot + ImPlotSubplot& plot = *gp.Subplots.GetByIndex(p); + ImGui::PushID(p); + if (ImGui::TreeNode("Subplot", "Subplot [0x%08X]", plot.ID)) { + int n_items = plot.Items.GetItemCount(); + if (ImGui::TreeNode("Items", "Items (%d)", n_items)) { + for (int i = 0; i < n_items; ++i) { + ImPlotItem* item = plot.Items.GetItemByIndex(i); + ImGui::PushID(i); + if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) { + ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show); + ImGui::Bullet(); + ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); + if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) + item->Color = ImGui::ColorConvertFloat4ToU32(temp); + + ImGui::BulletText("NameOffset: %d",item->NameOffset); + ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A"); + ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::BulletText("Flags: 0x%08X", plot.Flags); + ImGui::BulletText("FrameHovered: %s", plot.FrameHovered ? "true" : "false"); + ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Colormaps")) { + ImGui::BulletText("Colormaps: %d", gp.ColormapData.Count); + ImGui::BulletText("Memory: %d bytes", gp.ColormapData.Tables.Size * 4); + if (ImGui::TreeNode("Data")) { + for (int m = 0; m < gp.ColormapData.Count; ++m) { + if (ImGui::TreeNode(gp.ColormapData.GetName(m))) { + int count = gp.ColormapData.GetKeyCount(m); + int size = gp.ColormapData.GetTableSize(m); + bool qual = gp.ColormapData.IsQual(m); + ImGui::BulletText("Qualitative: %s", qual ? "true" : "false"); + ImGui::BulletText("Key Count: %d", count); + ImGui::BulletText("Table Size: %d", size); + ImGui::Indent(); + + static float t = 0.5; + ImVec4 samp; + float wid = 32 * 10 - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x; + ImGui::SetNextItemWidth(wid); + ImPlot::ColormapSlider("##Sample",&t,&samp,"%.3f",m); + ImGui::SameLine(); + ImGui::ColorButton("Sampler",samp); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int c = 0; c < size; ++c) { + ImVec4 col = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetTableColor(m,c)); + ImGui::PushID(m*1000+c); + ImGui::ColorButton("",col,0,ImVec2(10,10)); + ImGui::PopID(); + if ((c + 1) % 32 != 0 && c != size - 1) + ImGui::SameLine(); + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::Unindent(); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::End(); +} + +bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1, const ImPlotTime* t2) { + + ImGui::PushID(id); + ImGui::BeginGroup(); + + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 col_txt = style.Colors[ImGuiCol_Text]; + ImVec4 col_dis = style.Colors[ImGuiCol_TextDisabled]; + ImVec4 col_btn = style.Colors[ImGuiCol_Button]; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + + const float ht = ImGui::GetFrameHeight(); + ImVec2 cell_size(ht*1.25f,ht); + char buff[32]; + bool clk = false; + tm& Tm = GImPlot->Tm; + + const int min_yr = 1970; + const int max_yr = 2999; + + // t1 parts + int t1_mo = 0; int t1_md = 0; int t1_yr = 0; + if (t1 != nullptr) { + GetTime(*t1,&Tm); + t1_mo = Tm.tm_mon; + t1_md = Tm.tm_mday; + t1_yr = Tm.tm_year + 1900; + } + + // t2 parts + int t2_mo = 0; int t2_md = 0; int t2_yr = 0; + if (t2 != nullptr) { + GetTime(*t2,&Tm); + t2_mo = Tm.tm_mon; + t2_md = Tm.tm_mday; + t2_yr = Tm.tm_year + 1900; + } + + // day widget + if (*level == 0) { + *t = FloorTime(*t, ImPlotTimeUnit_Day); + GetTime(*t, &Tm); + const int this_year = Tm.tm_year + 1900; + const int last_year = this_year - 1; + const int next_year = this_year + 1; + const int this_mon = Tm.tm_mon; + const int last_mon = this_mon == 0 ? 11 : this_mon - 1; + const int next_mon = this_mon == 11 ? 0 : this_mon + 1; + const int days_this_mo = GetDaysInMonth(this_year, this_mon); + const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon); + ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo); + GetTime(t_first_mo,&Tm); + const int first_wd = Tm.tm_wday; + // month year + ImFormatString(buff, 32, "%s %d", MONTH_NAMES[this_mon], this_year); + if (ImGui::Button(buff)) + *level = 1; + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(this_year <= min_yr && this_mon == 0); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, -1); + EndDisabledControls(this_year <= min_yr && this_mon == 0); + ImGui::SameLine(); + BeginDisabledControls(this_year >= max_yr && this_mon == 11); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, 1); + EndDisabledControls(this_year >= max_yr && this_mon == 11); + // render weekday abbreviations + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + for (int i = 0; i < 7; ++i) { + ImGui::Button(WD_ABRVS[i],cell_size); + if (i != 6) { ImGui::SameLine(); } + } + ImGui::PopItemFlag(); + // 0 = last mo, 1 = this mo, 2 = next mo + int mo = first_wd > 0 ? 0 : 1; + int day = mo == 1 ? 1 : days_last_mo - first_wd + 1; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 7; ++j) { + if (mo == 0 && day > days_last_mo) { + mo = 1; + day = 1; + } + else if (mo == 1 && day > days_this_mo) { + mo = 2; + day = 1; + } + const int now_yr = (mo == 0 && this_mon == 0) ? last_year : ((mo == 2 && this_mon == 11) ? next_year : this_year); + const int now_mo = mo == 0 ? last_mon : (mo == 1 ? this_mon : next_mon); + const int now_md = day; + + const bool off_mo = mo == 0 || mo == 2; + const bool t1_or_t2 = (t1 != nullptr && t1_mo == now_mo && t1_yr == now_yr && t1_md == now_md) || + (t2 != nullptr && t2_mo == now_mo && t2_yr == now_yr && t2_md == now_md); + + if (off_mo) + ImGui::PushStyleColor(ImGuiCol_Text, col_dis); + if (t1_or_t2) { + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + ImGui::PushStyleColor(ImGuiCol_Text, col_txt); + } + ImGui::PushID(i*7+j); + ImFormatString(buff,32,"%d",day); + if (now_yr == min_yr-1 || now_yr == max_yr+1) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size) && !clk) { + *t = MakeTime(now_yr, now_mo, now_md); + clk = true; + } + ImGui::PopID(); + if (t1_or_t2) + ImGui::PopStyleColor(2); + if (off_mo) + ImGui::PopStyleColor(); + if (j != 6) + ImGui::SameLine(); + day++; + } + } + } + // month widget + else if (*level == 1) { + *t = FloorTime(*t, ImPlotTimeUnit_Mo); + GetTime(*t, &Tm); + int this_yr = Tm.tm_year + 1900; + ImFormatString(buff, 32, "%d", this_yr); + if (ImGui::Button(buff)) + *level = 2; + BeginDisabledControls(this_yr <= min_yr); + ImGui::SameLine(5*cell_size.x); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, -1); + EndDisabledControls(this_yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(this_yr >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, 1); + EndDisabledControls(this_yr >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/3.0f; + int mo = 0; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != nullptr && t1_yr == this_yr && t1_mo == mo) || + (t2 != nullptr && t2_yr == this_yr && t2_mo == mo); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + if (ImGui::Button(MONTH_ABRVS[mo],cell_size) && !clk) { + *t = MakeTime(this_yr, mo); + *level = 0; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + mo++; + } + } + } + else if (*level == 2) { + *t = FloorTime(*t, ImPlotTimeUnit_Yr); + int this_yr = GetYear(*t); + int yr = this_yr - this_yr % 20; + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImFormatString(buff,32,"%d-%d",yr,yr+19); + ImGui::Button(buff); + ImGui::PopItemFlag(); + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(yr <= min_yr); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = MakeTime(yr-20); + EndDisabledControls(yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(yr + 20 >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = MakeTime(yr+20); + EndDisabledControls(yr+ 20 >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/5.0f; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != nullptr && t1_yr == yr) || (t2 != nullptr && t2_yr == yr); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + ImFormatString(buff,32,"%d",yr); + if (yr<1970||yr>3000) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size)) { + *t = MakeTime(yr); + *level = 1; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + yr++; + } + } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::EndGroup(); + ImGui::PopID(); + return clk; +} + +bool ShowTimePicker(const char* id, ImPlotTime* t) { + ImPlotContext& gp = *GImPlot; + ImGui::PushID(id); + tm& Tm = gp.Tm; + GetTime(*t,&Tm); + + static const char* nums[] = { "00","01","02","03","04","05","06","07","08","09", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59"}; + + static const char* am_pm[] = {"am","pm"}; + + bool hour24 = gp.Style.Use24HourClock; + + int hr = hour24 ? Tm.tm_hour : ((Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12); + int min = Tm.tm_min; + int sec = Tm.tm_sec; + int ap = Tm.tm_hour < 12 ? 0 : 1; + + bool changed = false; + + ImVec2 spacing = ImGui::GetStyle().ItemSpacing; + spacing.x = 0; + float width = ImGui::CalcTextSize("888").x; + float height = ImGui::GetFrameHeight(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, spacing); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,2.0f); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered)); + + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##hr",nums[hr],ImGuiComboFlags_NoArrowButton)) { + const int ia = hour24 ? 0 : 1; + const int ib = hour24 ? 24 : 13; + for (int i = ia; i < ib; ++i) { + if (ImGui::Selectable(nums[i],i==hr)) { + hr = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##min",nums[min],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==min)) { + min = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##sec",nums[sec],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==sec)) { + sec = i; + changed = true; + } + } + ImGui::EndCombo(); + } + if (!hour24) { + ImGui::SameLine(); + if (ImGui::Button(am_pm[ap],ImVec2(0,height))) { + ap = 1 - ap; + changed = true; + } + } + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); + ImGui::PopID(); + + if (changed) { + if (!hour24) + hr = hr % 12 + ap * 12; + Tm.tm_hour = hr; + Tm.tm_min = min; + Tm.tm_sec = sec; + *t = MkTime(&Tm); + } + + return changed; +} + +void StyleColorsAuto(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.25f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_TitleText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_InlayText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisGrid] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Selection] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Crosshairs] = IMPLOT_AUTO_COL; +} + +void StyleColorsClassic(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.5f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImPlotCol_LegendBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImPlotCol_LegendText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(0.90f, 0.90f, 0.90f, 0.25f); + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(0.97f, 0.97f, 0.39f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.50f, 0.50f, 0.50f, 0.75f); +} + +void StyleColorsDark(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.25f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); + colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImPlotCol_LegendBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImPlotCol_LegendText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f); + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(1.00f, 1.00f, 1.00f, 0.50f); +} + +void StyleColorsLight(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 1.0f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImPlotCol_LegendBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.82f, 0.82f, 0.82f, 0.80f); + colors[ImPlotCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisTick] = ImVec4(0.00f, 0.00f, 0.00f, 0.25f); + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(0.82f, 0.64f, 0.03f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f); +} + +//----------------------------------------------------------------------------- +// [SECTION] Obsolete Functions/Types +//----------------------------------------------------------------------------- + +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS + +bool BeginPlot(const char* title, const char* x_label, const char* y1_label, const ImVec2& size, + ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y1_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags, + const char* y2_label, const char* y3_label) +{ + if (!BeginPlot(title, size, flags)) + return false; + SetupAxis(ImAxis_X1, x_label, x_flags); + SetupAxis(ImAxis_Y1, y1_label, y1_flags); + if (ImHasFlag(flags, ImPlotFlags_YAxis2)) + SetupAxis(ImAxis_Y2, y2_label, y2_flags); + if (ImHasFlag(flags, ImPlotFlags_YAxis3)) + SetupAxis(ImAxis_Y3, y3_label, y3_flags); + return true; +} + +#endif + +} // namespace ImPlot diff --git a/crogine/src/imgui/implot_demo.cpp b/crogine/src/imgui/implot_demo.cpp new file mode 100644 index 000000000..e0f12f91a --- /dev/null +++ b/crogine/src/imgui/implot_demo.cpp @@ -0,0 +1,2450 @@ +// MIT License + +// Copyright (c) 2022 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.14 + +// We define this so that the demo does not accidentally use deprecated API +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define sprintf sprintf_s +#endif + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define CHECKBOX_FLAG(flags, flag) ImGui::CheckboxFlags(#flag, (unsigned int*)&flags, flag) + +// Encapsulates examples for customizing ImPlot. +namespace MyImPlot { + +// Example for Custom Data and Getters section. +struct Vector2f { + Vector2f(float _x, float _y) { x = _x; y = _y; } + float x, y; +}; + +// Example for Custom Data and Getters section. +struct WaveData { + double X, Amp, Freq, Offset; + WaveData(double x, double amp, double freq, double offset) { X = x; Amp = amp; Freq = freq; Offset = offset; } +}; +ImPlotPoint SineWave(int idx, void* wave_data); +ImPlotPoint SawWave(int idx, void* wave_data); +ImPlotPoint Spiral(int idx, void* wave_data); + +// Example for Tables section. +void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size); + +// Example for Custom Plotters and Tooltips section. +void PlotCandlestick(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, bool tooltip = true, float width_percent = 0.25f, ImVec4 bullCol = ImVec4(0,1,0,1), ImVec4 bearCol = ImVec4(1,0,0,1)); + +// Example for Custom Styles section. +void StyleSeaborn(); + +} // namespace MyImPlot + +namespace ImPlot { + +template +inline T RandomRange(T min, T max) { + T scale = rand() / (T) RAND_MAX; + return min + scale * ( max - min ); +} + +ImVec4 RandomColor() { + ImVec4 col; + col.x = RandomRange(0.0f,1.0f); + col.y = RandomRange(0.0f,1.0f); + col.z = RandomRange(0.0f,1.0f); + col.w = 1.0f; + return col; +} + +double RandomGauss() { + static double V1, V2, S; + static int phase = 0; + double X; + if(phase == 0) { + do { + double U1 = (double)rand() / RAND_MAX; + double U2 = (double)rand() / RAND_MAX; + V1 = 2 * U1 - 1; + V2 = 2 * U2 - 1; + S = V1 * V1 + V2 * V2; + } while(S >= 1 || S == 0); + + X = V1 * sqrt(-2 * log(S) / S); + } else + X = V2 * sqrt(-2 * log(S) / S); + phase = 1 - phase; + return X; +} + +template +struct NormalDistribution { + NormalDistribution(double mean, double sd) { + for (int i = 0; i < N; ++i) + Data[i] = RandomGauss()*sd + mean; + } + double Data[N]; +}; + +// utility structure for realtime plot +struct ScrollingBuffer { + int MaxSize; + int Offset; + ImVector Data; + ScrollingBuffer(int max_size = 2000) { + MaxSize = max_size; + Offset = 0; + Data.reserve(MaxSize); + } + void AddPoint(float x, float y) { + if (Data.size() < MaxSize) + Data.push_back(ImVec2(x,y)); + else { + Data[Offset] = ImVec2(x,y); + Offset = (Offset + 1) % MaxSize; + } + } + void Erase() { + if (Data.size() > 0) { + Data.shrink(0); + Offset = 0; + } + } +}; + +// utility structure for realtime plot +struct RollingBuffer { + float Span; + ImVector Data; + RollingBuffer() { + Span = 10.0f; + Data.reserve(2000); + } + void AddPoint(float x, float y) { + float xmod = fmodf(x, Span); + if (!Data.empty() && xmod < Data.back().x) + Data.shrink(0); + Data.push_back(ImVec2(xmod, y)); + } +}; + +// Huge data used by Time Formatting example (~500 MB allocation!) +struct HugeTimeData { + HugeTimeData(double min) { + Ts = new double[Size]; + Ys = new double[Size]; + for (int i = 0; i < Size; ++i) { + Ts[i] = min + i; + Ys[i] = GetY(Ts[i]); + } + } + ~HugeTimeData() { delete[] Ts; delete[] Ys; } + static double GetY(double t) { + return 0.5 + 0.25 * sin(t/86400/12) + 0.005 * sin(t/3600); + } + double* Ts; + double* Ys; + static const int Size = 60*60*24*366; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Demo Functions +//----------------------------------------------------------------------------- + +void Demo_Help() { + ImGui::Text("ABOUT THIS DEMO:"); + ImGui::BulletText("Sections below are demonstrating many aspects of the library."); + ImGui::BulletText("The \"Tools\" menu above gives access to: Style Editors (ImPlot/ImGui)\n" + "and Metrics (general purpose Dear ImGui debugging tool)."); + ImGui::Separator(); + ImGui::Text("PROGRAMMER GUIDE:"); + ImGui::BulletText("See the ShowDemoWindow() code in implot_demo.cpp. <- you are here!"); + ImGui::BulletText("If you see visual artifacts, do one of the following:"); + ImGui::Indent(); + ImGui::BulletText("Handle ImGuiBackendFlags_RendererHasVtxOffset for 16-bit indices in your backend."); + ImGui::BulletText("Or, enable 32-bit indices in imconfig.h."); + ImGui::BulletText("Your current configuration is:"); + ImGui::Indent(); + ImGui::BulletText("ImDrawIdx: %d-bit", (int)(sizeof(ImDrawIdx) * 8)); + ImGui::BulletText("ImGuiBackendFlags_RendererHasVtxOffset: %s", (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ? "True" : "False"); + ImGui::Unindent(); + ImGui::Unindent(); + ImGui::Separator(); + ImGui::Text("USER GUIDE:"); + ShowUserGuide(); +} + +//----------------------------------------------------------------------------- + +void ButtonSelector(const char* label, ImGuiMouseButton* b) { + ImGui::PushID(label); + if (ImGui::RadioButton("LMB",*b == ImGuiMouseButton_Left)) + *b = ImGuiMouseButton_Left; + ImGui::SameLine(); + if (ImGui::RadioButton("RMB",*b == ImGuiMouseButton_Right)) + *b = ImGuiMouseButton_Right; + ImGui::SameLine(); + if (ImGui::RadioButton("MMB",*b == ImGuiMouseButton_Middle)) + *b = ImGuiMouseButton_Middle; + ImGui::PopID(); +} + +void ModSelector(const char* label, int* k) { + ImGui::PushID(label); + ImGui::CheckboxFlags("Ctrl", (unsigned int*)k, ImGuiMod_Ctrl); ImGui::SameLine(); + ImGui::CheckboxFlags("Shift", (unsigned int*)k, ImGuiMod_Shift); ImGui::SameLine(); + ImGui::CheckboxFlags("Alt", (unsigned int*)k, ImGuiMod_Alt); ImGui::SameLine(); + ImGui::CheckboxFlags("Super", (unsigned int*)k, ImGuiMod_Super); + ImGui::PopID(); +} + +void InputMapping(const char* label, ImGuiMouseButton* b, int* k) { + ImGui::LabelText("##","%s",label); + if (b != nullptr) { + ImGui::SameLine(100); + ButtonSelector(label,b); + } + if (k != nullptr) { + ImGui::SameLine(300); + ModSelector(label,k); + } +} + +void ShowInputMapping() { + ImPlotInputMap& map = ImPlot::GetInputMap(); + InputMapping("Pan",&map.Pan,&map.PanMod); + InputMapping("Fit",&map.Fit,nullptr); + InputMapping("Select",&map.Select,&map.SelectMod); + InputMapping("SelectHorzMod",nullptr,&map.SelectHorzMod); + InputMapping("SelectVertMod",nullptr,&map.SelectVertMod); + InputMapping("SelectCancel",&map.SelectCancel,nullptr); + InputMapping("Menu",&map.Menu,nullptr); + InputMapping("OverrideMod",nullptr,&map.OverrideMod); + InputMapping("ZoomMod",nullptr,&map.ZoomMod); + ImGui::SliderFloat("ZoomRate",&map.ZoomRate,-1,1); +} + +void Demo_Config() { + ImGui::ShowFontSelector("Font"); + ImGui::ShowStyleSelector("ImGui Style"); + ImPlot::ShowStyleSelector("ImPlot Style"); + ImPlot::ShowColormapSelector("ImPlot Colormap"); + ImPlot::ShowInputMapSelector("Input Map"); + ImGui::Separator(); + ImGui::Checkbox("Use Local Time", &ImPlot::GetStyle().UseLocalTime); + ImGui::Checkbox("Use ISO 8601", &ImPlot::GetStyle().UseISO8601); + ImGui::Checkbox("Use 24 Hour Clock", &ImPlot::GetStyle().Use24HourClock); + ImGui::Separator(); + if (ImPlot::BeginPlot("Preview")) { + static double now = (double)time(nullptr); + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxisLimits(ImAxis_X1, now, now + 24*3600); + for (int i = 0; i < 10; ++i) { + double x[2] = {now, now + 24*3600}; + double y[2] = {0,i/9.0}; + ImGui::PushID(i); + ImPlot::PlotLine("##Line",x,y,2); + ImGui::PopID(); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LinePlots() { + static float xs1[1001], ys1[1001]; + for (int i = 0; i < 1001; ++i) { + xs1[i] = i * 0.001f; + ys1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + } + static double xs2[20], ys2[20]; + for (int i = 0; i < 20; ++i) { + xs2[i] = i * 1/19.0f; + ys2[i] = xs2[i] * xs2[i]; + } + if (ImPlot::BeginPlot("Line Plots")) { + ImPlot::SetupAxes("x","y"); + ImPlot::PlotLine("f(x)", xs1, ys1, 1001); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::PlotLine("g(x)", xs2, ys2, 20,ImPlotLineFlags_Segments); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_FilledLinePlots() { + static double xs1[101], ys1[101], ys2[101], ys3[101]; + srand(0); + for (int i = 0; i < 101; ++i) { + xs1[i] = (float)i; + ys1[i] = RandomRange(400.0,450.0); + ys2[i] = RandomRange(275.0,350.0); + ys3[i] = RandomRange(150.0,225.0); + } + static bool show_lines = true; + static bool show_fills = true; + static float fill_ref = 0; + static int shade_mode = 0; + static ImPlotShadedFlags flags = 0; + ImGui::Checkbox("Lines",&show_lines); ImGui::SameLine(); + ImGui::Checkbox("Fills",&show_fills); + if (show_fills) { + ImGui::SameLine(); + if (ImGui::RadioButton("To -INF",shade_mode == 0)) + shade_mode = 0; + ImGui::SameLine(); + if (ImGui::RadioButton("To +INF",shade_mode == 1)) + shade_mode = 1; + ImGui::SameLine(); + if (ImGui::RadioButton("To Ref",shade_mode == 2)) + shade_mode = 2; + if (shade_mode == 2) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(100); + ImGui::DragFloat("##Ref",&fill_ref, 1, -100, 500); + } + } + + if (ImPlot::BeginPlot("Stock Prices")) { + ImPlot::SetupAxes("Days","Price"); + ImPlot::SetupAxesLimits(0,100,0,500); + if (show_fills) { + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::PlotShaded("Stock 1", xs1, ys1, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PlotShaded("Stock 2", xs1, ys2, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PlotShaded("Stock 3", xs1, ys3, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PopStyleVar(); + } + if (show_lines) { + ImPlot::PlotLine("Stock 1", xs1, ys1, 101); + ImPlot::PlotLine("Stock 2", xs1, ys2, 101); + ImPlot::PlotLine("Stock 3", xs1, ys3, 101); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ShadedPlots() { + static float xs[1001], ys[1001], ys1[1001], ys2[1001], ys3[1001], ys4[1001]; + srand(0); + for (int i = 0; i < 1001; ++i) { + xs[i] = i * 0.001f; + ys[i] = 0.25f + 0.25f * sinf(25 * xs[i]) * sinf(5 * xs[i]) + RandomRange(-0.01f, 0.01f); + ys1[i] = ys[i] + RandomRange(0.1f, 0.12f); + ys2[i] = ys[i] - RandomRange(0.1f, 0.12f); + ys3[i] = 0.75f + 0.2f * sinf(25 * xs[i]); + ys4[i] = 0.75f + 0.1f * cosf(25 * xs[i]); + } + static float alpha = 0.25f; + ImGui::DragFloat("Alpha",&alpha,0.01f,0,1); + + if (ImPlot::BeginPlot("Shaded Plots")) { + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); + ImPlot::PlotShaded("Uncertain Data",xs,ys1,ys2,1001); + ImPlot::PlotLine("Uncertain Data", xs, ys, 1001); + ImPlot::PlotShaded("Overlapping",xs,ys3,ys4,1001); + ImPlot::PlotLine("Overlapping",xs,ys3,1001); + ImPlot::PlotLine("Overlapping",xs,ys4,1001); + ImPlot::PopStyleVar(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ScatterPlots() { + srand(0); + static float xs1[100], ys1[100]; + for (int i = 0; i < 100; ++i) { + xs1[i] = i * 0.01f; + ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); + } + static float xs2[50], ys2[50]; + for (int i = 0; i < 50; i++) { + xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX); + ys2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX); + } + + if (ImPlot::BeginPlot("Scatter Plot")) { + ImPlot::PlotScatter("Data 1", xs1, ys1, 100); + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1)); + ImPlot::PlotScatter("Data 2", xs2, ys2, 50); + ImPlot::PopStyleVar(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_StairstepPlots() { + static float ys1[21], ys2[21]; + for (int i = 0; i < 21; ++i) { + ys1[i] = 0.75f + 0.2f * sinf(10 * i * 0.05f); + ys2[i] = 0.25f + 0.2f * sinf(10 * i * 0.05f); + } + static ImPlotStairsFlags flags = 0; + CHECKBOX_FLAG(flags, ImPlotStairsFlags_Shaded); + if (ImPlot::BeginPlot("Stairstep Plot")) { + ImPlot::SetupAxes("x","f(x)"); + ImPlot::SetupAxesLimits(0,1,0,1); + + ImPlot::PushStyleColor(ImPlotCol_Line, ImVec4(0.5f,0.5f,0.5f,1.0f)); + ImPlot::PlotLine("##1",ys1,21,0.05f); + ImPlot::PlotLine("##2",ys2,21,0.05f); + ImPlot::PopStyleColor(); + + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); + ImPlot::PlotStairs("Post Step (default)", ys1, 21, 0.05f, 0, flags); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); + ImPlot::PlotStairs("Pre Step", ys2, 21, 0.05f, 0, flags|ImPlotStairsFlags_PreStep); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarPlots() { + static ImS8 data[10] = {1,2,3,4,5,6,7,8,9,10}; + if (ImPlot::BeginPlot("Bar Plot")) { + ImPlot::PlotBars("Vertical",data,10,0.7,1); + ImPlot::PlotBars("Horizontal",data,10,0.4,1,ImPlotBarsFlags_Horizontal); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarGroups() { + static ImS8 data[30] = {83, 67, 23, 89, 83, 78, 91, 82, 85, 90, // midterm + 80, 62, 56, 99, 55, 78, 88, 78, 90, 100, // final + 80, 69, 52, 92, 72, 78, 75, 76, 89, 95}; // course + + static const char* ilabels[] = {"Midterm Exam","Final Exam","Course Grade"}; + static const char* glabels[] = {"S1","S2","S3","S4","S5","S6","S7","S8","S9","S10"}; + static const double positions[] = {0,1,2,3,4,5,6,7,8,9}; + + static int items = 3; + static int groups = 10; + static float size = 0.67f; + + static ImPlotBarGroupsFlags flags = 0; + static bool horz = false; + + ImGui::CheckboxFlags("Stacked", (unsigned int*)&flags, ImPlotBarGroupsFlags_Stacked); + ImGui::SameLine(); + ImGui::Checkbox("Horizontal",&horz); + + ImGui::SliderInt("Items",&items,1,3); + ImGui::SliderFloat("Size",&size,0,1); + + if (ImPlot::BeginPlot("Bar Group")) { + ImPlot::SetupLegend(ImPlotLocation_East, ImPlotLegendFlags_Outside); + if (horz) { + ImPlot::SetupAxes("Score","Student",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxisTicks(ImAxis_Y1,positions, groups, glabels); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags|ImPlotBarGroupsFlags_Horizontal); + } + else { + ImPlot::SetupAxes("Student","Score",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxisTicks(ImAxis_X1,positions, groups, glabels); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarStacks() { + + static ImPlotColormap Liars = -1; + if (Liars == -1) { + static const ImU32 Liars_Data[6] = { 4282515870, 4282609140, 4287357182, 4294630301, 4294945280, 4294921472 }; + Liars = ImPlot::AddColormap("Liars", Liars_Data, 6); + } + + static bool diverging = true; + ImGui::Checkbox("Diverging",&diverging); + + static const char* politicians[] = {"Trump","Bachman","Cruz","Gingrich","Palin","Santorum","Walker","Perry","Ryan","McCain","Rubio","Romney","Rand Paul","Christie","Biden","Kasich","Sanders","J Bush","H Clinton","Obama"}; + static int data_reg[] = {18,26,7,14,10,8,6,11,4,4,3,8,6,8,6,5,0,3,1,2, // Pants on Fire + 43,36,30,21,30,27,25,17,11,22,15,16,16,17,12,12,14,6,13,12, // False + 16,13,28,22,15,21,15,18,30,17,24,18,13,10,14,15,17,22,14,12, // Mostly False + 17,10,13,25,12,22,19,26,23,17,22,27,20,26,29,17,18,22,21,27, // Half True + 5,7,16,10,10,12,23,13,17,20,22,16,23,19,20,26,36,29,27,26, // Mostly True + 1,8,6,8,23,10,12,15,15,20,14,15,22,20,19,25,15,18,24,21}; // True + static const char* labels_reg[] = {"Pants on Fire","False","Mostly False","Half True","Mostly True","True"}; + + + static int data_div[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Pants on Fire (dummy, to order legend logically) + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // False (dummy, to order legend logically) + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Mostly False (dummy, to order legend logically) + -16,-13,-28,-22,-15,-21,-15,-18,-30,-17,-24,-18,-13,-10,-14,-15,-17,-22,-14,-12, // Mostly False + -43,-36,-30,-21,-30,-27,-25,-17,-11,-22,-15,-16,-16,-17,-12,-12,-14,-6,-13,-12, // False + -18,-26,-7,-14,-10,-8,-6,-11,-4,-4,-3,-8,-6,-8,-6,-5,0,-3,-1,-2, // Pants on Fire + 17,10,13,25,12,22,19,26,23,17,22,27,20,26,29,17,18,22,21,27, // Half True + 5,7,16,10,10,12,23,13,17,20,22,16,23,19,20,26,36,29,27,26, // Mostly True + 1,8,6,8,23,10,12,15,15,20,14,15,22,20,19,25,15,18,24,21}; // True + static const char* labels_div[] = {"Pants on Fire","False","Mostly False","Mostly False","False","Pants on Fire","Half True","Mostly True","True"}; + + ImPlot::PushColormap(Liars); + if (ImPlot::BeginPlot("PolitiFact: Who Lies More?",ImVec2(-1,400),ImPlotFlags_NoMouseText)) { + ImPlot::SetupLegend(ImPlotLocation_South, ImPlotLegendFlags_Outside|ImPlotLegendFlags_Horizontal); + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Invert); + ImPlot::SetupAxisTicks(ImAxis_Y1,0,19,20,politicians,false); + if (diverging) + ImPlot::PlotBarGroups(labels_div,data_div,9,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + else + ImPlot::PlotBarGroups(labels_reg,data_reg,6,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_ErrorBars() { + static float xs[5] = {1,2,3,4,5}; + static float bar[5] = {1,2,5,3,4}; + static float lin1[5] = {8,8,9,7,8}; + static float lin2[5] = {6,7,6,9,6}; + static float err1[5] = {0.2f, 0.4f, 0.2f, 0.6f, 0.4f}; + static float err2[5] = {0.4f, 0.2f, 0.4f, 0.8f, 0.6f}; + static float err3[5] = {0.09f, 0.14f, 0.09f, 0.12f, 0.16f}; + static float err4[5] = {0.02f, 0.08f, 0.15f, 0.05f, 0.2f}; + + + if (ImPlot::BeginPlot("##ErrorBars")) { + ImPlot::SetupAxesLimits(0, 6, 0, 10); + ImPlot::PlotBars("Bar", xs, bar, 5, 0.5f); + ImPlot::PlotErrorBars("Bar", xs, bar, err1, 5); + ImPlot::SetNextErrorBarStyle(ImPlot::GetColormapColor(1), 0); + ImPlot::PlotErrorBars("Line", xs, lin1, err1, err2, 5); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::PlotLine("Line", xs, lin1, 5); + ImPlot::PushStyleColor(ImPlotCol_ErrorBar, ImPlot::GetColormapColor(2)); + ImPlot::PlotErrorBars("Scatter", xs, lin2, err2, 5); + ImPlot::PlotErrorBars("Scatter", xs, lin2, err3, err4, 5, ImPlotErrorBarsFlags_Horizontal); + ImPlot::PopStyleColor(); + ImPlot::PlotScatter("Scatter", xs, lin2, 5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_StemPlots() { + static double xs[51], ys1[51], ys2[51]; + for (int i = 0; i < 51; ++i) { + xs[i] = i * 0.02; + ys1[i] = 1.0 + 0.5 * sin(25*xs[i])*cos(2*xs[i]); + ys2[i] = 0.5 + 0.25 * sin(10*xs[i]) * sin(xs[i]); + } + if (ImPlot::BeginPlot("Stem Plots")) { + ImPlot::SetupAxisLimits(ImAxis_X1,0,1.0); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1.6); + ImPlot::PlotStems("Stems 1",xs,ys1,51); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::PlotStems("Stems 2", xs, ys2,51); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_InfiniteLines() { + static double vals[] = {0.25, 0.5, 0.75}; + if (ImPlot::BeginPlot("##Infinite")) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoInitialFit,ImPlotAxisFlags_NoInitialFit); + ImPlot::PlotInfLines("Vertical",vals,3); + ImPlot::PlotInfLines("Horizontal",vals,3,ImPlotInfLinesFlags_Horizontal); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_PieCharts() { + static const char* labels1[] = {"Frogs","Hogs","Dogs","Logs"}; + static float data1[] = {0.15f, 0.30f, 0.2f, 0.05f}; + static ImPlotPieChartFlags flags = 0; + ImGui::SetNextItemWidth(250); + ImGui::DragFloat4("Values", data1, 0.01f, 0, 1); + if ((data1[0] + data1[1] + data1[2] + data1[3]) < 1) { + ImGui::SameLine(); + CHECKBOX_FLAG(flags,ImPlotPieChartFlags_Normalize); + } + + if (ImPlot::BeginPlot("##Pie1", ImVec2(250,250), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 1, 0, 1); + ImPlot::PlotPieChart(labels1, data1, 4, 0.5, 0.5, 0.4, "%.2f", 90, flags); + ImPlot::EndPlot(); + } + + ImGui::SameLine(); + + static const char* labels2[] = {"A","B","C","D","E"}; + static int data2[] = {1,1,2,3,5}; + + ImPlot::PushColormap(ImPlotColormap_Pastel); + if (ImPlot::BeginPlot("##Pie2", ImVec2(250,250), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 1, 0, 1); + ImPlot::PlotPieChart(labels2, data2, 5, 0.5, 0.5, 0.4, "%.0f", 180, flags); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_Heatmaps() { + static float values1[7][7] = {{0.8f, 2.4f, 2.5f, 3.9f, 0.0f, 4.0f, 0.0f}, + {2.4f, 0.0f, 4.0f, 1.0f, 2.7f, 0.0f, 0.0f}, + {1.1f, 2.4f, 0.8f, 4.3f, 1.9f, 4.4f, 0.0f}, + {0.6f, 0.0f, 0.3f, 0.0f, 3.1f, 0.0f, 0.0f}, + {0.7f, 1.7f, 0.6f, 2.6f, 2.2f, 6.2f, 0.0f}, + {1.3f, 1.2f, 0.0f, 0.0f, 0.0f, 3.2f, 5.1f}, + {0.1f, 2.0f, 0.0f, 1.4f, 0.0f, 1.9f, 6.3f}}; + static float scale_min = 0; + static float scale_max = 6.3f; + static const char* xlabels[] = {"C1","C2","C3","C4","C5","C6","C7"}; + static const char* ylabels[] = {"R1","R2","R3","R4","R5","R6","R7"}; + + static ImPlotColormap map = ImPlotColormap_Viridis; + if (ImPlot::ColormapButton(ImPlot::GetColormapName(map),ImVec2(225,0),map)) { + map = (map + 1) % ImPlot::GetColormapCount(); + // We bust the color cache of our plots so that item colors will + // resample the new colormap in the event that they have already + // been created. See documentation in implot.h. + BustColorCache("##Heatmap1"); + BustColorCache("##Heatmap2"); + } + + ImGui::SameLine(); + ImGui::LabelText("##Colormap Index", "%s", "Change Colormap"); + ImGui::SetNextItemWidth(225); + ImGui::DragFloatRange2("Min / Max",&scale_min, &scale_max, 0.01f, -20, 20); + + static ImPlotHeatmapFlags hm_flags = 0; + + ImGui::CheckboxFlags("Column Major", (unsigned int*)&hm_flags, ImPlotHeatmapFlags_ColMajor); + + static ImPlotAxisFlags axes_flags = ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks; + + ImPlot::PushColormap(map); + + if (ImPlot::BeginPlot("##Heatmap1",ImVec2(225,225),ImPlotFlags_NoLegend|ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, axes_flags, axes_flags); + ImPlot::SetupAxisTicks(ImAxis_X1,0 + 1.0/14.0, 1 - 1.0/14.0, 7, xlabels); + ImPlot::SetupAxisTicks(ImAxis_Y1,1 - 1.0/14.0, 0 + 1.0/14.0, 7, ylabels); + ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max,"%g",ImPlotPoint(0,0),ImPlotPoint(1,1),hm_flags); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale("##HeatScale",scale_min, scale_max, ImVec2(60,225)); + + ImGui::SameLine(); + + const int size = 80; + static double values2[size*size]; + srand((unsigned int)(ImGui::GetTime()*1000000)); + for (int i = 0; i < size*size; ++i) + values2[i] = RandomRange(0.0,1.0); + + if (ImPlot::BeginPlot("##Heatmap2",ImVec2(225,225))) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(-1,1,-1,1); + ImPlot::PlotHeatmap("heat1",values2,size,size,0,1,nullptr); + ImPlot::PlotHeatmap("heat2",values2,size,size,0,1,nullptr, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); + +} + +//----------------------------------------------------------------------------- + +void Demo_Histogram() { + static ImPlotHistogramFlags hist_flags = ImPlotHistogramFlags_Density; + static int bins = 50; + static double mu = 5; + static double sigma = 2; + ImGui::SetNextItemWidth(200); + if (ImGui::RadioButton("Sqrt",bins==ImPlotBin_Sqrt)) { bins = ImPlotBin_Sqrt; } ImGui::SameLine(); + if (ImGui::RadioButton("Sturges",bins==ImPlotBin_Sturges)) { bins = ImPlotBin_Sturges; } ImGui::SameLine(); + if (ImGui::RadioButton("Rice",bins==ImPlotBin_Rice)) { bins = ImPlotBin_Rice; } ImGui::SameLine(); + if (ImGui::RadioButton("Scott",bins==ImPlotBin_Scott)) { bins = ImPlotBin_Scott; } ImGui::SameLine(); + if (ImGui::RadioButton("N Bins",bins>=0)) { bins = 50; } + if (bins>=0) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::SliderInt("##Bins", &bins, 1, 100); + } + ImGui::CheckboxFlags("Horizontal", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Horizontal); + ImGui::SameLine(); + ImGui::CheckboxFlags("Density", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Density); + ImGui::SameLine(); + ImGui::CheckboxFlags("Cumulative", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Cumulative); + + static bool range = false; + ImGui::Checkbox("Range", &range); + static float rmin = -3; + static float rmax = 13; + if (range) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::DragFloat2("##Range",&rmin,0.1f,-3,13); + ImGui::SameLine(); + ImGui::CheckboxFlags("Exclude Outliers", (unsigned int*)&hist_flags, ImPlotHistogramFlags_NoOutliers); + } + static NormalDistribution<10000> dist(mu, sigma); + static double x[100]; + static double y[100]; + if (hist_flags & ImPlotHistogramFlags_Density) { + for (int i = 0; i < 100; ++i) { + x[i] = -3 + 16 * (double)i/99.0; + y[i] = exp( - (x[i]-mu)*(x[i]-mu) / (2*sigma*sigma)) / (sigma * sqrt(2*3.141592653589793238)); + } + if (hist_flags & ImPlotHistogramFlags_Cumulative) { + for (int i = 1; i < 100; ++i) + y[i] += y[i-1]; + for (int i = 0; i < 100; ++i) + y[i] /= y[99]; + } + } + + if (ImPlot::BeginPlot("##Histograms")) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); + ImPlot::PlotHistogram("Empirical", dist.Data, 10000, bins, 1.0, range ? ImPlotRange(rmin,rmax) : ImPlotRange(), hist_flags); + if ((hist_flags & ImPlotHistogramFlags_Density) && !(hist_flags & ImPlotHistogramFlags_NoOutliers)) { + if (hist_flags & ImPlotHistogramFlags_Horizontal) + ImPlot::PlotLine("Theoretical",y,x,100); + else + ImPlot::PlotLine("Theoretical",x,y,100); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Histogram2D() { + static int count = 50000; + static int xybins[2] = {100,100}; + + static ImPlotHistogramFlags hist_flags = 0; + + ImGui::SliderInt("Count",&count,100,100000); + ImGui::SliderInt2("Bins",xybins,1,500); + ImGui::SameLine(); + ImGui::CheckboxFlags("Density", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Density); + + static NormalDistribution<100000> dist1(1, 2); + static NormalDistribution<100000> dist2(1, 1); + double max_count = 0; + ImPlotAxisFlags flags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Foreground; + ImPlot::PushColormap("Hot"); + if (ImPlot::BeginPlot("##Hist2D",ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxesLimits(-6,6,-6,6); + max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],ImPlotRect(-6,6,-6,6), hist_flags); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale(hist_flags & ImPlotHistogramFlags_Density ? "Density" : "Count",0,max_count,ImVec2(100,0)); + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_DigitalPlots() { + ImGui::BulletText("Digital plots do not respond to Y drag and zoom, so that"); + ImGui::Indent(); + ImGui::Text("you can drag analog plots over the rising/falling digital edge."); + ImGui::Unindent(); + + static bool paused = false; + static ScrollingBuffer dataDigital[2]; + static ScrollingBuffer dataAnalog[2]; + static bool showDigital[2] = {true, false}; + static bool showAnalog[2] = {true, false}; + + char label[32]; + ImGui::Checkbox("digital_0", &showDigital[0]); ImGui::SameLine(); + ImGui::Checkbox("digital_1", &showDigital[1]); ImGui::SameLine(); + ImGui::Checkbox("analog_0", &showAnalog[0]); ImGui::SameLine(); + ImGui::Checkbox("analog_1", &showAnalog[1]); + + static float t = 0; + if (!paused) { + t += ImGui::GetIO().DeltaTime; + //digital signal values + if (showDigital[0]) + dataDigital[0].AddPoint(t, sinf(2*t) > 0.45); + if (showDigital[1]) + dataDigital[1].AddPoint(t, sinf(2*t) < 0.45); + //Analog signal values + if (showAnalog[0]) + dataAnalog[0].AddPoint(t, sinf(2*t)); + if (showAnalog[1]) + dataAnalog[1].AddPoint(t, cosf(2*t)); + } + if (ImPlot::BeginPlot("##Digital")) { + ImPlot::SetupAxisLimits(ImAxis_X1, t - 10.0, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1, -1, 1); + for (int i = 0; i < 2; ++i) { + if (showDigital[i] && dataDigital[i].Data.size() > 0) { + snprintf(label, sizeof(label), "digital_%d", i); + ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), 0, dataDigital[i].Offset, 2 * sizeof(float)); + } + } + for (int i = 0; i < 2; ++i) { + if (showAnalog[i]) { + snprintf(label, sizeof(label), "analog_%d", i); + if (dataAnalog[i].Data.size() > 0) + ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), 0, dataAnalog[i].Offset, 2 * sizeof(float)); + } + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Images() { + ImGui::BulletText("Below we are displaying the font texture, which is the only texture we have\naccess to in this demo."); + ImGui::BulletText("Use the 'ImTextureID' type as storage to pass pointers or identifiers to your\nown texture data."); + ImGui::BulletText("See ImGui Wiki page 'Image Loading and Displaying Examples'."); + static ImVec2 bmin(0,0); + static ImVec2 bmax(1,1); + static ImVec2 uv0(0,0); + static ImVec2 uv1(1,1); + static ImVec4 tint(1,1,1,1); + ImGui::SliderFloat2("Min", &bmin.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("Max", &bmax.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("UV0", &uv0.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("UV1", &uv1.x, -2, 2, "%.1f"); + ImGui::ColorEdit4("Tint",&tint.x); + if (ImPlot::BeginPlot("##image")) { + ImPlot::PlotImage("my image",ImGui::GetIO().Fonts->TexID, bmin, bmax, uv0, uv1, tint); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_RealtimePlots() { + ImGui::BulletText("Move your mouse to change the data!"); + ImGui::BulletText("This example assumes 60 FPS. Higher FPS requires larger buffer size."); + static ScrollingBuffer sdata1, sdata2; + static RollingBuffer rdata1, rdata2; + ImVec2 mouse = ImGui::GetMousePos(); + static float t = 0; + t += ImGui::GetIO().DeltaTime; + sdata1.AddPoint(t, mouse.x * 0.0005f); + rdata1.AddPoint(t, mouse.x * 0.0005f); + sdata2.AddPoint(t, mouse.y * 0.0005f); + rdata2.AddPoint(t, mouse.y * 0.0005f); + + static float history = 10.0f; + ImGui::SliderFloat("History",&history,1,30,"%.1f s"); + rdata1.Span = history; + rdata2.Span = history; + + static ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels; + + if (ImPlot::BeginPlot("##Scrolling", ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxisLimits(ImAxis_X1,t - history, t, ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); + ImPlot::PlotShaded("Mouse X", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), -INFINITY, 0, sdata1.Offset, 2 * sizeof(float)); + ImPlot::PlotLine("Mouse Y", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), 0, sdata2.Offset, 2*sizeof(float)); + ImPlot::EndPlot(); + } + if (ImPlot::BeginPlot("##Rolling", ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxisLimits(ImAxis_X1,0,history, ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); + ImPlot::PlotLine("Mouse X", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), 0, 0, 2 * sizeof(float)); + ImPlot::PlotLine("Mouse Y", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), 0, 0, 2 * sizeof(float)); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_MarkersAndText() { + static float mk_size = ImPlot::GetStyle().MarkerSize; + static float mk_weight = ImPlot::GetStyle().MarkerWeight; + ImGui::DragFloat("Marker Size",&mk_size,0.1f,2.0f,10.0f,"%.2f px"); + ImGui::DragFloat("Marker Weight", &mk_weight,0.05f,0.5f,3.0f,"%.2f px"); + + if (ImPlot::BeginPlot("##MarkerStyles", ImVec2(-1,0), ImPlotFlags_CanvasOnly)) { + + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 10, 0, 12); + + ImS8 xs[2] = {1,4}; + ImS8 ys[2] = {10,11}; + + // filled markers + for (int m = 0; m < ImPlotMarker_COUNT; ++m) { + ImGui::PushID(m); + ImPlot::SetNextMarkerStyle(m, mk_size, IMPLOT_AUTO_COL, mk_weight); + ImPlot::PlotLine("##Filled", xs, ys, 2); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + xs[0] = 6; xs[1] = 9; ys[0] = 10; ys[1] = 11; + // open markers + for (int m = 0; m < ImPlotMarker_COUNT; ++m) { + ImGui::PushID(m); + ImPlot::SetNextMarkerStyle(m, mk_size, ImVec4(0,0,0,0), mk_weight); + ImPlot::PlotLine("##Open", xs, ys, 2); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + + ImPlot::PlotText("Filled Markers", 2.5f, 6.0f); + ImPlot::PlotText("Open Markers", 7.5f, 6.0f); + + ImPlot::PushStyleColor(ImPlotCol_InlayText, ImVec4(1,0,1,1)); + ImPlot::PlotText("Vertical Text", 5.0f, 6.0f, ImVec2(0,0), ImPlotTextFlags_Vertical); + ImPlot::PopStyleColor(); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_NaNValues() { + + static bool include_nan = true; + static ImPlotLineFlags flags = 0; + + float data1[5] = {0.0f,0.25f,0.5f,0.75f,1.0f}; + float data2[5] = {0.0f,0.25f,0.5f,0.75f,1.0f}; + + if (include_nan) + data1[2] = NAN; + + ImGui::Checkbox("Include NaN",&include_nan); + ImGui::SameLine(); + ImGui::CheckboxFlags("Skip NaN", (unsigned int*)&flags, ImPlotLineFlags_SkipNaN); + + if (ImPlot::BeginPlot("##NaNValues")) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::PlotLine("line", data1, data2, 5, flags); + ImPlot::PlotBars("bars", data1, 5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LogScale() { + static double xs[1001], ys1[1001], ys2[1001], ys3[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = i*0.1f; + ys1[i] = sin(xs[i]) + 1; + ys2[i] = log(xs[i]); + ys3[i] = pow(10.0, xs[i]); + } + if (ImPlot::BeginPlot("Log Plot", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Log10); + ImPlot::SetupAxesLimits(0.1, 100, 0, 10); + ImPlot::PlotLine("f(x) = x", xs, xs, 1001); + ImPlot::PlotLine("f(x) = sin(x)+1", xs, ys1, 1001); + ImPlot::PlotLine("f(x) = log(x)", xs, ys2, 1001); + ImPlot::PlotLine("f(x) = 10^x", xs, ys3, 21); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SymmetricLogScale() { + static double xs[1001], ys1[1001], ys2[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = i*0.1f-50; + ys1[i] = sin(xs[i]); + ys2[i] = i*0.002 - 1; + } + if (ImPlot::BeginPlot("SymLog Plot", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_SymLog); + ImPlot::PlotLine("f(x) = a*x+b",xs,ys2,1001); + ImPlot::PlotLine("f(x) = sin(x)",xs,ys1,1001); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_TimeScale() { + + static double t_min = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) + static double t_max = 1640995200; // 01/01/2022 @ 12:00:00am (UTC) + + ImGui::BulletText("When ImPlotAxisFlags_Time is enabled on the X-Axis, values are interpreted as\n" + "UNIX timestamps in seconds and axis labels are formated as date/time."); + ImGui::BulletText("By default, labels are in UTC time but can be set to use local time instead."); + + ImGui::Checkbox("Local Time",&ImPlot::GetStyle().UseLocalTime); + ImGui::SameLine(); + ImGui::Checkbox("ISO 8601",&ImPlot::GetStyle().UseISO8601); + ImGui::SameLine(); + ImGui::Checkbox("24 Hour Clock",&ImPlot::GetStyle().Use24HourClock); + + static HugeTimeData* data = nullptr; + if (data == nullptr) { + ImGui::SameLine(); + if (ImGui::Button("Generate Huge Data (~500MB!)")) { + static HugeTimeData sdata(t_min); + data = &sdata; + } + } + + if (ImPlot::BeginPlot("##Time", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxesLimits(t_min,t_max,0,1); + if (data != nullptr) { + // downsample our data + int downsample = (int)ImPlot::GetPlotLimits().X.Size() / 1000 + 1; + int start = (int)(ImPlot::GetPlotLimits().X.Min - t_min); + start = start < 0 ? 0 : start > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : start; + int end = (int)(ImPlot::GetPlotLimits().X.Max - t_min) + 1000; + end = end < 0 ? 0 : end > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : end; + int size = (end - start)/downsample; + // plot it + ImPlot::PlotLine("Time Series", &data->Ts[start], &data->Ys[start], size, 0, 0, sizeof(double)*downsample); + } + // plot time now + double t_now = (double)time(nullptr); + double y_now = HugeTimeData::GetY(t_now); + ImPlot::PlotScatter("Now",&t_now,&y_now,1); + ImPlot::Annotation(t_now,y_now,ImPlot::GetLastItemColor(),ImVec2(10,10),false,"Now"); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +static inline double TransformForward_Sqrt(double v, void*) { + return sqrt(v); +} + +static inline double TransformInverse_Sqrt(double v, void*) { + return v*v; +} + +void Demo_CustomScale() { + static float v[100]; + for (int i = 0; i < 100; ++i) { + v[i] = i*0.01f; + } + if (ImPlot::BeginPlot("Sqrt")) { + ImPlot::SetupAxis(ImAxis_X1, "Linear"); + ImPlot::SetupAxis(ImAxis_Y1, "Sqrt"); + ImPlot::SetupAxisScale(ImAxis_Y1, TransformForward_Sqrt, TransformInverse_Sqrt); + ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, INFINITY); + ImPlot::PlotLine("##data",v,v,100); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_MultipleAxes() { + static float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = (i*0.1f); + xs2[i] = xs[i] + 10.0f; + ys1[i] = sinf(xs[i]) * 3 + 1; + ys2[i] = cosf(xs[i]) * 0.2f + 0.5f; + ys3[i] = sinf(xs[i]+0.5f) * 100 + 200; + } + + static bool x2_axis = true; + static bool y2_axis = true; + static bool y3_axis = true; + + ImGui::Checkbox("X-Axis 2", &x2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y-Axis 2", &y2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y-Axis 3", &y3_axis); + + ImGui::BulletText("You can drag axes to the opposite side of the plot."); + ImGui::BulletText("Hover over legend items to see which axis they are plotted on."); + + if (ImPlot::BeginPlot("Multi-Axis Plot", ImVec2(-1,0))) { + ImPlot::SetupAxes("X-Axis 1", "Y-Axis 1"); + ImPlot::SetupAxesLimits(0, 100, 0, 10); + if (x2_axis) { + ImPlot::SetupAxis(ImAxis_X2, "X-Axis 2",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_X2, 0, 100); + } + if (y2_axis) { + ImPlot::SetupAxis(ImAxis_Y2, "Y-Axis 2",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_Y2, 0, 1); + } + if (y3_axis) { + ImPlot::SetupAxis(ImAxis_Y3, "Y-Axis 3",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_Y3, 0, 300); + } + + ImPlot::PlotLine("f(x) = x", xs, xs, 1001); + if (x2_axis) { + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y1); + ImPlot::PlotLine("f(x) = sin(x)*3+1", xs2, ys1, 1001); + } + if (y2_axis) { + ImPlot::SetAxes(ImAxis_X1, ImAxis_Y2); + ImPlot::PlotLine("f(x) = cos(x)*.2+.5", xs, ys2, 1001); + } + if (y3_axis) { + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y3); + ImPlot::PlotLine("f(x) = sin(x+.5)*100+200 ", xs2, ys3, 1001); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LinkedAxes() { + static ImPlotRect lims(0,1,0,1); + static bool linkx = true, linky = true; + int data[2] = {0,1}; + ImGui::Checkbox("Link X", &linkx); + ImGui::SameLine(); + ImGui::Checkbox("Link Y", &linky); + + ImGui::DragScalarN("Limits",ImGuiDataType_Double,&lims.X.Min,4,0.01f); + + if (BeginAlignedPlots("AlignedGroup")) { + if (ImPlot::BeginPlot("Plot A")) { + ImPlot::SetupAxisLinks(ImAxis_X1, linkx ? &lims.X.Min : nullptr, linkx ? &lims.X.Max : nullptr); + ImPlot::SetupAxisLinks(ImAxis_Y1, linky ? &lims.Y.Min : nullptr, linky ? &lims.Y.Max : nullptr); + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + if (ImPlot::BeginPlot("Plot B")) { + ImPlot::SetupAxisLinks(ImAxis_X1, linkx ? &lims.X.Min : nullptr, linkx ? &lims.X.Max : nullptr); + ImPlot::SetupAxisLinks(ImAxis_Y1, linky ? &lims.Y.Min : nullptr, linky ? &lims.Y.Max : nullptr); + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + ImPlot::EndAlignedPlots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_AxisConstraints() { + static float constraints[4] = {-10,10,1,20}; + static ImPlotAxisFlags flags; + ImGui::DragFloat2("Limits Constraints", &constraints[0], 0.01f); + ImGui::DragFloat2("Zoom Constraints", &constraints[2], 0.01f); + CHECKBOX_FLAG(flags, ImPlotAxisFlags_PanStretch); + if (ImPlot::BeginPlot("##AxisConstraints",ImVec2(-1,0))) { + ImPlot::SetupAxes("X","Y",flags,flags); + ImPlot::SetupAxesLimits(-1,1,-1,1); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1,constraints[0], constraints[1]); + ImPlot::SetupAxisZoomConstraints(ImAxis_X1,constraints[2], constraints[3]); + ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1,constraints[0], constraints[1]); + ImPlot::SetupAxisZoomConstraints(ImAxis_Y1,constraints[2], constraints[3]); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_EqualAxes() { + ImGui::BulletText("Equal constraint applies to axis pairs (e.g ImAxis_X1/Y1, ImAxis_X2/Y2)"); + static double xs1[360], ys1[360]; + for (int i = 0; i < 360; ++i) { + double angle = i * 2 * PI / 359.0; + xs1[i] = cos(angle); ys1[i] = sin(angle); + } + float xs2[] = {-1,0,1,0,-1}; + float ys2[] = {0,1,0,-1,0}; + if (ImPlot::BeginPlot("##EqualAxes",ImVec2(-1,0),ImPlotFlags_Equal)) { + ImPlot::SetupAxis(ImAxis_X2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxis(ImAxis_Y2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::PlotLine("Circle",xs1,ys1,360); + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y2); + ImPlot::PlotLine("Diamond",xs2,ys2,5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_AutoFittingData() { + ImGui::BulletText("The Y-axis has been configured to auto-fit to only the data visible in X-axis range."); + ImGui::BulletText("Zoom and pan the X-axis. Disable Stems to see a difference in fit."); + ImGui::BulletText("If ImPlotAxisFlags_RangeFit is disabled, the axis will fit ALL data."); + + static ImPlotAxisFlags xflags = ImPlotAxisFlags_None; + static ImPlotAxisFlags yflags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_RangeFit; + + ImGui::TextUnformatted("X: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_RangeFit); + + ImGui::TextUnformatted("Y: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_RangeFit); + + static double data[101]; + srand(0); + for (int i = 0; i < 101; ++i) + data[i] = 1 + sin(i/10.0f); + + if (ImPlot::BeginPlot("##DataFitting")) { + ImPlot::SetupAxes("X","Y",xflags,yflags); + ImPlot::PlotLine("Line",data,101); + ImPlot::PlotStems("Stems",data,101); + ImPlot::EndPlot(); + }; +} + +//----------------------------------------------------------------------------- + +ImPlotPoint SinewaveGetter(int i, void* data) { + float f = *(float*)data; + return ImPlotPoint(i,sinf(f*i)); +} + +void Demo_SubplotsSizing() { + + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_None; + ImGui::CheckboxFlags("ImPlotSubplotFlags_NoResize", (unsigned int*)&flags, ImPlotSubplotFlags_NoResize); + ImGui::CheckboxFlags("ImPlotSubplotFlags_NoTitle", (unsigned int*)&flags, ImPlotSubplotFlags_NoTitle); + + static int rows = 3; + static int cols = 3; + ImGui::SliderInt("Rows",&rows,1,5); + ImGui::SliderInt("Cols",&cols,1,5); + static float rratios[] = {5,1,1,1,1,1}; + static float cratios[] = {5,1,1,1,1,1}; + ImGui::DragScalarN("Row Ratios",ImGuiDataType_Float,rratios,rows,0.01f,nullptr); + ImGui::DragScalarN("Col Ratios",ImGuiDataType_Float,cratios,cols,0.01f,nullptr); + if (ImPlot::BeginSubplots("My Subplots", rows, cols, ImVec2(-1,400), flags, rratios, cratios)) { + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("",ImVec2(),ImPlotFlags_NoLegend)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + float fi = 0.01f * (i+1); + ImPlot::SetNextLineStyle(SampleColormap((float)i/(float)(rows*cols-1),ImPlotColormap_Jet)); + ImPlot::PlotLineG("data",SinewaveGetter,&fi,1000); + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SubplotItemSharing() { + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_ShareItems; + ImGui::CheckboxFlags("ImPlotSubplotFlags_ShareItems", (unsigned int*)&flags, ImPlotSubplotFlags_ShareItems); + ImGui::CheckboxFlags("ImPlotSubplotFlags_ColMajor", (unsigned int*)&flags, ImPlotSubplotFlags_ColMajor); + ImGui::BulletText("Drag and drop items from the legend onto plots (except for 'common')"); + static int rows = 2; + static int cols = 3; + static int id[] = {0,1,2,3,4,5}; + static int curj = -1; + if (ImPlot::BeginSubplots("##ItemSharing", rows, cols, ImVec2(-1,400), flags)) { + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("")) { + float fc = 0.01f; + ImPlot::PlotLineG("common",SinewaveGetter,&fc,1000); + for (int j = 0; j < 6; ++j) { + if (id[j] == i) { + char label[8]; + float fj = 0.01f * (j+2); + snprintf(label, sizeof(label), "data%d", j); + ImPlot::PlotLineG(label,SinewaveGetter,&fj,1000); + if (ImPlot::BeginDragDropSourceItem(label)) { + curj = j; + ImGui::SetDragDropPayload("MY_DND",nullptr,0); + ImPlot::ItemIcon(GetLastItemColor()); ImGui::SameLine(); + ImGui::TextUnformatted(label); + ImPlot::EndDragDropSource(); + } + } + } + if (ImPlot::BeginDragDropTargetPlot()) { + if (ImGui::AcceptDragDropPayload("MY_DND")) + id[curj] = i; + ImPlot::EndDragDropTarget(); + } + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SubplotAxisLinking() { + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_LinkRows | ImPlotSubplotFlags_LinkCols; + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkRows", (unsigned int*)&flags, ImPlotSubplotFlags_LinkRows); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkCols", (unsigned int*)&flags, ImPlotSubplotFlags_LinkCols); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkAllX", (unsigned int*)&flags, ImPlotSubplotFlags_LinkAllX); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkAllY", (unsigned int*)&flags, ImPlotSubplotFlags_LinkAllY); + + static int rows = 2; + static int cols = 2; + if (ImPlot::BeginSubplots("##AxisLinking", rows, cols, ImVec2(-1,400), flags)) { + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("")) { + ImPlot::SetupAxesLimits(0,1000,-1,1); + float fc = 0.01f; + ImPlot::PlotLineG("common",SinewaveGetter,&fc,1000); + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LegendOptions() { + static ImPlotLocation loc = ImPlotLocation_East; + ImGui::CheckboxFlags("North", (unsigned int*)&loc, ImPlotLocation_North); ImGui::SameLine(); + ImGui::CheckboxFlags("South", (unsigned int*)&loc, ImPlotLocation_South); ImGui::SameLine(); + ImGui::CheckboxFlags("West", (unsigned int*)&loc, ImPlotLocation_West); ImGui::SameLine(); + ImGui::CheckboxFlags("East", (unsigned int*)&loc, ImPlotLocation_East); + + static ImPlotLegendFlags flags = 0; + + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Horizontal); + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Outside); + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Sort); + + ImGui::SliderFloat2("LegendPadding", (float*)&GetStyle().LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&GetStyle().LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&GetStyle().LegendSpacing, 0.0f, 5.0f, "%.0f"); + + if (ImPlot::BeginPlot("##Legend",ImVec2(-1,0))) { + ImPlot::SetupLegend(loc, flags); + static MyImPlot::WaveData data1(0.001, 0.2, 4, 0.2); + static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.4); + static MyImPlot::WaveData data3(0.001, 0.2, 4, 0.6); + static MyImPlot::WaveData data4(0.001, 0.2, 4, 0.8); + static MyImPlot::WaveData data5(0.001, 0.2, 4, 1.0); + + ImPlot::PlotLineG("Item B", MyImPlot::SawWave, &data1, 1000); // "Item B" added to legend + ImPlot::PlotLineG("Item A##IDText", MyImPlot::SawWave, &data2, 1000); // "Item A" added to legend, text after ## used for ID only + ImPlot::PlotLineG("##NotListed", MyImPlot::SawWave, &data3, 1000); // plotted, but not added to legend + ImPlot::PlotLineG("Item C", MyImPlot::SawWave, &data4, 1000); // "Item C" added to legend + ImPlot::PlotLineG("Item C", MyImPlot::SawWave, &data5, 1000); // combined with previous "Item C" + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragPoints() { + ImGui::BulletText("Click and drag each point."); + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + ImPlotAxisFlags ax_flags = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks; + if (ImPlot::BeginPlot("##Bezier",ImVec2(-1,0),ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(nullptr,nullptr,ax_flags,ax_flags); + ImPlot::SetupAxesLimits(0,1,0,1); + static ImPlotPoint P[] = {ImPlotPoint(.05f,.05f), ImPlotPoint(0.2,0.4), ImPlotPoint(0.8,0.6), ImPlotPoint(.95f,.95f)}; + + ImPlot::DragPoint(0,&P[0].x,&P[0].y, ImVec4(0,0.9f,0,1),4,flags); + ImPlot::DragPoint(1,&P[1].x,&P[1].y, ImVec4(1,0.5f,1,1),4,flags); + ImPlot::DragPoint(2,&P[2].x,&P[2].y, ImVec4(0,0.5f,1,1),4,flags); + ImPlot::DragPoint(3,&P[3].x,&P[3].y, ImVec4(0,0.9f,0,1),4,flags); + + static ImPlotPoint B[100]; + for (int i = 0; i < 100; ++i) { + double t = i / 99.0; + double u = 1 - t; + double w1 = u*u*u; + double w2 = 3*u*u*t; + double w3 = 3*u*t*t; + double w4 = t*t*t; + B[i] = ImPlotPoint(w1*P[0].x + w2*P[1].x + w3*P[2].x + w4*P[3].x, w1*P[0].y + w2*P[1].y + w3*P[2].y + w4*P[3].y); + } + + + ImPlot::SetNextLineStyle(ImVec4(1,0.5f,1,1)); + ImPlot::PlotLine("##h1",&P[0].x, &P[0].y, 2, 0, 0, sizeof(ImPlotPoint)); + ImPlot::SetNextLineStyle(ImVec4(0,0.5f,1,1)); + ImPlot::PlotLine("##h2",&P[2].x, &P[2].y, 2, 0, 0, sizeof(ImPlotPoint)); + ImPlot::SetNextLineStyle(ImVec4(0,0.9f,0,1), 2); + ImPlot::PlotLine("##bez",&B[0].x, &B[0].y, 100, 0, 0, sizeof(ImPlotPoint)); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragLines() { + ImGui::BulletText("Click and drag the horizontal and vertical lines."); + static double x1 = 0.2; + static double x2 = 0.8; + static double y1 = 0.25; + static double y2 = 0.75; + static double f = 0.1; + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + if (ImPlot::BeginPlot("##lines",ImVec2(-1,0))) { + ImPlot::SetupAxesLimits(0,1,0,1); + ImPlot::DragLineX(0,&x1,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineX(1,&x2,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineY(2,&y1,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineY(3,&y2,ImVec4(1,1,1,1),1,flags); + double xs[1000], ys[1000]; + for (int i = 0; i < 1000; ++i) { + xs[i] = (x2+x1)/2+fabs(x2-x1)*(i/1000.0f - 0.5f); + ys[i] = (y1+y2)/2+fabs(y2-y1)/2*sin(f*i/10); + } + ImPlot::PlotLine("Interactive Data", xs, ys, 1000); + ImPlot::DragLineY(120482,&f,ImVec4(1,0.5f,1,1),1,flags); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragRects() { + + static float x_data[512]; + static float y_data1[512]; + static float y_data2[512]; + static float y_data3[512]; + static float sampling_freq = 44100; + static float freq = 500; + for (size_t i = 0; i < 512; ++i) { + const float t = i / sampling_freq; + x_data[i] = t; + const float arg = 2 * 3.14f * freq * t; + y_data1[i] = sinf(arg); + y_data2[i] = y_data1[i] * -0.6f + sinf(2 * arg) * 0.4f; + y_data3[i] = y_data2[i] * -0.6f + sinf(3 * arg) * 0.4f; + } + ImGui::BulletText("Click and drag the edges, corners, and center of the rect."); + static ImPlotRect rect(0.0025,0.0045,0,0.5); + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + + if (ImPlot::BeginPlot("##Main",ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoTickLabels,ImPlotAxisFlags_NoTickLabels); + ImPlot::SetupAxesLimits(0,0.01,-1,1); + ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); + ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); + ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); + ImPlot::DragRect(0,&rect.X.Min,&rect.Y.Min,&rect.X.Max,&rect.Y.Max,ImVec4(1,0,1,1),flags); + ImPlot::EndPlot(); + } + if (ImPlot::BeginPlot("##rect",ImVec2(-1,150), ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(rect.X.Min, rect.X.Max, rect.Y.Min, rect.Y.Max, ImGuiCond_Always); + ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); + ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); + ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +ImPlotPoint FindCentroid(const ImVector& data, const ImPlotRect& bounds, int& cnt) { + cnt = 0; + ImPlotPoint avg; + ImPlotRect bounds_fixed; + bounds_fixed.X.Min = bounds.X.Min < bounds.X.Max ? bounds.X.Min : bounds.X.Max; + bounds_fixed.X.Max = bounds.X.Min < bounds.X.Max ? bounds.X.Max : bounds.X.Min; + bounds_fixed.Y.Min = bounds.Y.Min < bounds.Y.Max ? bounds.Y.Min : bounds.Y.Max; + bounds_fixed.Y.Max = bounds.Y.Min < bounds.Y.Max ? bounds.Y.Max : bounds.Y.Min; + for (int i = 0; i < data.size(); ++i) { + if (bounds_fixed.Contains(data[i].x, data[i].y)) { + avg.x += data[i].x; + avg.y += data[i].y; + cnt++; + } + } + if (cnt > 0) { + avg.x = avg.x / cnt; + avg.y = avg.y / cnt; + } + return avg; +} + +//----------------------------------------------------------------------------- + +void Demo_Querying() { + static ImVector data; + static ImVector rects; + static ImPlotRect limits, select; + static bool init = true; + if (init) { + for (int i = 0; i < 50; ++i) + { + double x = RandomRange(0.1, 0.9); + double y = RandomRange(0.1, 0.9); + data.push_back(ImPlotPoint(x,y)); + } + init = false; + } + + ImGui::BulletText("Box select and left click mouse to create a new query rect."); + ImGui::BulletText("Ctrl + click in the plot area to draw points."); + + if (ImGui::Button("Clear Queries")) + rects.shrink(0); + + if (ImPlot::BeginPlot("##Centroid")) { + ImPlot::SetupAxesLimits(0,1,0,1); + if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) { + ImPlotPoint pt = ImPlot::GetPlotMousePos(); + data.push_back(pt); + } + ImPlot::PlotScatter("Points", &data[0].x, &data[0].y, data.size(), 0, 0, 2 * sizeof(double)); + if (ImPlot::IsPlotSelected()) { + select = ImPlot::GetPlotSelection(); + int cnt; + ImPlotPoint centroid = FindCentroid(data,select,cnt); + if (cnt > 0) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + } + if (ImGui::IsMouseClicked(ImPlot::GetInputMap().SelectCancel)) { + CancelPlotSelection(); + rects.push_back(select); + } + } + for (int i = 0; i < rects.size(); ++i) { + int cnt; + ImPlotPoint centroid = FindCentroid(data,rects[i],cnt); + if (cnt > 0) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + } + ImPlot::DragRect(i,&rects[i].X.Min,&rects[i].Y.Min,&rects[i].X.Max,&rects[i].Y.Max,ImVec4(1,0,1,1)); + } + limits = ImPlot::GetPlotLimits(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Annotations() { + static bool clamp = false; + ImGui::Checkbox("Clamp",&clamp); + if (ImPlot::BeginPlot("##Annotations")) { + ImPlot::SetupAxesLimits(0,2,0,1); + static float p[] = {0.25f, 0.25f, 0.75f, 0.75f, 0.25f}; + ImPlot::PlotScatter("##Points",&p[0],&p[1],4); + ImVec4 col = GetLastItemColor(); + ImPlot::Annotation(0.25,0.25,col,ImVec2(-15,15),clamp,"BL"); + ImPlot::Annotation(0.75,0.25,col,ImVec2(15,15),clamp,"BR"); + ImPlot::Annotation(0.75,0.75,col,ImVec2(15,-15),clamp,"TR"); + ImPlot::Annotation(0.25,0.75,col,ImVec2(-15,-15),clamp,"TL"); + ImPlot::Annotation(0.5,0.5,col,ImVec2(0,0),clamp,"Center"); + + ImPlot::Annotation(1.25,0.75,ImVec4(0,1,0,1),ImVec2(0,0),clamp); + + float bx[] = {1.2f,1.5f,1.8f}; + float by[] = {0.25f, 0.5f, 0.75f}; + ImPlot::PlotBars("##Bars",bx,by,3,0.2); + for (int i = 0; i < 3; ++i) + ImPlot::Annotation(bx[i],by[i],ImVec4(0,0,0,0),ImVec2(0,-5),clamp,"B[%d]=%.2f",i,by[i]); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Tags() { + static bool show = true; + ImGui::Checkbox("Show Tags",&show); + if (ImPlot::BeginPlot("##Tags")) { + ImPlot::SetupAxis(ImAxis_X2); + ImPlot::SetupAxis(ImAxis_Y2); + if (show) { + ImPlot::TagX(0.25, ImVec4(1,1,0,1)); + ImPlot::TagY(0.75, ImVec4(1,1,0,1)); + static double drag_tag = 0.25; + ImPlot::DragLineY(0,&drag_tag,ImVec4(1,0,0,1),1,ImPlotDragToolFlags_NoFit); + ImPlot::TagY(drag_tag, ImVec4(1,0,0,1), "Drag"); + SetAxes(ImAxis_X2, ImAxis_Y2); + ImPlot::TagX(0.5, ImVec4(0,1,1,1), "%s", "MyTag"); + ImPlot::TagY(0.5, ImVec4(0,1,1,1), "Tag: %d", 42); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragAndDrop() { + ImGui::BulletText("Drag/drop items from the left column."); + ImGui::BulletText("Drag/drop items between plots."); + ImGui::Indent(); + ImGui::BulletText("Plot 1 Targets: Plot, Y-Axes, Legend"); + ImGui::BulletText("Plot 1 Sources: Legend Item Labels"); + ImGui::BulletText("Plot 2 Targets: Plot, X-Axis, Y-Axis"); + ImGui::BulletText("Plot 2 Sources: Plot, X-Axis, Y-Axis (hold Ctrl)"); + ImGui::Unindent(); + + // convenience struct to manage DND items; do this however you like + struct MyDndItem { + int Idx; + int Plt; + ImAxis Yax; + char Label[16]; + ImVector Data; + ImVec4 Color; + MyDndItem() { + static int i = 0; + Idx = i++; + Plt = 0; + Yax = ImAxis_Y1; + snprintf(Label, sizeof(Label), "%02d Hz", Idx+1); + Color = RandomColor(); + Data.reserve(1001); + for (int k = 0; k < 1001; ++k) { + float t = k * 1.0f / 999; + Data.push_back(ImVec2(t, 0.5f + 0.5f * sinf(2*3.14f*t*(Idx+1)))); + } + } + void Reset() { Plt = 0; Yax = ImAxis_Y1; } + }; + + const int k_dnd = 20; + static MyDndItem dnd[k_dnd]; + static MyDndItem* dndx = nullptr; // for plot 2 + static MyDndItem* dndy = nullptr; // for plot 2 + + // child window to serve as initial source for our DND items + ImGui::BeginChild("DND_LEFT",ImVec2(100,400)); + if (ImGui::Button("Reset Data")) { + for (int k = 0; k < k_dnd; ++k) + dnd[k].Reset(); + dndx = dndy = nullptr; + } + for (int k = 0; k < k_dnd; ++k) { + if (dnd[k].Plt > 0) + continue; + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::Selectable(dnd[k].Label, false, 0, ImVec2(100, 0)); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("MY_DND", &k, sizeof(int)); + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::TextUnformatted(dnd[k].Label); + ImGui::EndDragDropSource(); + } + } + ImGui::EndChild(); + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Reset(); + } + ImGui::EndDragDropTarget(); + } + + ImGui::SameLine(); + ImGui::BeginChild("DND_RIGHT",ImVec2(-1,400)); + // plot 1 (time series) + ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoHighlight; + if (ImPlot::BeginPlot("##DND1", ImVec2(-1,195))) { + ImPlot::SetupAxis(ImAxis_X1, nullptr, flags|ImPlotAxisFlags_Lock); + ImPlot::SetupAxis(ImAxis_Y1, "[drop here]", flags); + ImPlot::SetupAxis(ImAxis_Y2, "[drop here]", flags|ImPlotAxisFlags_Opposite); + ImPlot::SetupAxis(ImAxis_Y3, "[drop here]", flags|ImPlotAxisFlags_Opposite); + + for (int k = 0; k < k_dnd; ++k) { + if (dnd[k].Plt == 1 && dnd[k].Data.size() > 0) { + ImPlot::SetAxis(dnd[k].Yax); + ImPlot::SetNextLineStyle(dnd[k].Color); + ImPlot::PlotLine(dnd[k].Label, &dnd[k].Data[0].x, &dnd[k].Data[0].y, dnd[k].Data.size(), 0, 0, 2 * sizeof(float)); + // allow legend item labels to be DND sources + if (ImPlot::BeginDragDropSourceItem(dnd[k].Label)) { + ImGui::SetDragDropPayload("MY_DND", &k, sizeof(int)); + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::TextUnformatted(dnd[k].Label); + ImPlot::EndDragDropSource(); + } + } + } + // allow the main plot area to be a DND target + if (ImPlot::BeginDragDropTargetPlot()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = ImAxis_Y1; + } + ImPlot::EndDragDropTarget(); + } + // allow each y-axis to be a DND target + for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) { + if (ImPlot::BeginDragDropTargetAxis(y)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = y; + } + ImPlot::EndDragDropTarget(); + } + } + // allow the legend to be a DND target + if (ImPlot::BeginDragDropTargetLegend()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = ImAxis_Y1; + } + ImPlot::EndDragDropTarget(); + } + ImPlot::EndPlot(); + } + // plot 2 (Lissajous) + if (ImPlot::BeginPlot("##DND2", ImVec2(-1,195))) { + ImPlot::PushStyleColor(ImPlotCol_AxisBg, dndx != nullptr ? dndx->Color : ImPlot::GetStyle().Colors[ImPlotCol_AxisBg]); + ImPlot::SetupAxis(ImAxis_X1, dndx == nullptr ? "[drop here]" : dndx->Label, flags); + ImPlot::PushStyleColor(ImPlotCol_AxisBg, dndy != nullptr ? dndy->Color : ImPlot::GetStyle().Colors[ImPlotCol_AxisBg]); + ImPlot::SetupAxis(ImAxis_Y1, dndy == nullptr ? "[drop here]" : dndy->Label, flags); + ImPlot::PopStyleColor(2); + if (dndx != nullptr && dndy != nullptr) { + ImVec4 mixed((dndx->Color.x + dndy->Color.x)/2,(dndx->Color.y + dndy->Color.y)/2,(dndx->Color.z + dndy->Color.z)/2,(dndx->Color.w + dndy->Color.w)/2); + ImPlot::SetNextLineStyle(mixed); + ImPlot::PlotLine("##dndxy", &dndx->Data[0].y, &dndy->Data[0].y, dndx->Data.size(), 0, 0, 2 * sizeof(float)); + } + // allow the x-axis to be a DND target + if (ImPlot::BeginDragDropTargetAxis(ImAxis_X1)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndx = &dnd[i]; + } + ImPlot::EndDragDropTarget(); + } + // allow the x-axis to be a DND source + if (dndx != nullptr && ImPlot::BeginDragDropSourceAxis(ImAxis_X1)) { + ImGui::SetDragDropPayload("MY_DND", &dndx->Idx, sizeof(int)); + ImPlot::ItemIcon(dndx->Color); ImGui::SameLine(); + ImGui::TextUnformatted(dndx->Label); + ImPlot::EndDragDropSource(); + } + // allow the y-axis to be a DND target + if (ImPlot::BeginDragDropTargetAxis(ImAxis_Y1)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndy = &dnd[i]; + } + ImPlot::EndDragDropTarget(); + } + // allow the y-axis to be a DND source + if (dndy != nullptr && ImPlot::BeginDragDropSourceAxis(ImAxis_Y1)) { + ImGui::SetDragDropPayload("MY_DND", &dndy->Idx, sizeof(int)); + ImPlot::ItemIcon(dndy->Color); ImGui::SameLine(); + ImGui::TextUnformatted(dndy->Label); + ImPlot::EndDragDropSource(); + } + // allow the plot area to be a DND target + if (ImPlot::BeginDragDropTargetPlot()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndx = dndy = &dnd[i]; + } + } + // allow the plot area to be a DND source + if (ImPlot::BeginDragDropSourcePlot()) { + ImGui::TextUnformatted("Yes, you can\ndrag this!"); + ImPlot::EndDragDropSource(); + } + ImPlot::EndPlot(); + } + ImGui::EndChild(); +} + +//----------------------------------------------------------------------------- + +void Demo_Tables() { +#ifdef IMGUI_HAS_TABLE + static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; + static bool anim = true; + static int offset = 0; + ImGui::BulletText("Plots can be used inside of ImGui tables as another means of creating subplots."); + ImGui::Checkbox("Animate",&anim); + if (anim) + offset = (offset + 1) % 100; + if (ImGui::BeginTable("##table", 3, flags, ImVec2(-1,0))) { + ImGui::TableSetupColumn("Electrode", ImGuiTableColumnFlags_WidthFixed, 75.0f); + ImGui::TableSetupColumn("Voltage", ImGuiTableColumnFlags_WidthFixed, 75.0f); + ImGui::TableSetupColumn("EMG Signal"); + ImGui::TableHeadersRow(); + ImPlot::PushColormap(ImPlotColormap_Cool); + for (int row = 0; row < 10; row++) { + ImGui::TableNextRow(); + static float data[100]; + srand(row); + for (int i = 0; i < 100; ++i) + data[i] = RandomRange(0.0f,10.0f); + ImGui::TableSetColumnIndex(0); + ImGui::Text("EMG %d", row); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%.3f V", data[offset]); + ImGui::TableSetColumnIndex(2); + ImGui::PushID(row); + MyImPlot::Sparkline("##spark",data,100,0,11.0f,offset,ImPlot::GetColormapColor(row),ImVec2(-1, 35)); + ImGui::PopID(); + } + ImPlot::PopColormap(); + ImGui::EndTable(); + } +#else + ImGui::BulletText("You need to merge the ImGui 'tables' branch for this section."); +#endif +} + +//----------------------------------------------------------------------------- + +void Demo_OffsetAndStride() { + static const int k_circles = 11; + static const int k_points_per = 50; + static const int k_size = 2 * k_points_per * k_circles; + static double interleaved_data[k_size]; + for (int p = 0; p < k_points_per; ++p) { + for (int c = 0; c < k_circles; ++c) { + double r = (double)c / (k_circles - 1) * 0.2 + 0.2; + interleaved_data[p*2*k_circles + 2*c + 0] = 0.5 + r * cos((double)p/k_points_per * 6.28); + interleaved_data[p*2*k_circles + 2*c + 1] = 0.5 + r * sin((double)p/k_points_per * 6.28); + } + } + static int offset = 0; + ImGui::BulletText("Offsetting is useful for realtime plots (see above) and circular buffers."); + ImGui::BulletText("Striding is useful for interleaved data (e.g. audio) or plotting structs."); + ImGui::BulletText("Here, all circle data is stored in a single interleaved buffer:"); + ImGui::BulletText("[c0.x0 c0.y0 ... cn.x0 cn.y0 c0.x1 c0.y1 ... cn.x1 cn.y1 ... cn.xm cn.ym]"); + ImGui::BulletText("The offset value indicates which circle point index is considered the first."); + ImGui::BulletText("Offsets can be negative and/or larger than the actual data count."); + ImGui::SliderInt("Offset", &offset, -2*k_points_per, 2*k_points_per); + if (ImPlot::BeginPlot("##strideoffset",ImVec2(-1,0),ImPlotFlags_Equal)) { + ImPlot::PushColormap(ImPlotColormap_Jet); + char buff[32]; + for (int c = 0; c < k_circles; ++c) { + snprintf(buff, sizeof(buff), "Circle %d", c); + ImPlot::PlotLine(buff, &interleaved_data[c*2 + 0], &interleaved_data[c*2 + 1], k_points_per, 0, offset, 2*k_circles*sizeof(double)); + } + ImPlot::EndPlot(); + ImPlot::PopColormap(); + } + // offset++; uncomment for animation! +} + +//----------------------------------------------------------------------------- + +void Demo_CustomDataAndGetters() { + ImGui::BulletText("You can plot custom structs using the stride feature."); + ImGui::BulletText("Most plotters can also be passed a function pointer for getting data."); + ImGui::Indent(); + ImGui::BulletText("You can optionally pass user data to be given to your getter function."); + ImGui::BulletText("C++ lambdas can be passed as function pointers as well!"); + ImGui::Unindent(); + + MyImPlot::Vector2f vec2_data[2] = { MyImPlot::Vector2f(0,0), MyImPlot::Vector2f(1,1) }; + + if (ImPlot::BeginPlot("##Custom Data")) { + + // custom structs using stride example: + ImPlot::PlotLine("Vector2f", &vec2_data[0].x, &vec2_data[0].y, 2, 0, 0, sizeof(MyImPlot::Vector2f) /* or sizeof(float) * 2 */); + + // custom getter example 1: + ImPlot::PlotLineG("Spiral", MyImPlot::Spiral, nullptr, 1000); + + // custom getter example 2: + static MyImPlot::WaveData data1(0.001, 0.2, 2, 0.75); + static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.25); + ImPlot::PlotLineG("Waves", MyImPlot::SineWave, &data1, 1000); + ImPlot::PlotLineG("Waves", MyImPlot::SawWave, &data2, 1000); + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::PlotShadedG("Waves", MyImPlot::SineWave, &data1, MyImPlot::SawWave, &data2, 1000); + ImPlot::PopStyleVar(); + + // you can also pass C++ lambdas: + // auto lamda = [](void* data, int idx) { ... return ImPlotPoint(x,y); }; + // ImPlot::PlotLine("My Lambda", lambda, data, 1000); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +int MetricFormatter(double value, char* buff, int size, void* data) { + const char* unit = (const char*)data; + static double v[] = {1000000000,1000000,1000,1,0.001,0.000001,0.000000001}; + static const char* p[] = {"G","M","k","","m","u","n"}; + if (value == 0) { + return snprintf(buff,size,"0 %s", unit); + } + for (int i = 0; i < 7; ++i) { + if (fabs(value) >= v[i]) { + return snprintf(buff,size,"%g %s%s",value/v[i],p[i],unit); + } + } + return snprintf(buff,size,"%g %s%s",value/v[6],p[6],unit); +} + +void Demo_TickLabels() { + static bool custom_fmt = true; + static bool custom_ticks = false; + static bool custom_labels = true; + ImGui::Checkbox("Show Custom Format", &custom_fmt); + ImGui::SameLine(); + ImGui::Checkbox("Show Custom Ticks", &custom_ticks); + if (custom_ticks) { + ImGui::SameLine(); + ImGui::Checkbox("Show Custom Labels", &custom_labels); + } + const double pi = 3.14; + const char* pi_str[] = {"PI"}; + static double yticks[] = {100,300,700,900}; + static const char* ylabels[] = {"One","Three","Seven","Nine"}; + static double yticks_aux[] = {0.2,0.4,0.6}; + static const char* ylabels_aux[] = {"A","B","C","D","E","F"}; + if (ImPlot::BeginPlot("##Ticks")) { + ImPlot::SetupAxesLimits(2.5,5,0,1000); + ImPlot::SetupAxis(ImAxis_Y2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxis(ImAxis_Y3, nullptr, ImPlotAxisFlags_AuxDefault); + if (custom_fmt) { + ImPlot::SetupAxisFormat(ImAxis_X1, "%g ms"); + ImPlot::SetupAxisFormat(ImAxis_Y1, MetricFormatter, (void*)"Hz"); + ImPlot::SetupAxisFormat(ImAxis_Y2, "%g dB"); + ImPlot::SetupAxisFormat(ImAxis_Y3, MetricFormatter, (void*)"m"); + } + if (custom_ticks) { + ImPlot::SetupAxisTicks(ImAxis_X1, &pi,1,custom_labels ? pi_str : nullptr, true); + ImPlot::SetupAxisTicks(ImAxis_Y1, yticks, 4, custom_labels ? ylabels : nullptr, false); + ImPlot::SetupAxisTicks(ImAxis_Y2, yticks_aux, 3, custom_labels ? ylabels_aux : nullptr, false); + ImPlot::SetupAxisTicks(ImAxis_Y3, 0, 1, 6, custom_labels ? ylabels_aux : nullptr, false); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_CustomStyles() { + ImPlot::PushColormap(ImPlotColormap_Deep); + // normally you wouldn't change the entire style each frame + ImPlotStyle backup = ImPlot::GetStyle(); + MyImPlot::StyleSeaborn(); + if (ImPlot::BeginPlot("seaborn style")) { + ImPlot::SetupAxes( "x-axis", "y-axis"); + ImPlot::SetupAxesLimits(-0.5f, 9.5f, 0, 10); + unsigned int lin[10] = {8,8,9,7,8,8,8,9,7,8}; + unsigned int bar[10] = {1,2,5,3,4,1,2,5,3,4}; + unsigned int dot[10] = {7,6,6,7,8,5,6,5,8,7}; + ImPlot::PlotBars("Bars", bar, 10, 0.5f); + ImPlot::PlotLine("Line", lin, 10); + ImPlot::NextColormapColor(); // skip green + ImPlot::PlotScatter("Scatter", dot, 10); + ImPlot::EndPlot(); + } + ImPlot::GetStyle() = backup; + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_CustomRendering() { + if (ImPlot::BeginPlot("##CustomRend")) { + ImVec2 cntr = ImPlot::PlotToPixels(ImPlotPoint(0.5f, 0.5f)); + ImVec2 rmin = ImPlot::PlotToPixels(ImPlotPoint(0.25f, 0.75f)); + ImVec2 rmax = ImPlot::PlotToPixels(ImPlotPoint(0.75f, 0.25f)); + ImPlot::PushPlotClipRect(); + ImPlot::GetPlotDrawList()->AddCircleFilled(cntr,20,IM_COL32(255,255,0,255),20); + ImPlot::GetPlotDrawList()->AddRect(rmin, rmax, IM_COL32(128,0,255,255)); + ImPlot::PopPlotClipRect(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LegendPopups() { + ImGui::BulletText("You can implement legend context menus to inject per-item controls and widgets."); + ImGui::BulletText("Right click the legend label/icon to edit custom item attributes."); + + static float frequency = 0.1f; + static float amplitude = 0.5f; + static ImVec4 color = ImVec4(1,1,0,1); + static float alpha = 1.0f; + static bool line = false; + static float thickness = 1; + static bool markers = false; + static bool shaded = false; + + static float vals[101]; + for (int i = 0; i < 101; ++i) + vals[i] = amplitude * sinf(frequency * i); + + if (ImPlot::BeginPlot("Right Click the Legend")) { + ImPlot::SetupAxesLimits(0,100,-1,1); + // rendering logic + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); + if (!line) { + ImPlot::SetNextFillStyle(color); + ImPlot::PlotBars("Right Click Me", vals, 101); + } + else { + if (markers) ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::SetNextLineStyle(color, thickness); + ImPlot::PlotLine("Right Click Me", vals, 101); + if (shaded) ImPlot::PlotShaded("Right Click Me",vals,101); + } + ImPlot::PopStyleVar(); + // custom legend context menu + if (ImPlot::BeginLegendPopup("Right Click Me")) { + ImGui::SliderFloat("Frequency",&frequency,0,1,"%0.2f"); + ImGui::SliderFloat("Amplitude",&litude,0,1,"%0.2f"); + ImGui::Separator(); + ImGui::ColorEdit3("Color",&color.x); + ImGui::SliderFloat("Transparency",&alpha,0,1,"%.2f"); + ImGui::Checkbox("Line Plot", &line); + if (line) { + ImGui::SliderFloat("Thickness", &thickness, 0, 5); + ImGui::Checkbox("Markers", &markers); + ImGui::Checkbox("Shaded",&shaded); + } + ImPlot::EndLegendPopup(); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ColormapWidgets() { + static int cmap = ImPlotColormap_Viridis; + + if (ImPlot::ColormapButton("Button",ImVec2(0,0),cmap)) { + cmap = (cmap + 1) % ImPlot::GetColormapCount(); + } + + static float t = 0.5f; + static ImVec4 col; + ImGui::ColorButton("##Display",col,ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); + ImPlot::ColormapSlider("Slider", &t, &col, "%.3f", cmap); + + ImPlot::ColormapIcon(cmap); ImGui::SameLine(); ImGui::Text("Icon"); + + static ImPlotColormapScaleFlags flags = 0; + static float scale[2] = {0, 100}; + ImPlot::ColormapScale("Scale",scale[0],scale[1],ImVec2(0,0),"%g dB",flags,cmap); + ImGui::InputFloat2("Scale",scale); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_NoLabel); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_Opposite); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_Invert); +} + +//----------------------------------------------------------------------------- + +void Demo_CustomPlottersAndTooltips() { + ImGui::BulletText("You can create custom plotters or extend ImPlot using implot_internal.h."); + double dates[] = {1546300800,1546387200,1546473600,1546560000,1546819200,1546905600,1546992000,1547078400,1547164800,1547424000,1547510400,1547596800,1547683200,1547769600,1547942400,1548028800,1548115200,1548201600,1548288000,1548374400,1548633600,1548720000,1548806400,1548892800,1548979200,1549238400,1549324800,1549411200,1549497600,1549584000,1549843200,1549929600,1550016000,1550102400,1550188800,1550361600,1550448000,1550534400,1550620800,1550707200,1550793600,1551052800,1551139200,1551225600,1551312000,1551398400,1551657600,1551744000,1551830400,1551916800,1552003200,1552262400,1552348800,1552435200,1552521600,1552608000,1552867200,1552953600,1553040000,1553126400,1553212800,1553472000,1553558400,1553644800,1553731200,1553817600,1554076800,1554163200,1554249600,1554336000,1554422400,1554681600,1554768000,1554854400,1554940800,1555027200,1555286400,1555372800,1555459200,1555545600,1555632000,1555891200,1555977600,1556064000,1556150400,1556236800,1556496000,1556582400,1556668800,1556755200,1556841600,1557100800,1557187200,1557273600,1557360000,1557446400,1557705600,1557792000,1557878400,1557964800,1558051200,1558310400,1558396800,1558483200,1558569600,1558656000,1558828800,1558915200,1559001600,1559088000,1559174400,1559260800,1559520000,1559606400,1559692800,1559779200,1559865600,1560124800,1560211200,1560297600,1560384000,1560470400,1560729600,1560816000,1560902400,1560988800,1561075200,1561334400,1561420800,1561507200,1561593600,1561680000,1561939200,1562025600,1562112000,1562198400,1562284800,1562544000,1562630400,1562716800,1562803200,1562889600,1563148800,1563235200,1563321600,1563408000,1563494400,1563753600,1563840000,1563926400,1564012800,1564099200,1564358400,1564444800,1564531200,1564617600,1564704000,1564963200,1565049600,1565136000,1565222400,1565308800,1565568000,1565654400,1565740800,1565827200,1565913600,1566172800,1566259200,1566345600,1566432000,1566518400,1566777600,1566864000,1566950400,1567036800,1567123200,1567296000,1567382400,1567468800,1567555200,1567641600,1567728000,1567987200,1568073600,1568160000,1568246400,1568332800,1568592000,1568678400,1568764800,1568851200,1568937600,1569196800,1569283200,1569369600,1569456000,1569542400,1569801600,1569888000,1569974400,1570060800,1570147200,1570406400,1570492800,1570579200,1570665600,1570752000,1571011200,1571097600,1571184000,1571270400,1571356800,1571616000,1571702400,1571788800,1571875200,1571961600}; + double opens[] = {1284.7,1319.9,1318.7,1328,1317.6,1321.6,1314.3,1325,1319.3,1323.1,1324.7,1321.3,1323.5,1322,1281.3,1281.95,1311.1,1315,1314,1313.1,1331.9,1334.2,1341.3,1350.6,1349.8,1346.4,1343.4,1344.9,1335.6,1337.9,1342.5,1337,1338.6,1337,1340.4,1324.65,1324.35,1349.5,1371.3,1367.9,1351.3,1357.8,1356.1,1356,1347.6,1339.1,1320.6,1311.8,1314,1312.4,1312.3,1323.5,1319.1,1327.2,1332.1,1320.3,1323.1,1328,1330.9,1338,1333,1335.3,1345.2,1341.1,1332.5,1314,1314.4,1310.7,1314,1313.1,1315,1313.7,1320,1326.5,1329.2,1314.2,1312.3,1309.5,1297.4,1293.7,1277.9,1295.8,1295.2,1290.3,1294.2,1298,1306.4,1299.8,1302.3,1297,1289.6,1302,1300.7,1303.5,1300.5,1303.2,1306,1318.7,1315,1314.5,1304.1,1294.7,1293.7,1291.2,1290.2,1300.4,1284.2,1284.25,1301.8,1295.9,1296.2,1304.4,1323.1,1340.9,1341,1348,1351.4,1351.4,1343.5,1342.3,1349,1357.6,1357.1,1354.7,1361.4,1375.2,1403.5,1414.7,1433.2,1438,1423.6,1424.4,1418,1399.5,1435.5,1421.25,1434.1,1412.4,1409.8,1412.2,1433.4,1418.4,1429,1428.8,1420.6,1441,1460.4,1441.7,1438.4,1431,1439.3,1427.4,1431.9,1439.5,1443.7,1425.6,1457.5,1451.2,1481.1,1486.7,1512.1,1515.9,1509.2,1522.3,1513,1526.6,1533.9,1523,1506.3,1518.4,1512.4,1508.8,1545.4,1537.3,1551.8,1549.4,1536.9,1535.25,1537.95,1535.2,1556,1561.4,1525.6,1516.4,1507,1493.9,1504.9,1506.5,1513.1,1506.5,1509.7,1502,1506.8,1521.5,1529.8,1539.8,1510.9,1511.8,1501.7,1478,1485.4,1505.6,1511.6,1518.6,1498.7,1510.9,1510.8,1498.3,1492,1497.7,1484.8,1494.2,1495.6,1495.6,1487.5,1491.1,1495.1,1506.4}; + double highs[] = {1284.75,1320.6,1327,1330.8,1326.8,1321.6,1326,1328,1325.8,1327.1,1326,1326,1323.5,1322.1,1282.7,1282.95,1315.8,1316.3,1314,1333.2,1334.7,1341.7,1353.2,1354.6,1352.2,1346.4,1345.7,1344.9,1340.7,1344.2,1342.7,1342.1,1345.2,1342,1350,1324.95,1330.75,1369.6,1374.3,1368.4,1359.8,1359,1357,1356,1353.4,1340.6,1322.3,1314.1,1316.1,1312.9,1325.7,1323.5,1326.3,1336,1332.1,1330.1,1330.4,1334.7,1341.1,1344.2,1338.8,1348.4,1345.6,1342.8,1334.7,1322.3,1319.3,1314.7,1316.6,1316.4,1315,1325.4,1328.3,1332.2,1329.2,1316.9,1312.3,1309.5,1299.6,1296.9,1277.9,1299.5,1296.2,1298.4,1302.5,1308.7,1306.4,1305.9,1307,1297.2,1301.7,1305,1305.3,1310.2,1307,1308,1319.8,1321.7,1318.7,1316.2,1305.9,1295.8,1293.8,1293.7,1304.2,1302,1285.15,1286.85,1304,1302,1305.2,1323,1344.1,1345.2,1360.1,1355.3,1363.8,1353,1344.7,1353.6,1358,1373.6,1358.2,1369.6,1377.6,1408.9,1425.5,1435.9,1453.7,1438,1426,1439.1,1418,1435,1452.6,1426.65,1437.5,1421.5,1414.1,1433.3,1441.3,1431.4,1433.9,1432.4,1440.8,1462.3,1467,1443.5,1444,1442.9,1447,1437.6,1440.8,1445.7,1447.8,1458.2,1461.9,1481.8,1486.8,1522.7,1521.3,1521.1,1531.5,1546.1,1534.9,1537.7,1538.6,1523.6,1518.8,1518.4,1514.6,1540.3,1565,1554.5,1556.6,1559.8,1541.9,1542.9,1540.05,1558.9,1566.2,1561.9,1536.2,1523.8,1509.1,1506.2,1532.2,1516.6,1519.7,1515,1519.5,1512.1,1524.5,1534.4,1543.3,1543.3,1542.8,1519.5,1507.2,1493.5,1511.4,1525.8,1522.2,1518.8,1515.3,1518,1522.3,1508,1501.5,1503,1495.5,1501.1,1497.9,1498.7,1492.1,1499.4,1506.9,1520.9}; + double lows[] = {1282.85,1315,1318.7,1309.6,1317.6,1312.9,1312.4,1319.1,1319,1321,1318.1,1321.3,1319.9,1312,1280.5,1276.15,1308,1309.9,1308.5,1312.3,1329.3,1333.1,1340.2,1347,1345.9,1338,1340.8,1335,1332,1337.9,1333,1336.8,1333.2,1329.9,1340.4,1323.85,1324.05,1349,1366.3,1351.2,1349.1,1352.4,1350.7,1344.3,1338.9,1316.3,1308.4,1306.9,1309.6,1306.7,1312.3,1315.4,1319,1327.2,1317.2,1320,1323,1328,1323,1327.8,1331.7,1335.3,1336.6,1331.8,1311.4,1310,1309.5,1308,1310.6,1302.8,1306.6,1313.7,1320,1322.8,1311,1312.1,1303.6,1293.9,1293.5,1291,1277.9,1294.1,1286,1289.1,1293.5,1296.9,1298,1299.6,1292.9,1285.1,1288.5,1296.3,1297.2,1298.4,1298.6,1302,1300.3,1312,1310.8,1301.9,1292,1291.1,1286.3,1289.2,1289.9,1297.4,1283.65,1283.25,1292.9,1295.9,1290.8,1304.2,1322.7,1336.1,1341,1343.5,1345.8,1340.3,1335.1,1341.5,1347.6,1352.8,1348.2,1353.7,1356.5,1373.3,1398,1414.7,1427,1416.4,1412.7,1420.1,1396.4,1398.8,1426.6,1412.85,1400.7,1406,1399.8,1404.4,1415.5,1417.2,1421.9,1415,1413.7,1428.1,1434,1435.7,1427.5,1429.4,1423.9,1425.6,1427.5,1434.8,1422.3,1412.1,1442.5,1448.8,1468.2,1484.3,1501.6,1506.2,1498.6,1488.9,1504.5,1518.3,1513.9,1503.3,1503,1506.5,1502.1,1503,1534.8,1535.3,1541.4,1528.6,1525.6,1535.25,1528.15,1528,1542.6,1514.3,1510.7,1505.5,1492.1,1492.9,1496.8,1493.1,1503.4,1500.9,1490.7,1496.3,1505.3,1505.3,1517.9,1507.4,1507.1,1493.3,1470.5,1465,1480.5,1501.7,1501.4,1493.3,1492.1,1505.1,1495.7,1478,1487.1,1480.8,1480.6,1487,1488.3,1484.8,1484,1490.7,1490.4,1503.1}; + double closes[] = {1283.35,1315.3,1326.1,1317.4,1321.5,1317.4,1323.5,1319.2,1321.3,1323.3,1319.7,1325.1,1323.6,1313.8,1282.05,1279.05,1314.2,1315.2,1310.8,1329.1,1334.5,1340.2,1340.5,1350,1347.1,1344.3,1344.6,1339.7,1339.4,1343.7,1337,1338.9,1340.1,1338.7,1346.8,1324.25,1329.55,1369.6,1372.5,1352.4,1357.6,1354.2,1353.4,1346,1341,1323.8,1311.9,1309.1,1312.2,1310.7,1324.3,1315.7,1322.4,1333.8,1319.4,1327.1,1325.8,1330.9,1325.8,1331.6,1336.5,1346.7,1339.2,1334.7,1313.3,1316.5,1312.4,1313.4,1313.3,1312.2,1313.7,1319.9,1326.3,1331.9,1311.3,1313.4,1309.4,1295.2,1294.7,1294.1,1277.9,1295.8,1291.2,1297.4,1297.7,1306.8,1299.4,1303.6,1302.2,1289.9,1299.2,1301.8,1303.6,1299.5,1303.2,1305.3,1319.5,1313.6,1315.1,1303.5,1293,1294.6,1290.4,1291.4,1302.7,1301,1284.15,1284.95,1294.3,1297.9,1304.1,1322.6,1339.3,1340.1,1344.9,1354,1357.4,1340.7,1342.7,1348.2,1355.1,1355.9,1354.2,1362.1,1360.1,1408.3,1411.2,1429.5,1430.1,1426.8,1423.4,1425.1,1400.8,1419.8,1432.9,1423.55,1412.1,1412.2,1412.8,1424.9,1419.3,1424.8,1426.1,1423.6,1435.9,1440.8,1439.4,1439.7,1434.5,1436.5,1427.5,1432.2,1433.3,1441.8,1437.8,1432.4,1457.5,1476.5,1484.2,1519.6,1509.5,1508.5,1517.2,1514.1,1527.8,1531.2,1523.6,1511.6,1515.7,1515.7,1508.5,1537.6,1537.2,1551.8,1549.1,1536.9,1529.4,1538.05,1535.15,1555.9,1560.4,1525.5,1515.5,1511.1,1499.2,1503.2,1507.4,1499.5,1511.5,1513.4,1515.8,1506.2,1515.1,1531.5,1540.2,1512.3,1515.2,1506.4,1472.9,1489,1507.9,1513.8,1512.9,1504.4,1503.9,1512.8,1500.9,1488.7,1497.6,1483.5,1494,1498.3,1494.1,1488.1,1487.5,1495.7,1504.7,1505.3}; + static bool tooltip = true; + ImGui::Checkbox("Show Tooltip", &tooltip); + ImGui::SameLine(); + static ImVec4 bullCol = ImVec4(0.000f, 1.000f, 0.441f, 1.000f); + static ImVec4 bearCol = ImVec4(0.853f, 0.050f, 0.310f, 1.000f); + ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); + ImPlot::GetStyle().UseLocalTime = false; + + if (ImPlot::BeginPlot("Candlestick Chart",ImVec2(-1,0))) { + ImPlot::SetupAxes(nullptr,nullptr,0,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_RangeFit); + ImPlot::SetupAxesLimits(1546300800, 1571961600, 1250, 1600); + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1, 1546300800, 1571961600); + ImPlot::SetupAxisZoomConstraints(ImAxis_X1, 60*60*24*14, 1571961600-1546300800); + ImPlot::SetupAxisFormat(ImAxis_Y1, "$%.0f"); + MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); + ImPlot::EndPlot(); + } + } + +//----------------------------------------------------------------------------- +// DEMO WINDOW +//----------------------------------------------------------------------------- + +void DemoHeader(const char* label, void(*demo)()) { + if (ImGui::TreeNodeEx(label)) { + demo(); + ImGui::TreePop(); + } +} + +void ShowDemoWindow(bool* p_open) { + static bool show_implot_metrics = false; + static bool show_implot_style_editor = false; + static bool show_imgui_metrics = false; + static bool show_imgui_style_editor = false; + static bool show_imgui_demo = false; + + if (show_implot_metrics) { + ImPlot::ShowMetricsWindow(&show_implot_metrics); + } + if (show_implot_style_editor) { + ImGui::SetNextWindowSize(ImVec2(415,762), ImGuiCond_Appearing); + ImGui::Begin("Style Editor (ImPlot)", &show_implot_style_editor); + ImPlot::ShowStyleEditor(); + ImGui::End(); + } + if (show_imgui_style_editor) { + ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor); + ImGui::ShowStyleEditor(); + ImGui::End(); + } + if (show_imgui_metrics) { + ImGui::ShowMetricsWindow(&show_imgui_metrics); + } + if (show_imgui_demo) { + ImGui::ShowDemoWindow(&show_imgui_demo); + } + ImGui::SetNextWindowPos(ImVec2(50, 50), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600, 750), ImGuiCond_FirstUseEver); + ImGui::Begin("ImPlot Demo", p_open, ImGuiWindowFlags_MenuBar); + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Tools")) { + ImGui::MenuItem("Metrics", nullptr, &show_implot_metrics); + ImGui::MenuItem("Style Editor", nullptr, &show_implot_style_editor); + ImGui::Separator(); + ImGui::MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); + ImGui::MenuItem("ImGui Style Editor", nullptr, &show_imgui_style_editor); + ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + //------------------------------------------------------------------------- + ImGui::Text("ImPlot says hello. (%s)", IMPLOT_VERSION); + // display warning about 16-bit indices + static bool showWarning = sizeof(ImDrawIdx)*8 == 16 && (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) == false; + if (showWarning) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,1,0,1)); + ImGui::TextWrapped("WARNING: ImDrawIdx is 16-bit and ImGuiBackendFlags_RendererHasVtxOffset is false. Expect visual glitches and artifacts! See README for more information."); + ImGui::PopStyleColor(); + } + + ImGui::Spacing(); + + if (ImGui::BeginTabBar("ImPlotDemoTabs")) { + if (ImGui::BeginTabItem("Plots")) { + DemoHeader("Line Plots", Demo_LinePlots); + DemoHeader("Filled Line Plots", Demo_FilledLinePlots); + DemoHeader("Shaded Plots##", Demo_ShadedPlots); + DemoHeader("Scatter Plots", Demo_ScatterPlots); + DemoHeader("Realtime Plots", Demo_RealtimePlots); + DemoHeader("Stairstep Plots", Demo_StairstepPlots); + DemoHeader("Bar Plots", Demo_BarPlots); + DemoHeader("Bar Groups", Demo_BarGroups); + DemoHeader("Bar Stacks", Demo_BarStacks); + DemoHeader("Error Bars", Demo_ErrorBars); + DemoHeader("Stem Plots##", Demo_StemPlots); + DemoHeader("Infinite Lines", Demo_InfiniteLines); + DemoHeader("Pie Charts", Demo_PieCharts); + DemoHeader("Heatmaps", Demo_Heatmaps); + DemoHeader("Histogram", Demo_Histogram); + DemoHeader("Histogram 2D", Demo_Histogram2D); + DemoHeader("Digital Plots", Demo_DigitalPlots); + DemoHeader("Images", Demo_Images); + DemoHeader("Markers and Text", Demo_MarkersAndText); + DemoHeader("NaN Values", Demo_NaNValues); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Subplots")) { + DemoHeader("Sizing", Demo_SubplotsSizing); + DemoHeader("Item Sharing", Demo_SubplotItemSharing); + DemoHeader("Axis Linking", Demo_SubplotAxisLinking); + DemoHeader("Tables", Demo_Tables); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Axes")) { + DemoHeader("Log Scale", Demo_LogScale); + DemoHeader("Symmetric Log Scale", Demo_SymmetricLogScale); + DemoHeader("Time Scale", Demo_TimeScale); + DemoHeader("Custom Scale", Demo_CustomScale); + DemoHeader("Multiple Axes", Demo_MultipleAxes); + DemoHeader("Tick Labels", Demo_TickLabels); + DemoHeader("Linked Axes", Demo_LinkedAxes); + DemoHeader("Axis Constraints", Demo_AxisConstraints); + DemoHeader("Equal Axes", Demo_EqualAxes); + DemoHeader("Auto-Fitting Data", Demo_AutoFittingData); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Tools")) { + DemoHeader("Offset and Stride", Demo_OffsetAndStride); + DemoHeader("Drag Points", Demo_DragPoints); + DemoHeader("Drag Lines", Demo_DragLines); + DemoHeader("Drag Rects", Demo_DragRects); + DemoHeader("Querying", Demo_Querying); + DemoHeader("Annotations", Demo_Annotations); + DemoHeader("Tags", Demo_Tags); + DemoHeader("Drag and Drop", Demo_DragAndDrop); + DemoHeader("Legend Options", Demo_LegendOptions); + DemoHeader("Legend Popups", Demo_LegendPopups); + DemoHeader("Colormap Widgets", Demo_ColormapWidgets); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Custom")) { + DemoHeader("Custom Styles", Demo_CustomStyles); + DemoHeader("Custom Data and Getters", Demo_CustomDataAndGetters); + DemoHeader("Custom Rendering", Demo_CustomRendering); + DemoHeader("Custom Plotters and Tooltips", Demo_CustomPlottersAndTooltips); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Config")) { + Demo_Config(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Help")) { + Demo_Help(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::End(); +} + +} // namespace ImPlot + +namespace MyImPlot { + +ImPlotPoint SineWave(int idx, void* data) { + WaveData* wd = (WaveData*)data; + double x = idx * wd->X; + return ImPlotPoint(x, wd->Offset + wd->Amp * sin(2 * 3.14 * wd->Freq * x)); +} + +ImPlotPoint SawWave(int idx, void* data) { + WaveData* wd = (WaveData*)data; + double x = idx * wd->X; + return ImPlotPoint(x, wd->Offset + wd->Amp * (-2 / 3.14 * atan(cos(3.14 * wd->Freq * x) / sin(3.14 * wd->Freq * x)))); +} + +ImPlotPoint Spiral(int idx, void*) { + float r = 0.9f; // outer radius + float a = 0; // inner radius + float b = 0.05f; // increment per rev + float n = (r - a) / b; // number of revolutions + double th = 2 * n * 3.14; // angle + float Th = float(th * idx / (1000 - 1)); + return ImPlotPoint(0.5f+(a + b*Th / (2.0f * (float) 3.14))*cos(Th), + 0.5f + (a + b*Th / (2.0f * (float)3.14))*sin(Th)); +} + +void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size) { + ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0,0)); + if (ImPlot::BeginPlot(id,size,ImPlotFlags_CanvasOnly|ImPlotFlags_NoChild)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, count - 1, min_v, max_v, ImGuiCond_Always); + ImPlot::SetNextLineStyle(col); + ImPlot::SetNextFillStyle(col, 0.25); + ImPlot::PlotLine(id, values, count, 1, 0, ImPlotLineFlags_Shaded, offset); + ImPlot::EndPlot(); + } + ImPlot::PopStyleVar(); +} + +void StyleSeaborn() { + + ImPlotStyle& style = ImPlot::GetStyle(); + + ImVec4* colors = style.Colors; + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_PlotBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImPlotCol_LegendBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.80f, 0.81f, 0.85f, 1.00f); + colors[ImPlotCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisBgHovered] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_AxisBgActive] = ImVec4(0.92f, 0.92f, 0.95f, 0.75f); + colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.65f, 0.00f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.23f, 0.10f, 0.64f, 0.50f); + + style.LineWeight = 1.5; + style.Marker = ImPlotMarker_None; + style.MarkerSize = 4; + style.MarkerWeight = 1; + style.FillAlpha = 1.0f; + style.ErrorBarSize = 5; + style.ErrorBarWeight = 1.5f; + style.DigitalBitHeight = 8; + style.DigitalBitGap = 4; + style.PlotBorderSize = 0; + style.MinorAlpha = 1.0f; + style.MajorTickLen = ImVec2(0,0); + style.MinorTickLen = ImVec2(0,0); + style.MajorTickSize = ImVec2(0,0); + style.MinorTickSize = ImVec2(0,0); + style.MajorGridSize = ImVec2(1.2f,1.2f); + style.MinorGridSize = ImVec2(1.2f,1.2f); + style.PlotPadding = ImVec2(12,12); + style.LabelPadding = ImVec2(5,5); + style.LegendPadding = ImVec2(5,5); + style.MousePosPadding = ImVec2(5,5); + style.PlotMinSize = ImVec2(300,225); +} + +} // namespaece MyImPlot + +// WARNING: +// +// You can use "implot_internal.h" to build custom plotting fuctions or extend ImPlot. +// However, note that forward compatibility of this file is not guaranteed and the +// internal API is subject to change. At some point we hope to bring more of this +// into the public API and expose the necessary building blocks to fully support +// custom plotters. For now, proceed at your own risk! + +#include "implot_internal.h" + +namespace MyImPlot { + +template +int BinarySearch(const T* arr, int l, int r, T x) { + if (r >= l) { + int mid = l + (r - l) / 2; + if (arr[mid] == x) + return mid; + if (arr[mid] > x) + return BinarySearch(arr, l, mid - 1, x); + return BinarySearch(arr, mid + 1, r, x); + } + return -1; +} + +void PlotCandlestick(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, bool tooltip, float width_percent, ImVec4 bullCol, ImVec4 bearCol) { + + // get ImGui window DrawList + ImDrawList* draw_list = ImPlot::GetPlotDrawList(); + // calc real value width + double half_width = count > 1 ? (xs[1] - xs[0]) * width_percent : width_percent; + + // custom tool + if (ImPlot::IsPlotHovered() && tooltip) { + ImPlotPoint mouse = ImPlot::GetPlotMousePos(); + mouse.x = ImPlot::RoundTime(ImPlotTime::FromDouble(mouse.x), ImPlotTimeUnit_Day).ToDouble(); + float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; + float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; + float tool_t = ImPlot::GetPlotPos().y; + float tool_b = tool_t + ImPlot::GetPlotSize().y; + ImPlot::PushPlotClipRect(); + draw_list->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), IM_COL32(128,128,128,64)); + ImPlot::PopPlotClipRect(); + // find mouse location index + int idx = BinarySearch(xs, 0, count - 1, mouse.x); + // render tool tip (won't be affected by plot clip rect) + if (idx != -1) { + ImGui::BeginTooltip(); + char buff[32]; + ImPlot::FormatDate(ImPlotTime::FromDouble(xs[idx]),buff,32,ImPlotDateFmt_DayMoYr,ImPlot::GetStyle().UseISO8601); + ImGui::Text("Day: %s", buff); + ImGui::Text("Open: $%.2f", opens[idx]); + ImGui::Text("Close: $%.2f", closes[idx]); + ImGui::Text("Low: $%.2f", lows[idx]); + ImGui::Text("High: $%.2f", highs[idx]); + ImGui::EndTooltip(); + } + } + + // begin plot item + if (ImPlot::BeginItem(label_id)) { + // override legend icon color + ImPlot::GetCurrentItem()->Color = IM_COL32(64,64,64,255); + // fit data if requested + if (ImPlot::FitThisFrame()) { + for (int i = 0; i < count; ++i) { + ImPlot::FitPoint(ImPlotPoint(xs[i], lows[i])); + ImPlot::FitPoint(ImPlotPoint(xs[i], highs[i])); + } + } + // render data + for (int i = 0; i < count; ++i) { + ImVec2 open_pos = ImPlot::PlotToPixels(xs[i] - half_width, opens[i]); + ImVec2 close_pos = ImPlot::PlotToPixels(xs[i] + half_width, closes[i]); + ImVec2 low_pos = ImPlot::PlotToPixels(xs[i], lows[i]); + ImVec2 high_pos = ImPlot::PlotToPixels(xs[i], highs[i]); + ImU32 color = ImGui::GetColorU32(opens[i] > closes[i] ? bearCol : bullCol); + draw_list->AddLine(low_pos, high_pos, color); + draw_list->AddRectFilled(open_pos, close_pos, color); + } + + // end plot item + ImPlot::EndItem(); + } +} + +} // namespace MyImplot diff --git a/crogine/src/imgui/implot_internal.h b/crogine/src/imgui/implot_internal.h new file mode 100644 index 000000000..51f0b429c --- /dev/null +++ b/crogine/src/imgui/implot_internal.h @@ -0,0 +1,1667 @@ +// MIT License + +// Copyright (c) 2022 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.14 + +// You may use this file to debug, understand or extend ImPlot features but we +// don't provide any guarantee of forward compatibility! + +//----------------------------------------------------------------------------- +// [SECTION] Header Mess +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include "imgui_internal.h" + +#ifndef IMPLOT_VERSION +#error Must include implot.h before implot_internal.h +#endif + + +// Support for pre-1.84 versions. ImPool's GetSize() -> GetBufSize() +#if (IMGUI_VERSION_NUM < 18303) +#define GetBufSize GetSize +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Constants +//----------------------------------------------------------------------------- + +// Constants can be changed unless stated otherwise. We may move some of these +// to ImPlotStyleVar_ over time. + +// Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS) +#define IMPLOT_MIN_TIME 0 +// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) +#define IMPLOT_MAX_TIME 32503680000 +// Default label format for axis labels +#define IMPLOT_LABEL_FORMAT "%g" +// Max character size for tick labels +#define IMPLOT_LABEL_MAX_SIZE 32 + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +#define IMPLOT_NUM_X_AXES ImAxis_Y1 +#define IMPLOT_NUM_Y_AXES (ImAxis_COUNT - IMPLOT_NUM_X_AXES) + +// Split ImU32 color into RGB components [0 255] +#define IM_COL32_SPLIT_RGB(col,r,g,b) \ + ImU32 r = ((col >> IM_COL32_R_SHIFT) & 0xFF); \ + ImU32 g = ((col >> IM_COL32_G_SHIFT) & 0xFF); \ + ImU32 b = ((col >> IM_COL32_B_SHIFT) & 0xFF); + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//----------------------------------------------------------------------------- + +struct ImPlotTick; +struct ImPlotAxis; +struct ImPlotAxisColor; +struct ImPlotItem; +struct ImPlotLegend; +struct ImPlotPlot; +struct ImPlotNextPlotData; +struct ImPlotTicker; + +//----------------------------------------------------------------------------- +// [SECTION] Context Pointer +//----------------------------------------------------------------------------- + +#ifndef GImPlot +extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Generic Helpers +//----------------------------------------------------------------------------- + +// Computes the common (base-10) logarithm +static inline float ImLog10(float x) { return log10f(x); } +static inline double ImLog10(double x) { return log10(x); } +static inline float ImSinh(float x) { return sinhf(x); } +static inline double ImSinh(double x) { return sinh(x); } +static inline float ImAsinh(float x) { return asinhf(x); } +static inline double ImAsinh(double x) { return asinh(x); } +// Returns true if a flag is set +template +static inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; } +// Flips a flag in a flagset +template +static inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; } +// Linearly remaps x from [x0 x1] to [y0 y1]. +template +static inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } +// Linear rempas x from [x0 x1] to [0 1] +template +static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } +// Returns always positive modulo (assumes r != 0) +static inline int ImPosMod(int l, int r) { return (l % r + r) % r; } +// Returns true if val is NAN +static inline bool ImNan(double val) { return isnan(val); } +// Returns true if val is NAN or INFINITY +static inline bool ImNanOrInf(double val) { return !(val >= -DBL_MAX && val <= DBL_MAX) || ImNan(val); } +// Turns NANs to 0s +static inline double ImConstrainNan(double val) { return ImNan(val) ? 0 : val; } +// Turns infinity to floating point maximums +static inline double ImConstrainInf(double val) { return val >= DBL_MAX ? DBL_MAX : val <= -DBL_MAX ? - DBL_MAX : val; } +// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) +static inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val; } +// Turns numbers less than 0 to zero +static inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } +// True if two numbers are approximately equal using units in the last place. +static inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1-v2) < DBL_EPSILON * ImAbs(v1+v2) * ulp || ImAbs(v1-v2) < DBL_MIN; } +// Finds min value in an unsorted array +template +static inline T ImMinArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] < m) { m = values[i]; } } return m; } +// Finds the max value in an unsorted array +template +static inline T ImMaxArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] > m) { m = values[i]; } } return m; } +// Finds the min and max value in an unsorted array +template +static inline void ImMinMaxArray(const T* values, int count, T* min_out, T* max_out) { + T Min = values[0]; T Max = values[0]; + for (int i = 1; i < count; ++i) { + if (values[i] < Min) { Min = values[i]; } + if (values[i] > Max) { Max = values[i]; } + } + *min_out = Min; *max_out = Max; +} +// Finds the sim of an array +template +static inline T ImSum(const T* values, int count) { + T sum = 0; + for (int i = 0; i < count; ++i) + sum += values[i]; + return sum; +} +// Finds the mean of an array +template +static inline double ImMean(const T* values, int count) { + double den = 1.0 / count; + double mu = 0; + for (int i = 0; i < count; ++i) + mu += (double)values[i] * den; + return mu; +} +// Finds the sample standard deviation of an array +template +static inline double ImStdDev(const T* values, int count) { + double den = 1.0 / (count - 1.0); + double mu = ImMean(values, count); + double x = 0; + for (int i = 0; i < count; ++i) + x += ((double)values[i] - mu) * ((double)values[i] - mu) * den; + return sqrt(x); +} +// Mix color a and b by factor s in [0 256] +static inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) { +#ifdef IMPLOT_MIX64 + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24); + const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24); + const ImU64 mix = (al * af + bl * bf); + return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8); +#else + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU32 al = (a & 0x00ff00ff); + const ImU32 ah = (a & 0xff00ff00) >> 8; + const ImU32 bl = (b & 0x00ff00ff); + const ImU32 bh = (b & 0xff00ff00) >> 8; + const ImU32 ml = (al * af + bl * bf); + const ImU32 mh = (ah * af + bh * bf); + return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8); +#endif +} + +// Lerp across an array of 32-bit collors given t in [0.0 1.0] +static inline ImU32 ImLerpU32(const ImU32* colors, int size, float t) { + int i1 = (int)((size - 1 ) * t); + int i2 = i1 + 1; + if (i2 == size || size == 1) + return colors[i1]; + float den = 1.0f / (size - 1); + float t1 = i1 * den; + float t2 = i2 * den; + float tr = ImRemap01(t, t1, t2); + return ImMixU32(colors[i1], colors[i2], (ImU32)(tr*256)); +} + +// Set alpha channel of 32-bit color from float in range [0.0 1.0] +static inline ImU32 ImAlphaU32(ImU32 col, float alpha) { + return col & ~((ImU32)((1.0f-alpha)*255)< +static inline bool ImOverlaps(T min_a, T max_a, T min_b, T max_b) { + return min_a <= max_b && min_b <= max_a; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImPlot Enums +//----------------------------------------------------------------------------- + +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ + +enum ImPlotTimeUnit_ { + ImPlotTimeUnit_Us, // microsecond + ImPlotTimeUnit_Ms, // millisecond + ImPlotTimeUnit_S, // second + ImPlotTimeUnit_Min, // minute + ImPlotTimeUnit_Hr, // hour + ImPlotTimeUnit_Day, // day + ImPlotTimeUnit_Mo, // month + ImPlotTimeUnit_Yr, // year + ImPlotTimeUnit_COUNT +}; + +enum ImPlotDateFmt_ { // default [ ISO 8601 ] + ImPlotDateFmt_None = 0, + ImPlotDateFmt_DayMo, // 10/3 [ --10-03 ] + ImPlotDateFmt_DayMoYr, // 10/3/91 [ 1991-10-03 ] + ImPlotDateFmt_MoYr, // Oct 1991 [ 1991-10 ] + ImPlotDateFmt_Mo, // Oct [ --10 ] + ImPlotDateFmt_Yr // 1991 [ 1991 ] +}; + +enum ImPlotTimeFmt_ { // default [ 24 Hour Clock ] + ImPlotTimeFmt_None = 0, + ImPlotTimeFmt_Us, // .428 552 [ .428 552 ] + ImPlotTimeFmt_SUs, // :29.428 552 [ :29.428 552 ] + ImPlotTimeFmt_SMs, // :29.428 [ :29.428 ] + ImPlotTimeFmt_S, // :29 [ :29 ] + ImPlotTimeFmt_MinSMs, // 21:29.428 [ 21:29.428 ] + ImPlotTimeFmt_HrMinSMs, // 7:21:29.428pm [ 19:21:29.428 ] + ImPlotTimeFmt_HrMinS, // 7:21:29pm [ 19:21:29 ] + ImPlotTimeFmt_HrMin, // 7:21pm [ 19:21 ] + ImPlotTimeFmt_Hr // 7pm [ 19:00 ] +}; + +//----------------------------------------------------------------------------- +// [SECTION] Callbacks +//----------------------------------------------------------------------------- + +typedef void (*ImPlotLocator)(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); + +//----------------------------------------------------------------------------- +// [SECTION] Structs +//----------------------------------------------------------------------------- + +// Combined date/time format spec +struct ImPlotDateTimeSpec { + ImPlotDateTimeSpec() {} + ImPlotDateTimeSpec(ImPlotDateFmt date_fmt, ImPlotTimeFmt time_fmt, bool use_24_hr_clk = false, bool use_iso_8601 = false) { + Date = date_fmt; + Time = time_fmt; + UseISO8601 = use_iso_8601; + Use24HourClock = use_24_hr_clk; + } + ImPlotDateFmt Date; + ImPlotTimeFmt Time; + bool UseISO8601; + bool Use24HourClock; +}; + +// Two part timestamp struct. +struct ImPlotTime { + time_t S; // second part + int Us; // microsecond part + ImPlotTime() { S = 0; Us = 0; } + ImPlotTime(time_t s, int us = 0) { S = s + us / 1000000; Us = us % 1000000; } + void RollOver() { S = S + Us / 1000000; Us = Us % 1000000; } + double ToDouble() const { return (double)S + (double)Us / 1000000.0; } + static ImPlotTime FromDouble(double t) { return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); } +}; + +static inline ImPlotTime operator+(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S + rhs.S, lhs.Us + rhs.Us); } +static inline ImPlotTime operator-(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S - rhs.S, lhs.Us - rhs.Us); } +static inline bool operator==(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S && lhs.Us == rhs.Us; } +static inline bool operator<(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S ? lhs.Us < rhs.Us : lhs.S < rhs.S; } +static inline bool operator>(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return rhs < lhs; } +static inline bool operator<=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs < rhs || lhs == rhs; } +static inline bool operator>=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs > rhs || lhs == rhs; } + +// Colormap data storage +struct ImPlotColormapData { + ImVector Keys; + ImVector KeyCounts; + ImVector KeyOffsets; + ImVector Tables; + ImVector TableSizes; + ImVector TableOffsets; + ImGuiTextBuffer Text; + ImVector TextOffsets; + ImVector Quals; + ImGuiStorage Map; + int Count; + + ImPlotColormapData() { Count = 0; } + + int Append(const char* name, const ImU32* keys, int count, bool qual) { + if (GetIndex(name) != -1) + return -1; + KeyOffsets.push_back(Keys.size()); + KeyCounts.push_back(count); + Keys.reserve(Keys.size()+count); + for (int i = 0; i < count; ++i) + Keys.push_back(keys[i]); + TextOffsets.push_back(Text.size()); + Text.append(name, name + strlen(name) + 1); + Quals.push_back(qual); + ImGuiID id = ImHashStr(name); + int idx = Count++; + Map.SetInt(id,idx); + _AppendTable(idx); + return idx; + } + + void _AppendTable(ImPlotColormap cmap) { + int key_count = GetKeyCount(cmap); + const ImU32* keys = GetKeys(cmap); + int off = Tables.size(); + TableOffsets.push_back(off); + if (IsQual(cmap)) { + Tables.reserve(key_count); + for (int i = 0; i < key_count; ++i) + Tables.push_back(keys[i]); + TableSizes.push_back(key_count); + } + else { + int max_size = 255 * (key_count-1) + 1; + Tables.reserve(off + max_size); + // ImU32 last = keys[0]; + // Tables.push_back(last); + // int n = 1; + for (int i = 0; i < key_count-1; ++i) { + for (int s = 0; s < 255; ++s) { + ImU32 a = keys[i]; + ImU32 b = keys[i+1]; + ImU32 c = ImMixU32(a,b,s); + // if (c != last) { + Tables.push_back(c); + // last = c; + // n++; + // } + } + } + ImU32 c = keys[key_count-1]; + // if (c != last) { + Tables.push_back(c); + // n++; + // } + // TableSizes.push_back(n); + TableSizes.push_back(max_size); + } + } + + void RebuildTables() { + Tables.resize(0); + TableSizes.resize(0); + TableOffsets.resize(0); + for (int i = 0; i < Count; ++i) + _AppendTable(i); + } + + inline bool IsQual(ImPlotColormap cmap) const { return Quals[cmap]; } + inline const char* GetName(ImPlotColormap cmap) const { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : nullptr; } + inline ImPlotColormap GetIndex(const char* name) const { ImGuiID key = ImHashStr(name); return Map.GetInt(key,-1); } + + inline const ImU32* GetKeys(ImPlotColormap cmap) const { return &Keys[KeyOffsets[cmap]]; } + inline int GetKeyCount(ImPlotColormap cmap) const { return KeyCounts[cmap]; } + inline ImU32 GetKeyColor(ImPlotColormap cmap, int idx) const { return Keys[KeyOffsets[cmap]+idx]; } + inline void SetKeyColor(ImPlotColormap cmap, int idx, ImU32 value) { Keys[KeyOffsets[cmap]+idx] = value; RebuildTables(); } + + inline const ImU32* GetTable(ImPlotColormap cmap) const { return &Tables[TableOffsets[cmap]]; } + inline int GetTableSize(ImPlotColormap cmap) const { return TableSizes[cmap]; } + inline ImU32 GetTableColor(ImPlotColormap cmap, int idx) const { return Tables[TableOffsets[cmap]+idx]; } + + inline ImU32 LerpTable(ImPlotColormap cmap, float t) const { + int off = TableOffsets[cmap]; + int siz = TableSizes[cmap]; + int idx = Quals[cmap] ? ImClamp((int)(siz*t),0,siz-1) : (int)((siz - 1) * t + 0.5f); + return Tables[off + idx]; + } +}; + +// ImPlotPoint with positive/negative error values +struct ImPlotPointError { + double X, Y, Neg, Pos; + ImPlotPointError(double x, double y, double neg, double pos) { + X = x; Y = y; Neg = neg; Pos = pos; + } +}; + +// Interior plot label/annotation +struct ImPlotAnnotation { + ImVec2 Pos; + ImVec2 Offset; + ImU32 ColorBg; + ImU32 ColorFg; + int TextOffset; + bool Clamp; + ImPlotAnnotation() { + ColorBg = ColorFg = 0; + TextOffset = 0; + Clamp = false; + } +}; + +// Collection of plot labels +struct ImPlotAnnotationCollection { + + ImVector Annotations; + ImGuiTextBuffer TextBuffer; + int Size; + + ImPlotAnnotationCollection() { Reset(); } + + void AppendV(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt, va_list args) IM_FMTLIST(7) { + ImPlotAnnotation an; + an.Pos = pos; an.Offset = off; + an.ColorBg = bg; an.ColorFg = fg; + an.TextOffset = TextBuffer.size(); + an.Clamp = clamp; + Annotations.push_back(an); + TextBuffer.appendfv(fmt, args); + const char nul[] = ""; + TextBuffer.append(nul,nul+1); + Size++; + } + + void Append(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt, ...) IM_FMTARGS(7) { + va_list args; + va_start(args, fmt); + AppendV(pos, off, bg, fg, clamp, fmt, args); + va_end(args); + } + + const char* GetText(int idx) { + return TextBuffer.Buf.Data + Annotations[idx].TextOffset; + } + + void Reset() { + Annotations.shrink(0); + TextBuffer.Buf.shrink(0); + Size = 0; + } +}; + +struct ImPlotTag { + ImAxis Axis; + double Value; + ImU32 ColorBg; + ImU32 ColorFg; + int TextOffset; +}; + +struct ImPlotTagCollection { + + ImVector Tags; + ImGuiTextBuffer TextBuffer; + int Size; + + ImPlotTagCollection() { Reset(); } + + void AppendV(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, va_list args) IM_FMTLIST(6) { + ImPlotTag tag; + tag.Axis = axis; + tag.Value = value; + tag.ColorBg = bg; + tag.ColorFg = fg; + tag.TextOffset = TextBuffer.size(); + Tags.push_back(tag); + TextBuffer.appendfv(fmt, args); + const char nul[] = ""; + TextBuffer.append(nul,nul+1); + Size++; + } + + void Append(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, ...) IM_FMTARGS(6) { + va_list args; + va_start(args, fmt); + AppendV(axis, value, bg, fg, fmt, args); + va_end(args); + } + + const char* GetText(int idx) { + return TextBuffer.Buf.Data + Tags[idx].TextOffset; + } + + void Reset() { + Tags.shrink(0); + TextBuffer.Buf.shrink(0); + Size = 0; + } +}; + +// Tick mark info +struct ImPlotTick +{ + double PlotPos; + float PixelPos; + ImVec2 LabelSize; + int TextOffset; + bool Major; + bool ShowLabel; + int Level; + int Idx; + + ImPlotTick(double value, bool major, int level, bool show_label) { + PixelPos = 0; + PlotPos = value; + Major = major; + ShowLabel = show_label; + Level = level; + TextOffset = -1; + } +}; + +// Collection of ticks +struct ImPlotTicker { + ImVector Ticks; + ImGuiTextBuffer TextBuffer; + ImVec2 MaxSize; + ImVec2 LateSize; + int Levels; + + ImPlotTicker() { + Reset(); + } + + ImPlotTick& AddTick(double value, bool major, int level, bool show_label, const char* label) { + ImPlotTick tick(value, major, level, show_label); + if (show_label && label != nullptr) { + tick.TextOffset = TextBuffer.size(); + TextBuffer.append(label, label + strlen(label) + 1); + tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); + } + return AddTick(tick); + } + + ImPlotTick& AddTick(double value, bool major, int level, bool show_label, ImPlotFormatter formatter, void* data) { + ImPlotTick tick(value, major, level, show_label); + if (show_label && formatter != nullptr) { + char buff[IMPLOT_LABEL_MAX_SIZE]; + tick.TextOffset = TextBuffer.size(); + formatter(tick.PlotPos, buff, sizeof(buff), data); + TextBuffer.append(buff, buff + strlen(buff) + 1); + tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); + } + return AddTick(tick); + } + + inline ImPlotTick& AddTick(ImPlotTick tick) { + if (tick.ShowLabel) { + MaxSize.x = tick.LabelSize.x > MaxSize.x ? tick.LabelSize.x : MaxSize.x; + MaxSize.y = tick.LabelSize.y > MaxSize.y ? tick.LabelSize.y : MaxSize.y; + } + tick.Idx = Ticks.size(); + Ticks.push_back(tick); + return Ticks.back(); + } + + const char* GetText(int idx) const { + return TextBuffer.Buf.Data + Ticks[idx].TextOffset; + } + + const char* GetText(const ImPlotTick& tick) { + return GetText(tick.Idx); + } + + void OverrideSizeLate(const ImVec2& size) { + LateSize.x = size.x > LateSize.x ? size.x : LateSize.x; + LateSize.y = size.y > LateSize.y ? size.y : LateSize.y; + } + + void Reset() { + Ticks.shrink(0); + TextBuffer.Buf.shrink(0); + MaxSize = LateSize; + LateSize = ImVec2(0,0); + Levels = 1; + } + + int TickCount() const { + return Ticks.Size; + } +}; + +// Axis state information that must persist after EndPlot +struct ImPlotAxis +{ + ImGuiID ID; + ImPlotAxisFlags Flags; + ImPlotAxisFlags PreviousFlags; + ImPlotRange Range; + ImPlotCond RangeCond; + ImPlotScale Scale; + ImPlotRange FitExtents; + ImPlotAxis* OrthoAxis; + ImPlotRange ConstraintRange; + ImPlotRange ConstraintZoom; + + ImPlotTicker Ticker; + ImPlotFormatter Formatter; + void* FormatterData; + char FormatSpec[16]; + ImPlotLocator Locator; + + double* LinkedMin; + double* LinkedMax; + + int PickerLevel; + ImPlotTime PickerTimeMin, PickerTimeMax; + + ImPlotTransform TransformForward; + ImPlotTransform TransformInverse; + void* TransformData; + float PixelMin, PixelMax; + double ScaleMin, ScaleMax; + double ScaleToPixel; + float Datum1, Datum2; + + ImRect HoverRect; + int LabelOffset; + ImU32 ColorMaj, ColorMin, ColorTick, ColorTxt, ColorBg, ColorHov, ColorAct, ColorHiLi; + + bool Enabled; + bool Vertical; + bool FitThisFrame; + bool HasRange; + bool HasFormatSpec; + bool ShowDefaultTicks; + bool Hovered; + bool Held; + + ImPlotAxis() { + ID = 0; + Flags = PreviousFlags = ImPlotAxisFlags_None; + Range.Min = 0; + Range.Max = 1; + Scale = ImPlotScale_Linear; + TransformForward = TransformInverse = nullptr; + TransformData = nullptr; + FitExtents.Min = HUGE_VAL; + FitExtents.Max = -HUGE_VAL; + OrthoAxis = nullptr; + ConstraintRange = ImPlotRange(-INFINITY,INFINITY); + ConstraintZoom = ImPlotRange(DBL_MIN,INFINITY); + LinkedMin = LinkedMax = nullptr; + PickerLevel = 0; + Datum1 = Datum2 = 0; + PixelMin = PixelMax = 0; + LabelOffset = -1; + ColorMaj = ColorMin = ColorTick = ColorTxt = ColorBg = ColorHov = ColorAct = 0; + ColorHiLi = IM_COL32_BLACK_TRANS; + Formatter = nullptr; + FormatterData = nullptr; + Locator = nullptr; + Enabled = Hovered = Held = FitThisFrame = HasRange = HasFormatSpec = false; + ShowDefaultTicks = true; + } + + inline void Reset() { + Enabled = false; + Scale = ImPlotScale_Linear; + TransformForward = TransformInverse = nullptr; + TransformData = nullptr; + LabelOffset = -1; + HasFormatSpec = false; + Formatter = nullptr; + FormatterData = nullptr; + Locator = nullptr; + ShowDefaultTicks = true; + FitThisFrame = false; + FitExtents.Min = HUGE_VAL; + FitExtents.Max = -HUGE_VAL; + OrthoAxis = nullptr; + ConstraintRange = ImPlotRange(-INFINITY,INFINITY); + ConstraintZoom = ImPlotRange(DBL_MIN,INFINITY); + Ticker.Reset(); + } + + inline bool SetMin(double _min, bool force=false) { + if (!force && IsLockedMin()) + return false; + _min = ImConstrainNan(ImConstrainInf(_min)); + if (_min < ConstraintRange.Min) + _min = ConstraintRange.Min; + double z = Range.Max - _min; + if (z < ConstraintZoom.Min) + _min = Range.Max - ConstraintZoom.Min; + if (z > ConstraintZoom.Max) + _min = Range.Max - ConstraintZoom.Max; + if (_min >= Range.Max) + return false; + Range.Min = _min; + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); + UpdateTransformCache(); + return true; + }; + + inline bool SetMax(double _max, bool force=false) { + if (!force && IsLockedMax()) + return false; + _max = ImConstrainNan(ImConstrainInf(_max)); + if (_max > ConstraintRange.Max) + _max = ConstraintRange.Max; + double z = _max - Range.Min; + if (z < ConstraintZoom.Min) + _max = Range.Min + ConstraintZoom.Min; + if (z > ConstraintZoom.Max) + _max = Range.Min + ConstraintZoom.Max; + if (_max <= Range.Min) + return false; + Range.Max = _max; + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); + UpdateTransformCache(); + return true; + }; + + inline void SetRange(double v1, double v2) { + Range.Min = ImMin(v1,v2); + Range.Max = ImMax(v1,v2); + Constrain(); + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); + UpdateTransformCache(); + } + + inline void SetRange(const ImPlotRange& range) { + SetRange(range.Min, range.Max); + } + + inline void SetAspect(double unit_per_pix) { + double new_size = unit_per_pix * PixelSize(); + double delta = (new_size - Range.Size()) * 0.5; + if (IsLocked()) + return; + else if (IsLockedMin() && !IsLockedMax()) + SetRange(Range.Min, Range.Max + 2*delta); + else if (!IsLockedMin() && IsLockedMax()) + SetRange(Range.Min - 2*delta, Range.Max); + else + SetRange(Range.Min - delta, Range.Max + delta); + } + + inline float PixelSize() const { return ImAbs(PixelMax - PixelMin); } + + inline double GetAspect() const { return Range.Size() / PixelSize(); } + + inline void Constrain() { + Range.Min = ImConstrainNan(ImConstrainInf(Range.Min)); + Range.Max = ImConstrainNan(ImConstrainInf(Range.Max)); + if (Range.Min < ConstraintRange.Min) + Range.Min = ConstraintRange.Min; + if (Range.Max > ConstraintRange.Max) + Range.Max = ConstraintRange.Max; + double z = Range.Size(); + if (z < ConstraintZoom.Min) { + double delta = (ConstraintZoom.Min - z) * 0.5; + Range.Min -= delta; + Range.Max += delta; + } + if (z > ConstraintZoom.Max) { + double delta = (z - ConstraintZoom.Max) * 0.5; + Range.Min += delta; + Range.Max -= delta; + } + if (Range.Max <= Range.Min) + Range.Max = Range.Min + DBL_EPSILON; + } + + inline void UpdateTransformCache() { + ScaleToPixel = (PixelMax - PixelMin) / Range.Size(); + if (TransformForward != nullptr) { + ScaleMin = TransformForward(Range.Min, TransformData); + ScaleMax = TransformForward(Range.Max, TransformData); + } + else { + ScaleMin = Range.Min; + ScaleMax = Range.Max; + } + } + + inline float PlotToPixels(double plt) const { + if (TransformForward != nullptr) { + double s = TransformForward(plt, TransformData); + double t = (s - ScaleMin) / (ScaleMax - ScaleMin); + plt = Range.Min + Range.Size() * t; + } + return (float)(PixelMin + ScaleToPixel * (plt - Range.Min)); + } + + + inline double PixelsToPlot(float pix) const { + double plt = (pix - PixelMin) / ScaleToPixel + Range.Min; + if (TransformInverse != nullptr) { + double t = (plt - Range.Min) / Range.Size(); + double s = t * (ScaleMax - ScaleMin) + ScaleMin; + plt = TransformInverse(s, TransformData); + } + return plt; + } + + inline void ExtendFit(double v) { + if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) { + FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min; + FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max; + } + } + + inline void ExtendFitWith(ImPlotAxis& alt, double v, double v_alt) { + if (ImHasFlag(Flags, ImPlotAxisFlags_RangeFit) && !alt.Range.Contains(v_alt)) + return; + if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) { + FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min; + FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max; + } + } + + inline void ApplyFit(float padding) { + const double ext_size = FitExtents.Size() * 0.5; + FitExtents.Min -= ext_size * padding; + FitExtents.Max += ext_size * padding; + if (!IsLockedMin() && !ImNanOrInf(FitExtents.Min)) + Range.Min = FitExtents.Min; + if (!IsLockedMax() && !ImNanOrInf(FitExtents.Max)) + Range.Max = FitExtents.Max; + if (ImAlmostEqual(Range.Min, Range.Max)) { + Range.Max += 0.5; + Range.Min -= 0.5; + } + Constrain(); + UpdateTransformCache(); + } + + inline bool HasLabel() const { return LabelOffset != -1 && !ImHasFlag(Flags, ImPlotAxisFlags_NoLabel); } + inline bool HasGridLines() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoGridLines); } + inline bool HasTickLabels() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels); } + inline bool HasTickMarks() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickMarks); } + inline bool WillRender() const { return Enabled && (HasGridLines() || HasTickLabels() || HasTickMarks()); } + inline bool IsOpposite() const { return ImHasFlag(Flags, ImPlotAxisFlags_Opposite); } + inline bool IsInverted() const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert); } + inline bool IsForeground() const { return ImHasFlag(Flags, ImPlotAxisFlags_Foreground); } + inline bool IsAutoFitting() const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit); } + inline bool CanInitFit() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoInitialFit) && !HasRange && !LinkedMin && !LinkedMax; } + inline bool IsRangeLocked() const { return HasRange && RangeCond == ImPlotCond_Always; } + inline bool IsLockedMin() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMin); } + inline bool IsLockedMax() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMax); } + inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); } + inline bool IsInputLockedMin() const { return IsLockedMin() || IsAutoFitting(); } + inline bool IsInputLockedMax() const { return IsLockedMax() || IsAutoFitting(); } + inline bool IsInputLocked() const { return IsLocked() || IsAutoFitting(); } + inline bool HasMenus() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoMenus); } + + inline bool IsPanLocked(bool increasing) { + if (ImHasFlag(Flags, ImPlotAxisFlags_PanStretch)) { + return IsInputLocked(); + } + else { + if (IsLockedMin() || IsLockedMax() || IsAutoFitting()) + return false; + if (increasing) + return Range.Max == ConstraintRange.Max; + else + return Range.Min == ConstraintRange.Min; + } + } + + void PushLinks() { + if (LinkedMin) { *LinkedMin = Range.Min; } + if (LinkedMax) { *LinkedMax = Range.Max; } + } + + void PullLinks() { + if (LinkedMin) { SetMin(*LinkedMin,true); } + if (LinkedMax) { SetMax(*LinkedMax,true); } + } +}; + +// Align plots group data +struct ImPlotAlignmentData { + bool Vertical; + float PadA; + float PadB; + float PadAMax; + float PadBMax; + ImPlotAlignmentData() { + Vertical = true; + PadA = PadB = PadAMax = PadBMax = 0; + } + void Begin() { PadAMax = PadBMax = 0; } + void Update(float& pad_a, float& pad_b, float& delta_a, float& delta_b) { + float bak_a = pad_a; float bak_b = pad_b; + if (PadAMax < pad_a) { PadAMax = pad_a; } + if (PadBMax < pad_b) { PadBMax = pad_b; } + if (pad_a < PadA) { pad_a = PadA; delta_a = pad_a - bak_a; } else { delta_a = 0; } + if (pad_b < PadB) { pad_b = PadB; delta_b = pad_b - bak_b; } else { delta_b = 0; } + } + void End() { PadA = PadAMax; PadB = PadBMax; } + void Reset() { PadA = PadB = PadAMax = PadBMax = 0; } +}; + +// State information for Plot items +struct ImPlotItem +{ + ImGuiID ID; + ImU32 Color; + ImRect LegendHoverRect; + int NameOffset; + bool Show; + bool LegendHovered; + bool SeenThisFrame; + + ImPlotItem() { + ID = 0; + Color = IM_COL32_WHITE; + NameOffset = -1; + Show = true; + SeenThisFrame = false; + LegendHovered = false; + } + + ~ImPlotItem() { ID = 0; } +}; + +// Holds Legend state +struct ImPlotLegend +{ + ImPlotLegendFlags Flags; + ImPlotLegendFlags PreviousFlags; + ImPlotLocation Location; + ImPlotLocation PreviousLocation; + ImVector Indices; + ImGuiTextBuffer Labels; + ImRect Rect; + bool Hovered; + bool Held; + bool CanGoInside; + + ImPlotLegend() { + Flags = PreviousFlags = ImPlotLegendFlags_None; + CanGoInside = true; + Hovered = Held = false; + Location = PreviousLocation = ImPlotLocation_NorthWest; + } + + void Reset() { Indices.shrink(0); Labels.Buf.shrink(0); } +}; + +// Holds Items and Legend data +struct ImPlotItemGroup +{ + ImGuiID ID; + ImPlotLegend Legend; + ImPool ItemPool; + int ColormapIdx; + + ImPlotItemGroup() { ID = 0; ColormapIdx = 0; } + + int GetItemCount() const { return ItemPool.GetBufSize(); } + ImGuiID GetItemID(const char* label_id) { return ImGui::GetID(label_id); /* GetIDWithSeed */ } + ImPlotItem* GetItem(ImGuiID id) { return ItemPool.GetByKey(id); } + ImPlotItem* GetItem(const char* label_id) { return GetItem(GetItemID(label_id)); } + ImPlotItem* GetOrAddItem(ImGuiID id) { return ItemPool.GetOrAddByKey(id); } + ImPlotItem* GetItemByIndex(int i) { return ItemPool.GetByIndex(i); } + int GetItemIndex(ImPlotItem* item) { return ItemPool.GetIndex(item); } + int GetLegendCount() const { return Legend.Indices.size(); } + ImPlotItem* GetLegendItem(int i) { return ItemPool.GetByIndex(Legend.Indices[i]); } + const char* GetLegendLabel(int i) { return Legend.Labels.Buf.Data + GetLegendItem(i)->NameOffset; } + void Reset() { ItemPool.Clear(); Legend.Reset(); ColormapIdx = 0; } +}; + +// Holds Plot state information that must persist after EndPlot +struct ImPlotPlot +{ + ImGuiID ID; + ImPlotFlags Flags; + ImPlotFlags PreviousFlags; + ImPlotLocation MouseTextLocation; + ImPlotMouseTextFlags MouseTextFlags; + ImPlotAxis Axes[ImAxis_COUNT]; + ImGuiTextBuffer TextBuffer; + ImPlotItemGroup Items; + ImAxis CurrentX; + ImAxis CurrentY; + ImRect FrameRect; + ImRect CanvasRect; + ImRect PlotRect; + ImRect AxesRect; + ImRect SelectRect; + ImVec2 SelectStart; + int TitleOffset; + bool JustCreated; + bool Initialized; + bool SetupLocked; + bool FitThisFrame; + bool Hovered; + bool Held; + bool Selecting; + bool Selected; + bool ContextLocked; + + ImPlotPlot() { + Flags = PreviousFlags = ImPlotFlags_None; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) + XAxis(i).Vertical = false; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) + YAxis(i).Vertical = true; + SelectStart = ImVec2(0,0); + CurrentX = ImAxis_X1; + CurrentY = ImAxis_Y1; + MouseTextLocation = ImPlotLocation_South | ImPlotLocation_East; + MouseTextFlags = ImPlotMouseTextFlags_None; + TitleOffset = -1; + JustCreated = true; + Initialized = SetupLocked = FitThisFrame = false; + Hovered = Held = Selected = Selecting = ContextLocked = false; + } + + inline bool IsInputLocked() const { + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + if (!XAxis(i).IsInputLocked()) + return false; + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + if (!YAxis(i).IsInputLocked()) + return false; + } + return true; + } + + inline void ClearTextBuffer() { TextBuffer.Buf.shrink(0); } + + inline void SetTitle(const char* title) { + if (title && ImGui::FindRenderedTextEnd(title, nullptr) != title) { + TitleOffset = TextBuffer.size(); + TextBuffer.append(title, title + strlen(title) + 1); + } + else { + TitleOffset = -1; + } + } + inline bool HasTitle() const { return TitleOffset != -1 && !ImHasFlag(Flags, ImPlotFlags_NoTitle); } + inline const char* GetTitle() const { return TextBuffer.Buf.Data + TitleOffset; } + + inline ImPlotAxis& XAxis(int i) { return Axes[ImAxis_X1 + i]; } + inline const ImPlotAxis& XAxis(int i) const { return Axes[ImAxis_X1 + i]; } + inline ImPlotAxis& YAxis(int i) { return Axes[ImAxis_Y1 + i]; } + inline const ImPlotAxis& YAxis(int i) const { return Axes[ImAxis_Y1 + i]; } + + inline int EnabledAxesX() { + int cnt = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) + cnt += XAxis(i).Enabled; + return cnt; + } + + inline int EnabledAxesY() { + int cnt = 0; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) + cnt += YAxis(i).Enabled; + return cnt; + } + + inline void SetAxisLabel(ImPlotAxis& axis, const char* label) { + if (label && ImGui::FindRenderedTextEnd(label, nullptr) != label) { + axis.LabelOffset = TextBuffer.size(); + TextBuffer.append(label, label + strlen(label) + 1); + } + else { + axis.LabelOffset = -1; + } + } + + inline const char* GetAxisLabel(const ImPlotAxis& axis) const { return TextBuffer.Buf.Data + axis.LabelOffset; } +}; + +// Holds subplot data that must persist after EndSubplot +struct ImPlotSubplot { + ImGuiID ID; + ImPlotSubplotFlags Flags; + ImPlotSubplotFlags PreviousFlags; + ImPlotItemGroup Items; + int Rows; + int Cols; + int CurrentIdx; + ImRect FrameRect; + ImRect GridRect; + ImVec2 CellSize; + ImVector RowAlignmentData; + ImVector ColAlignmentData; + ImVector RowRatios; + ImVector ColRatios; + ImVector RowLinkData; + ImVector ColLinkData; + float TempSizes[2]; + bool FrameHovered; + bool HasTitle; + + ImPlotSubplot() { + ID = 0; + Flags = PreviousFlags = ImPlotSubplotFlags_None; + Rows = Cols = CurrentIdx = 0; + FrameHovered = false; + Items.Legend.Location = ImPlotLocation_North; + Items.Legend.Flags = ImPlotLegendFlags_Horizontal|ImPlotLegendFlags_Outside; + Items.Legend.CanGoInside = false; + TempSizes[0] = TempSizes[1] = 0; + FrameHovered = false; + HasTitle = false; + } +}; + +// Temporary data storage for upcoming plot +struct ImPlotNextPlotData +{ + ImPlotCond RangeCond[ImAxis_COUNT]; + ImPlotRange Range[ImAxis_COUNT]; + bool HasRange[ImAxis_COUNT]; + bool Fit[ImAxis_COUNT]; + double* LinkedMin[ImAxis_COUNT]; + double* LinkedMax[ImAxis_COUNT]; + + ImPlotNextPlotData() { Reset(); } + + void Reset() { + for (int i = 0; i < ImAxis_COUNT; ++i) { + HasRange[i] = false; + Fit[i] = false; + LinkedMin[i] = LinkedMax[i] = nullptr; + } + } + +}; + +// Temporary data storage for upcoming item +struct ImPlotNextItemData { + ImVec4 Colors[5]; // ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar + float LineWeight; + ImPlotMarker Marker; + float MarkerSize; + float MarkerWeight; + float FillAlpha; + float ErrorBarSize; + float ErrorBarWeight; + float DigitalBitHeight; + float DigitalBitGap; + bool RenderLine; + bool RenderFill; + bool RenderMarkerLine; + bool RenderMarkerFill; + bool HasHidden; + bool Hidden; + ImPlotCond HiddenCond; + ImPlotNextItemData() { Reset(); } + void Reset() { + for (int i = 0; i < 5; ++i) + Colors[i] = IMPLOT_AUTO_COL; + LineWeight = MarkerSize = MarkerWeight = FillAlpha = ErrorBarSize = ErrorBarWeight = DigitalBitHeight = DigitalBitGap = IMPLOT_AUTO; + Marker = IMPLOT_AUTO; + HasHidden = Hidden = false; + } +}; + +// Holds state information that must persist between calls to BeginPlot()/EndPlot() +struct ImPlotContext { + // Plot States + ImPool Plots; + ImPool Subplots; + ImPlotPlot* CurrentPlot; + ImPlotSubplot* CurrentSubplot; + ImPlotItemGroup* CurrentItems; + ImPlotItem* CurrentItem; + ImPlotItem* PreviousItem; + + // Tick Marks and Labels + ImPlotTicker CTicker; + + // Annotation and Tabs + ImPlotAnnotationCollection Annotations; + ImPlotTagCollection Tags; + + // Flags + bool ChildWindowMade; + + // Style and Colormaps + ImPlotStyle Style; + ImVector ColorModifiers; + ImVector StyleModifiers; + ImPlotColormapData ColormapData; + ImVector ColormapModifiers; + + // Time + tm Tm; + + // Temp data for general use + ImVector TempDouble1, TempDouble2; + ImVector TempInt1; + + // Misc + int DigitalPlotItemCnt; + int DigitalPlotOffset; + ImPlotNextPlotData NextPlotData; + ImPlotNextItemData NextItemData; + ImPlotInputMap InputMap; + bool OpenContextThisFrame; + ImGuiTextBuffer MousePosStringBuilder; + ImPlotItemGroup* SortItems; + + // Align plots + ImPool AlignmentData; + ImPlotAlignmentData* CurrentAlignmentH; + ImPlotAlignmentData* CurrentAlignmentV; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Internal API +// No guarantee of forward compatibility here! +//----------------------------------------------------------------------------- + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Context Utils +//----------------------------------------------------------------------------- + +// Initializes an ImPlotContext +IMPLOT_API void Initialize(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginPlot +IMPLOT_API void ResetCtxForNextPlot(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginAlignedPlots +IMPLOT_API void ResetCtxForNextAlignedPlots(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginSubplot +IMPLOT_API void ResetCtxForNextSubplot(ImPlotContext* ctx); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +// Gets a plot from the current ImPlotContext +IMPLOT_API ImPlotPlot* GetPlot(const char* title); +// Gets the current plot from the current ImPlotContext +IMPLOT_API ImPlotPlot* GetCurrentPlot(); +// Busts the cache for every plot in the current context +IMPLOT_API void BustPlotCache(); + +// Shows a plot's context menu. +IMPLOT_API void ShowPlotContextMenu(ImPlotPlot& plot); + +//----------------------------------------------------------------------------- +// [SECTION] Setup Utils +//----------------------------------------------------------------------------- + +// Lock Setup and call SetupFinish if necessary. +static inline void SetupLock() { + ImPlotContext& gp = *GImPlot; + if (!gp.CurrentPlot->SetupLocked) + SetupFinish(); + gp.CurrentPlot->SetupLocked = true; +} + +//----------------------------------------------------------------------------- +// [SECTION] Subplot Utils +//----------------------------------------------------------------------------- + +// Advances to next subplot +IMPLOT_API void SubplotNextCell(); + +// Shows a subplot's context menu. +IMPLOT_API void ShowSubplotsContextMenu(ImPlotSubplot& subplot); + +//----------------------------------------------------------------------------- +// [SECTION] Item Utils +//----------------------------------------------------------------------------- + +// Begins a new item. Returns false if the item should not be plotted. Pushes PlotClipRect. +IMPLOT_API bool BeginItem(const char* label_id, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO); + +// Same as above but with fitting functionality. +template +bool BeginItemEx(const char* label_id, const _Fitter& fitter, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO) { + if (BeginItem(label_id, flags, recolor_from)) { + ImPlotPlot& plot = *GetCurrentPlot(); + if (plot.FitThisFrame && !ImHasFlag(flags, ImPlotItemFlags_NoFit)) + fitter.Fit(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]); + return true; + } + return false; +} + +// Ends an item (call only if BeginItem returns true). Pops PlotClipRect. +IMPLOT_API void EndItem(); + +// Register or get an existing item from the current plot. +IMPLOT_API ImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created = nullptr); +// Get a plot item from the current plot. +IMPLOT_API ImPlotItem* GetItem(const char* label_id); +// Gets the current item. +IMPLOT_API ImPlotItem* GetCurrentItem(); +// Busts the cache for every item for every plot in the current context. +IMPLOT_API void BustItemCache(); + +//----------------------------------------------------------------------------- +// [SECTION] Axis Utils +//----------------------------------------------------------------------------- + +// Returns true if any enabled axis is locked from user input. +static inline bool AnyAxesInputLocked(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].IsInputLocked()) + return true; + } + return false; +} + +// Returns true if all enabled axes are locked from user input. +static inline bool AllAxesInputLocked(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && !axes[i].IsInputLocked()) + return false; + } + return true; +} + +static inline bool AnyAxesHeld(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].Held) + return true; + } + return false; +} + +static inline bool AnyAxesHovered(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].Hovered) + return true; + } + return false; +} + +// Returns true if the user has requested data to be fit. +static inline bool FitThisFrame() { + return GImPlot->CurrentPlot->FitThisFrame; +} + +// Extends the current plot's axes so that it encompasses a vertical line at x +static inline void FitPointX(double x) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& x_axis = plot.Axes[plot.CurrentX]; + x_axis.ExtendFit(x); +} + +// Extends the current plot's axes so that it encompasses a horizontal line at y +static inline void FitPointY(double y) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& y_axis = plot.Axes[plot.CurrentY]; + y_axis.ExtendFit(y); +} + +// Extends the current plot's axes so that it encompasses point p +static inline void FitPoint(const ImPlotPoint& p) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& x_axis = plot.Axes[plot.CurrentX]; + ImPlotAxis& y_axis = plot.Axes[plot.CurrentY]; + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); +} + +// Returns true if two ranges overlap +static inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) +{ return r1.Min <= r2.Max && r2.Min <= r1.Max; } + +// Shows an axis's context menu. +IMPLOT_API void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed = false); + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils +//----------------------------------------------------------------------------- + +// Gets the position of an inner rect that is located inside of an outer rect according to an ImPlotLocation and padding amount. +IMPLOT_API ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation location, const ImVec2& pad = ImVec2(0,0)); +// Calculates the bounding box size of a legend +IMPLOT_API ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical); +// Renders legend entries into a bounding box +IMPLOT_API bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool interactable, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList); +// Shows an alternate legend for the plot identified by #title_id, outside of the plot frame (can be called before or after of Begin/EndPlot but must occur in the same ImGui window!). +IMPLOT_API void ShowAltLegend(const char* title_id, bool vertical = true, const ImVec2 size = ImVec2(0,0), bool interactable = true); +// Shows an legends's context menu. +IMPLOT_API bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible); + +//----------------------------------------------------------------------------- +// [SECTION] Label Utils +//----------------------------------------------------------------------------- + +// Create a a string label for a an axis value +IMPLOT_API void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round = false); + +//----------------------------------------------------------------------------- +// [SECTION] Styling Utils +//----------------------------------------------------------------------------- + +// Get styling data for next item (call between Begin/EndItem) +static inline const ImPlotNextItemData& GetItemData() { return GImPlot->NextItemData; } + +// Returns true if a color is set to be automatically determined +static inline bool IsColorAuto(const ImVec4& col) { return col.w == -1; } +// Returns true if a style color is set to be automatically determined +static inline bool IsColorAuto(ImPlotCol idx) { return IsColorAuto(GImPlot->Style.Colors[idx]); } +// Returns the automatically deduced style color +IMPLOT_API ImVec4 GetAutoColor(ImPlotCol idx); + +// Returns the style color whether it is automatic or custom set +static inline ImVec4 GetStyleColorVec4(ImPlotCol idx) { return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot->Style.Colors[idx]; } +static inline ImU32 GetStyleColorU32(ImPlotCol idx) { return ImGui::ColorConvertFloat4ToU32(GetStyleColorVec4(idx)); } + +// Draws vertical text. The position is the bottom left of the text rect. +IMPLOT_API void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char* text_begin, const char* text_end = nullptr); +// Draws multiline horizontal text centered. +IMPLOT_API void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end = nullptr); +// Calculates the size of vertical text +static inline ImVec2 CalcTextSizeVertical(const char *text) { + ImVec2 sz = ImGui::CalcTextSize(text); + return ImVec2(sz.y, sz.x); +} +// Returns white or black text given background color +static inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299f + bg.y * 0.587f + bg.z * 0.114f) > 0.5f ? IM_COL32_BLACK : IM_COL32_WHITE; } +static inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); } +// Lightens or darkens a color for hover +static inline ImU32 CalcHoverColor(ImU32 col) { return ImMixU32(col, CalcTextColor(col), 32); } + +// Clamps a label position so that it fits a rect defined by Min/Max +static inline ImVec2 ClampLabelPos(ImVec2 pos, const ImVec2& size, const ImVec2& Min, const ImVec2& Max) { + if (pos.x < Min.x) pos.x = Min.x; + if (pos.y < Min.y) pos.y = Min.y; + if ((pos.x + size.x) > Max.x) pos.x = Max.x - size.x; + if ((pos.y + size.y) > Max.y) pos.y = Max.y - size.y; + return pos; +} + +// Returns a color from the Color map given an index >= 0 (modulo will be performed). +IMPLOT_API ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap); +// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. +IMPLOT_API ImU32 NextColormapColorU32(); +// Linearly interpolates a color from the current colormap given t between 0 and 1. +IMPLOT_API ImU32 SampleColormapU32(float t, ImPlotColormap cmap); + +// Render a colormap bar +IMPLOT_API void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous); + +//----------------------------------------------------------------------------- +// [SECTION] Math and Misc Utils +//----------------------------------------------------------------------------- + +// Rounds x to powers of 2,5 and 10 for generating axis labels (from Graphics Gems 1 Chapter 11.2) +IMPLOT_API double NiceNum(double x, bool round); +// Computes order of magnitude of double. +static inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); } +// Returns the precision required for a order of magnitude. +static inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; } +// Returns a floating point precision to use given a value +static inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); } +// Round a value to a given precision +static inline double RoundTo(double val, int prec) { double p = pow(10,(double)prec); return floor(val*p+0.5)/p; } + +// Returns the intersection point of two lines A and B (assumes they are not parallel!) +static inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) { + float v1 = (a1.x * a2.y - a1.y * a2.x); float v2 = (b1.x * b2.y - b1.y * b2.x); + float v3 = ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)); + return ImVec2((v1 * (b1.x - b2.x) - v2 * (a1.x - a2.x)) / v3, (v1 * (b1.y - b2.y) - v2 * (a1.y - a2.y)) / v3); +} + +// Fills a buffer with n samples linear interpolated from vmin to vmax +template +void FillRange(ImVector& buffer, int n, T vmin, T vmax) { + buffer.resize(n); + T step = (vmax - vmin) / (n - 1); + for (int i = 0; i < n; ++i) { + buffer[i] = vmin + i * step; + } +} + +// Calculate histogram bin counts and widths +template +static inline void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { + switch (meth) { + case ImPlotBin_Sqrt: + bins_out = (int)ceil(sqrt(count)); + break; + case ImPlotBin_Sturges: + bins_out = (int)ceil(1.0 + log2(count)); + break; + case ImPlotBin_Rice: + bins_out = (int)ceil(2 * cbrt(count)); + break; + case ImPlotBin_Scott: + width_out = 3.49 * ImStdDev(values, count) / cbrt(count); + bins_out = (int)round(range.Size() / width_out); + break; + } + width_out = range.Size() / bins_out; +} + +//----------------------------------------------------------------------------- +// Time Utils +//----------------------------------------------------------------------------- + +// Returns true if year is leap year (366 days long) +static inline bool IsLeapYear(int year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} +// Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed. +static inline int GetDaysInMonth(int year, int month) { + static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + return days[month] + (int)(month == 1 && IsLeapYear(year)); +} + +// Make a UNIX timestamp from a tm struct expressed in UTC time (i.e. GMT timezone). +IMPLOT_API ImPlotTime MkGmtTime(struct tm *ptm); +// Make a tm struct expressed in UTC time (i.e. GMT timezone) from a UNIX timestamp. +IMPLOT_API tm* GetGmtTime(const ImPlotTime& t, tm* ptm); + +// Make a UNIX timestamp from a tm struct expressed in local time. +IMPLOT_API ImPlotTime MkLocTime(struct tm *ptm); +// Make a tm struct expressed in local time from a UNIX timestamp. +IMPLOT_API tm* GetLocTime(const ImPlotTime& t, tm* ptm); + +// NB: The following functions only work if there is a current ImPlotContext because the +// internal tm struct is owned by the context! They are aware of ImPlotStyle.UseLocalTime. + +// Make a timestamp from time components. +// year[1970-3000], month[0-11], day[1-31], hour[0-23], min[0-59], sec[0-59], us[0,999999] +IMPLOT_API ImPlotTime MakeTime(int year, int month = 0, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0); +// Get year component from timestamp [1970-3000] +IMPLOT_API int GetYear(const ImPlotTime& t); + +// Adds or subtracts time from a timestamp. #count > 0 to add, < 0 to subtract. +IMPLOT_API ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); +// Rounds a timestamp down to nearest unit. +IMPLOT_API ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up to the nearest unit. +IMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up or down to the nearest unit. +IMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Combines the date of one timestamp with the time-of-day of another timestamp. +IMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& time_part); + +// Formats the time part of timestamp t into a buffer according to #fmt +IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk); +// Formats the date part of timestamp t into a buffer according to #fmt +IMPLOT_API int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601); +// Formats the time and/or date parts of a timestamp t into a buffer according to #fmt +IMPLOT_API int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt); + +// Shows a date picker widget block (year/month/day). +// #level = 0 for day, 1 for month, 2 for year. Modified by user interaction. +// #t will be set when a day is clicked and the function will return true. +// #t1 and #t2 are optional dates to highlight. +IMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1 = nullptr, const ImPlotTime* t2 = nullptr); +// Shows a time picker widget block (hour/min/sec). +// #t will be set when a new hour, minute, or sec is selected or am/pm is toggled, and the function will return true. +IMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t); + +//----------------------------------------------------------------------------- +// [SECTION] Transforms +//----------------------------------------------------------------------------- + +static inline double TransformForward_Log10(double v, void*) { + v = v <= 0.0 ? DBL_MIN : v; + return ImLog10(v); +} + +static inline double TransformInverse_Log10(double v, void*) { + return ImPow(10, v); +} + +static inline double TransformForward_SymLog(double v, void*) { + return 2.0 * ImAsinh(v / 2.0); +} + +static inline double TransformInverse_SymLog(double v, void*) { + return 2.0 * ImSinh(v / 2.0); +} + +static inline double TransformForward_Logit(double v, void*) { + v = ImClamp(v, DBL_MIN, 1.0 - DBL_EPSILON); + return ImLog10(v / (1 - v)); +} + +static inline double TransformInverse_Logit(double v, void*) { + return 1.0 / (1.0 + ImPow(10,-v)); +} + +//----------------------------------------------------------------------------- +// [SECTION] Formatters +//----------------------------------------------------------------------------- + +static inline int Formatter_Default(double value, char* buff, int size, void* data) { + char* fmt = (char*)data; + return ImFormatString(buff, size, fmt, value); +} + +static inline int Formatter_Logit(double value, char* buff, int size, void*) { + if (value == 0.5) + return ImFormatString(buff,size,"1/2"); + else if (value < 0.5) + return ImFormatString(buff,size,"%g", value); + else + return ImFormatString(buff,size,"1 - %g", 1 - value); +} + +struct Formatter_Time_Data { + ImPlotTime Time; + ImPlotDateTimeSpec Spec; + ImPlotFormatter UserFormatter; + void* UserFormatterData; +}; + +static inline int Formatter_Time(double, char* buff, int size, void* data) { + Formatter_Time_Data* ftd = (Formatter_Time_Data*)data; + return FormatDateTime(ftd->Time, buff, size, ftd->Spec); +} + +//------------------------------------------------------------------------------ +// [SECTION] Locator +//------------------------------------------------------------------------------ + +void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); + +} // namespace ImPlot diff --git a/crogine/src/imgui/implot_items.cpp b/crogine/src/imgui/implot_items.cpp new file mode 100644 index 000000000..a9f81c7d6 --- /dev/null +++ b/crogine/src/imgui/implot_items.cpp @@ -0,0 +1,2692 @@ +// MIT License + +// Copyright (c) 2020 Evan Pezent + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// ImPlot v0.14 + +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include "implot_internal.h" + +//----------------------------------------------------------------------------- +// [SECTION] Macros and Defines +//----------------------------------------------------------------------------- + +#define SQRT_1_2 0.70710678118f +#define SQRT_3_2 0.86602540378f + +#ifndef IMPLOT_NO_FORCE_INLINE + #ifdef _MSC_VER + #define IMPLOT_INLINE __forceinline + #elif defined(__GNUC__) + #define IMPLOT_INLINE inline __attribute__((__always_inline__)) + #elif defined(__CLANG__) + #if __has_attribute(__always_inline__) + #define IMPLOT_INLINE inline __attribute__((__always_inline__)) + #else + #define IMPLOT_INLINE inline + #endif + #else + #define IMPLOT_INLINE inline + #endif +#else + #define IMPLOT_INLINE inline +#endif + +#if defined __SSE__ || defined __x86_64__ || defined _M_X64 +#ifndef IMGUI_ENABLE_SSE +#include +#endif +static IMPLOT_INLINE float ImInvSqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +#else +static IMPLOT_INLINE float ImInvSqrt(float x) { return 1.0f / sqrtf(x); } +#endif + +#define IMPLOT_NORMALIZE2F_OVER_ZERO(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImInvSqrt(d2); VX *= inv_len; VY *= inv_len; } } while (0) + +// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean "all corners" but in order to support older versions we are more explicit. +#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll) +#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Template instantiation utility +//----------------------------------------------------------------------------- + +// By default, templates are instantiated for `float`, `double`, and for the following integer types, which are defined in imgui.h: +// signed char ImS8; // 8-bit signed integer +// unsigned char ImU8; // 8-bit unsigned integer +// signed short ImS16; // 16-bit signed integer +// unsigned short ImU16; // 16-bit unsigned integer +// signed int ImS32; // 32-bit signed integer == int +// unsigned int ImU32; // 32-bit unsigned integer +// signed long long ImS64; // 64-bit signed integer +// unsigned long long ImU64; // 64-bit unsigned integer +// (note: this list does *not* include `long`, `unsigned long` and `long double`) +// +// You can customize the supported types by defining IMPLOT_CUSTOM_NUMERIC_TYPES at compile time to define your own type list. +// As an example, you could use the compile time define given by the line below in order to support only float and double. +// -DIMPLOT_CUSTOM_NUMERIC_TYPES="(float)(double)" +// In order to support all known C++ types, use: +// -DIMPLOT_CUSTOM_NUMERIC_TYPES="(signed char)(unsigned char)(signed short)(unsigned short)(signed int)(unsigned int)(signed long)(unsigned long)(signed long long)(unsigned long long)(float)(double)(long double)" + +#ifdef IMPLOT_CUSTOM_NUMERIC_TYPES + #define IMPLOT_NUMERIC_TYPES IMPLOT_CUSTOM_NUMERIC_TYPES +#else + #define IMPLOT_NUMERIC_TYPES (ImS8)(ImU8)(ImS16)(ImU16)(ImS32)(ImU32)(ImS64)(ImU64)(float)(double) +#endif + +// CALL_INSTANTIATE_FOR_NUMERIC_TYPES will duplicate the template instantion code `INSTANTIATE_MACRO(T)` on supported types. +#define _CAT(x, y) _CAT_(x, y) +#define _CAT_(x,y) x ## y +#define _INSTANTIATE_FOR_NUMERIC_TYPES(chain) _CAT(_INSTANTIATE_FOR_NUMERIC_TYPES_1 chain, _END) +#define _INSTANTIATE_FOR_NUMERIC_TYPES_1(T) INSTANTIATE_MACRO(T); _INSTANTIATE_FOR_NUMERIC_TYPES_2 +#define _INSTANTIATE_FOR_NUMERIC_TYPES_2(T) INSTANTIATE_MACRO(T); _INSTANTIATE_FOR_NUMERIC_TYPES_1 +#define _INSTANTIATE_FOR_NUMERIC_TYPES_1_END +#define _INSTANTIATE_FOR_NUMERIC_TYPES_2_END +#define CALL_INSTANTIATE_FOR_NUMERIC_TYPES() _INSTANTIATE_FOR_NUMERIC_TYPES(IMPLOT_NUMERIC_TYPES); + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Utils +//----------------------------------------------------------------------------- + +// Calc maximum index size of ImDrawIdx +template +struct MaxIdx { static const unsigned int Value; }; +template <> const unsigned int MaxIdx::Value = 65535; +template <> const unsigned int MaxIdx::Value = 4294967295; + +IMPLOT_INLINE void GetLineRenderProps(const ImDrawList& draw_list, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) { + const bool aa = ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLines) && + ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLinesUseTex); + if (aa) { + ImVec4 tex_uvs = draw_list._Data->TexUvLines[(int)(half_weight*2)]; + tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y); + tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w); + half_weight += 1; + } + else { + tex_uv0 = tex_uv1 = draw_list._Data->TexUvWhitePixel; + } +} + +IMPLOT_INLINE void PrimLine(ImDrawList& draw_list, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, const ImVec2 tex_uv1) { + float dx = P2.x - P1.x; + float dy = P2.y - P1.y; + IMPLOT_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= half_weight; + dy *= half_weight; + draw_list._VtxWritePtr[0].pos.x = P1.x + dy; + draw_list._VtxWritePtr[0].pos.y = P1.y - dx; + draw_list._VtxWritePtr[0].uv = tex_uv0; + draw_list._VtxWritePtr[0].col = col; + draw_list._VtxWritePtr[1].pos.x = P2.x + dy; + draw_list._VtxWritePtr[1].pos.y = P2.y - dx; + draw_list._VtxWritePtr[1].uv = tex_uv0; + draw_list._VtxWritePtr[1].col = col; + draw_list._VtxWritePtr[2].pos.x = P2.x - dy; + draw_list._VtxWritePtr[2].pos.y = P2.y + dx; + draw_list._VtxWritePtr[2].uv = tex_uv1; + draw_list._VtxWritePtr[2].col = col; + draw_list._VtxWritePtr[3].pos.x = P1.x - dy; + draw_list._VtxWritePtr[3].pos.y = P1.y + dx; + draw_list._VtxWritePtr[3].uv = tex_uv1; + draw_list._VtxWritePtr[3].col = col; + draw_list._VtxWritePtr += 4; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 4; +} + +IMPLOT_INLINE void PrimRectFill(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, ImU32 col, const ImVec2& uv) { + draw_list._VtxWritePtr[0].pos = Pmin; + draw_list._VtxWritePtr[0].uv = uv; + draw_list._VtxWritePtr[0].col = col; + draw_list._VtxWritePtr[1].pos = Pmax; + draw_list._VtxWritePtr[1].uv = uv; + draw_list._VtxWritePtr[1].col = col; + draw_list._VtxWritePtr[2].pos.x = Pmin.x; + draw_list._VtxWritePtr[2].pos.y = Pmax.y; + draw_list._VtxWritePtr[2].uv = uv; + draw_list._VtxWritePtr[2].col = col; + draw_list._VtxWritePtr[3].pos.x = Pmax.x; + draw_list._VtxWritePtr[3].pos.y = Pmin.y; + draw_list._VtxWritePtr[3].uv = uv; + draw_list._VtxWritePtr[3].col = col; + draw_list._VtxWritePtr += 4; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 4; +} + +IMPLOT_INLINE void PrimRectLine(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, float weight, ImU32 col, const ImVec2& uv) { + + draw_list._VtxWritePtr[0].pos.x = Pmin.x; + draw_list._VtxWritePtr[0].pos.y = Pmin.y; + draw_list._VtxWritePtr[0].uv = uv; + draw_list._VtxWritePtr[0].col = col; + + draw_list._VtxWritePtr[1].pos.x = Pmin.x; + draw_list._VtxWritePtr[1].pos.y = Pmax.y; + draw_list._VtxWritePtr[1].uv = uv; + draw_list._VtxWritePtr[1].col = col; + + draw_list._VtxWritePtr[2].pos.x = Pmax.x; + draw_list._VtxWritePtr[2].pos.y = Pmax.y; + draw_list._VtxWritePtr[2].uv = uv; + draw_list._VtxWritePtr[2].col = col; + + draw_list._VtxWritePtr[3].pos.x = Pmax.x; + draw_list._VtxWritePtr[3].pos.y = Pmin.y; + draw_list._VtxWritePtr[3].uv = uv; + draw_list._VtxWritePtr[3].col = col; + + draw_list._VtxWritePtr[4].pos.x = Pmin.x + weight; + draw_list._VtxWritePtr[4].pos.y = Pmin.y + weight; + draw_list._VtxWritePtr[4].uv = uv; + draw_list._VtxWritePtr[4].col = col; + + draw_list._VtxWritePtr[5].pos.x = Pmin.x + weight; + draw_list._VtxWritePtr[5].pos.y = Pmax.y - weight; + draw_list._VtxWritePtr[5].uv = uv; + draw_list._VtxWritePtr[5].col = col; + + draw_list._VtxWritePtr[6].pos.x = Pmax.x - weight; + draw_list._VtxWritePtr[6].pos.y = Pmax.y - weight; + draw_list._VtxWritePtr[6].uv = uv; + draw_list._VtxWritePtr[6].col = col; + + draw_list._VtxWritePtr[7].pos.x = Pmax.x - weight; + draw_list._VtxWritePtr[7].pos.y = Pmin.y + weight; + draw_list._VtxWritePtr[7].uv = uv; + draw_list._VtxWritePtr[7].col = col; + + draw_list._VtxWritePtr += 8; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr += 3; + + draw_list._VtxCurrentIdx += 8; +} + + +//----------------------------------------------------------------------------- +// [SECTION] Item Utils +//----------------------------------------------------------------------------- + +ImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created) { + ImPlotContext& gp = *GImPlot; + ImPlotItemGroup& Items = *gp.CurrentItems; + ImGuiID id = Items.GetItemID(label_id); + if (just_created != nullptr) + *just_created = Items.GetItem(id) == nullptr; + ImPlotItem* item = Items.GetOrAddItem(id); + if (item->SeenThisFrame) + return item; + item->SeenThisFrame = true; + int idx = Items.GetItemIndex(item); + item->ID = id; + if (!ImHasFlag(flags, ImPlotItemFlags_NoLegend) && ImGui::FindRenderedTextEnd(label_id, nullptr) != label_id) { + Items.Legend.Indices.push_back(idx); + item->NameOffset = Items.Legend.Labels.size(); + Items.Legend.Labels.append(label_id, label_id + strlen(label_id) + 1); + } + else { + item->Show = true; + } + return item; +} + +ImPlotItem* GetItem(const char* label_id) { + ImPlotContext& gp = *GImPlot; + return gp.CurrentItems->GetItem(label_id); +} + +bool IsItemHidden(const char* label_id) { + ImPlotItem* item = GetItem(label_id); + return item != nullptr && !item->Show; +} + +ImPlotItem* GetCurrentItem() { + ImPlotContext& gp = *GImPlot; + return gp.CurrentItem; +} + +void SetNextLineStyle(const ImVec4& col, float weight) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_Line] = col; + gp.NextItemData.LineWeight = weight; +} + +void SetNextFillStyle(const ImVec4& col, float alpha) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_Fill] = col; + gp.NextItemData.FillAlpha = alpha; +} + +void SetNextMarkerStyle(ImPlotMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Marker = marker; + gp.NextItemData.Colors[ImPlotCol_MarkerFill] = fill; + gp.NextItemData.MarkerSize = size; + gp.NextItemData.Colors[ImPlotCol_MarkerOutline] = outline; + gp.NextItemData.MarkerWeight = weight; +} + +void SetNextErrorBarStyle(const ImVec4& col, float size, float weight) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_ErrorBar] = col; + gp.NextItemData.ErrorBarSize = size; + gp.NextItemData.ErrorBarWeight = weight; +} + +ImVec4 GetLastItemColor() { + ImPlotContext& gp = *GImPlot; + if (gp.PreviousItem) + return ImGui::ColorConvertU32ToFloat4(gp.PreviousItem->Color); + return ImVec4(); +} + +void BustItemCache() { + ImPlotContext& gp = *GImPlot; + for (int p = 0; p < gp.Plots.GetBufSize(); ++p) { + ImPlotPlot& plot = *gp.Plots.GetByIndex(p); + plot.Items.Reset(); + } + for (int p = 0; p < gp.Subplots.GetBufSize(); ++p) { + ImPlotSubplot& subplot = *gp.Subplots.GetByIndex(p); + subplot.Items.Reset(); + } +} + +void BustColorCache(const char* plot_title_id) { + ImPlotContext& gp = *GImPlot; + if (plot_title_id == nullptr) { + BustItemCache(); + } + else { + ImGuiID id = ImGui::GetCurrentWindow()->GetID(plot_title_id); + ImPlotPlot* plot = gp.Plots.GetByKey(id); + if (plot != nullptr) + plot->Items.Reset(); + else { + ImPlotSubplot* subplot = gp.Subplots.GetByKey(id); + if (subplot != nullptr) + subplot->Items.Reset(); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] BeginItem / EndItem +//----------------------------------------------------------------------------- + +static const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f; +static const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f; + +// Begins a new item. Returns false if the item should not be plotted. +bool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_from) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotX() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + bool just_created; + ImPlotItem* item = RegisterOrGetItem(label_id, flags, &just_created); + // set current item + gp.CurrentItem = item; + ImPlotNextItemData& s = gp.NextItemData; + // set/override item color + if (recolor_from != -1) { + if (!IsColorAuto(s.Colors[recolor_from])) + item->Color = ImGui::ColorConvertFloat4ToU32(s.Colors[recolor_from]); + else if (!IsColorAuto(gp.Style.Colors[recolor_from])) + item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]); + else if (just_created) + item->Color = NextColormapColorU32(); + } + else if (just_created) { + item->Color = NextColormapColorU32(); + } + // hide/show item + if (gp.NextItemData.HasHidden) { + if (just_created || gp.NextItemData.HiddenCond == ImGuiCond_Always) + item->Show = !gp.NextItemData.Hidden; + } + if (!item->Show) { + // reset next item data + gp.NextItemData.Reset(); + gp.PreviousItem = item; + gp.CurrentItem = nullptr; + return false; + } + else { + ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color); + // stage next item colors + s.Colors[ImPlotCol_Line] = IsColorAuto(s.Colors[ImPlotCol_Line]) ? ( IsColorAuto(ImPlotCol_Line) ? item_color : gp.Style.Colors[ImPlotCol_Line] ) : s.Colors[ImPlotCol_Line]; + s.Colors[ImPlotCol_Fill] = IsColorAuto(s.Colors[ImPlotCol_Fill]) ? ( IsColorAuto(ImPlotCol_Fill) ? item_color : gp.Style.Colors[ImPlotCol_Fill] ) : s.Colors[ImPlotCol_Fill]; + s.Colors[ImPlotCol_MarkerOutline] = IsColorAuto(s.Colors[ImPlotCol_MarkerOutline]) ? ( IsColorAuto(ImPlotCol_MarkerOutline) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerOutline] ) : s.Colors[ImPlotCol_MarkerOutline]; + s.Colors[ImPlotCol_MarkerFill] = IsColorAuto(s.Colors[ImPlotCol_MarkerFill]) ? ( IsColorAuto(ImPlotCol_MarkerFill) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerFill] ) : s.Colors[ImPlotCol_MarkerFill]; + s.Colors[ImPlotCol_ErrorBar] = IsColorAuto(s.Colors[ImPlotCol_ErrorBar]) ? ( GetStyleColorVec4(ImPlotCol_ErrorBar) ) : s.Colors[ImPlotCol_ErrorBar]; + // stage next item style vars + s.LineWeight = s.LineWeight < 0 ? gp.Style.LineWeight : s.LineWeight; + s.Marker = s.Marker < 0 ? gp.Style.Marker : s.Marker; + s.MarkerSize = s.MarkerSize < 0 ? gp.Style.MarkerSize : s.MarkerSize; + s.MarkerWeight = s.MarkerWeight < 0 ? gp.Style.MarkerWeight : s.MarkerWeight; + s.FillAlpha = s.FillAlpha < 0 ? gp.Style.FillAlpha : s.FillAlpha; + s.ErrorBarSize = s.ErrorBarSize < 0 ? gp.Style.ErrorBarSize : s.ErrorBarSize; + s.ErrorBarWeight = s.ErrorBarWeight < 0 ? gp.Style.ErrorBarWeight : s.ErrorBarWeight; + s.DigitalBitHeight = s.DigitalBitHeight < 0 ? gp.Style.DigitalBitHeight : s.DigitalBitHeight; + s.DigitalBitGap = s.DigitalBitGap < 0 ? gp.Style.DigitalBitGap : s.DigitalBitGap; + // apply alpha modifier(s) + s.Colors[ImPlotCol_Fill].w *= s.FillAlpha; + s.Colors[ImPlotCol_MarkerFill].w *= s.FillAlpha; // TODO: this should be separate, if it at all + // apply highlight mods + if (item->LegendHovered) { + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightItem)) { + s.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + s.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE; + s.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + // TODO: how to highlight fills? + } + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)) { + if (gp.CurrentPlot->EnabledAxesX() > 1) + gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX].ColorHiLi = item->Color; + if (gp.CurrentPlot->EnabledAxesY() > 1) + gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY].ColorHiLi = item->Color; + } + } + // set render flags + s.RenderLine = s.Colors[ImPlotCol_Line].w > 0 && s.LineWeight > 0; + s.RenderFill = s.Colors[ImPlotCol_Fill].w > 0; + s.RenderMarkerFill = s.Colors[ImPlotCol_MarkerFill].w > 0; + s.RenderMarkerLine = s.Colors[ImPlotCol_MarkerOutline].w > 0 && s.MarkerWeight > 0; + // push rendering clip rect + PushPlotClipRect(); + return true; + } +} + +// Ends an item (call only if BeginItem returns true) +void EndItem() { + ImPlotContext& gp = *GImPlot; + // pop rendering clip rect + PopPlotClipRect(); + // reset next item data + gp.NextItemData.Reset(); + // set current item + gp.PreviousItem = gp.CurrentItem; + gp.CurrentItem = nullptr; +} + +//----------------------------------------------------------------------------- +// [SECTION] Indexers +//----------------------------------------------------------------------------- + +template +IMPLOT_INLINE T IndexData(const T* data, int idx, int count, int offset, int stride) { + const int s = ((offset == 0) << 0) | ((stride == sizeof(T)) << 1); + switch (s) { + case 3 : return data[idx]; + case 2 : return data[(offset + idx) % count]; + case 1 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((idx) ) * stride); + case 0 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((offset + idx) % count) * stride); + default: return T(0); + } +} + +template +struct IndexerIdx { + IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : + Data(data), + Count(count), + Offset(count ? ImPosMod(offset, count) : 0), + Stride(stride) + { } + template IMPLOT_INLINE double operator()(I idx) const { + return (double)IndexData(Data, idx, Count, Offset, Stride); + } + const T* Data; + int Count; + int Offset; + int Stride; +}; + +template +struct IndexerAdd { + IndexerAdd(const _Indexer1& indexer1, const _Indexer2& indexer2, double scale1 = 1, double scale2 = 1) + : Indexer1(indexer1), + Indexer2(indexer2), + Scale1(scale1), + Scale2(scale2), + Count(ImMin(Indexer1.Count, Indexer2.Count)) + { } + template IMPLOT_INLINE double operator()(I idx) const { + return Scale1 * Indexer1(idx) + Scale2 * Indexer2(idx); + } + const _Indexer1& Indexer1; + const _Indexer2& Indexer2; + double Scale1; + double Scale2; + int Count; +}; + +struct IndexerLin { + IndexerLin(double m, double b) : M(m), B(b) { } + template IMPLOT_INLINE double operator()(I idx) const { + return M * idx + B; + } + const double M; + const double B; +}; + +struct IndexerConst { + IndexerConst(double ref) : Ref(ref) { } + template IMPLOT_INLINE double operator()(I) const { return Ref; } + const double Ref; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Getters +//----------------------------------------------------------------------------- + +template +struct GetterXY { + GetterXY(_IndexerX x, _IndexerY y, int count) : IndxerX(x), IndxerY(y), Count(count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + return ImPlotPoint(IndxerX(idx),IndxerY(idx)); + } + const _IndexerX IndxerX; + const _IndexerY IndxerY; + const int Count; +}; + +/// Interprets a user's function pointer as ImPlotPoints +struct GetterFuncPtr { + GetterFuncPtr(ImPlotGetter getter, void* data, int count) : + Getter(getter), + Data(data), + Count(count) + { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + return Getter(idx, Data); + } + ImPlotGetter Getter; + void* const Data; + const int Count; +}; + +template +struct GetterOverrideX { + GetterOverrideX(_Getter getter, double x) : Getter(getter), X(x), Count(getter.Count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + ImPlotPoint p = Getter(idx); + p.x = X; + return p; + } + const _Getter Getter; + const double X; + const int Count; +}; + +template +struct GetterOverrideY { + GetterOverrideY(_Getter getter, double y) : Getter(getter), Y(y), Count(getter.Count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + ImPlotPoint p = Getter(idx); + p.y = Y; + return p; + } + const _Getter Getter; + const double Y; + const int Count; +}; + +template +struct GetterLoop { + GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + idx = idx % (Count - 1); + return Getter(idx); + } + const _Getter Getter; + const int Count; +}; + +template +struct GetterError { + GetterError(const T* xs, const T* ys, const T* neg, const T* pos, int count, int offset, int stride) : + Xs(xs), + Ys(ys), + Neg(neg), + Pos(pos), + Count(count), + Offset(count ? ImPosMod(offset, count) : 0), + Stride(stride) + { } + template IMPLOT_INLINE ImPlotPointError operator()(I idx) const { + return ImPlotPointError((double)IndexData(Xs, idx, Count, Offset, Stride), + (double)IndexData(Ys, idx, Count, Offset, Stride), + (double)IndexData(Neg, idx, Count, Offset, Stride), + (double)IndexData(Pos, idx, Count, Offset, Stride)); + } + const T* const Xs; + const T* const Ys; + const T* const Neg; + const T* const Pos; + const int Count; + const int Offset; + const int Stride; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Fitters +//----------------------------------------------------------------------------- + +template +struct Fitter1 { + Fitter1(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + } + const _Getter1& Getter; +}; + +template +struct FitterX { + FitterX(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis&) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + x_axis.ExtendFit(p.x); + } + } + const _Getter1& Getter; +}; + +template +struct FitterY { + FitterY(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis&, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + y_axis.ExtendFit(p.y); + } + } + const _Getter1& Getter; +}; + +template +struct Fitter2 { + Fitter2(const _Getter1& getter1, const _Getter2& getter2) : Getter1(getter1), Getter2(getter2) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter1.Count; ++i) { + ImPlotPoint p = Getter1(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + for (int i = 0; i < Getter2.Count; ++i) { + ImPlotPoint p = Getter2(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; +}; + +template +struct FitterBarV { + FitterBarV(const _Getter1& getter1, const _Getter2& getter2, double width) : + Getter1(getter1), + Getter2(getter2), + HalfWidth(width*0.5) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + int count = ImMin(Getter1.Count, Getter2.Count); + for (int i = 0; i < count; ++i) { + ImPlotPoint p1 = Getter1(i); p1.x -= HalfWidth; + ImPlotPoint p2 = Getter2(i); p2.x += HalfWidth; + x_axis.ExtendFitWith(y_axis, p1.x, p1.y); + y_axis.ExtendFitWith(x_axis, p1.y, p1.x); + x_axis.ExtendFitWith(y_axis, p2.x, p2.y); + y_axis.ExtendFitWith(x_axis, p2.y, p2.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const double HalfWidth; +}; + +template +struct FitterBarH { + FitterBarH(const _Getter1& getter1, const _Getter2& getter2, double height) : + Getter1(getter1), + Getter2(getter2), + HalfHeight(height*0.5) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + int count = ImMin(Getter1.Count, Getter2.Count); + for (int i = 0; i < count; ++i) { + ImPlotPoint p1 = Getter1(i); p1.y -= HalfHeight; + ImPlotPoint p2 = Getter2(i); p2.y += HalfHeight; + x_axis.ExtendFitWith(y_axis, p1.x, p1.y); + y_axis.ExtendFitWith(x_axis, p1.y, p1.x); + x_axis.ExtendFitWith(y_axis, p2.x, p2.y); + y_axis.ExtendFitWith(x_axis, p2.y, p2.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const double HalfHeight; +}; + +struct FitterRect { + FitterRect(const ImPlotPoint& pmin, const ImPlotPoint& pmax) : + Pmin(pmin), + Pmax(pmax) + { } + FitterRect(const ImPlotRect& rect) : + FitterRect(rect.Min(), rect.Max()) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + x_axis.ExtendFitWith(y_axis, Pmin.x, Pmin.y); + y_axis.ExtendFitWith(x_axis, Pmin.y, Pmin.x); + x_axis.ExtendFitWith(y_axis, Pmax.x, Pmax.y); + y_axis.ExtendFitWith(x_axis, Pmax.y, Pmax.x); + } + const ImPlotPoint Pmin; + const ImPlotPoint Pmax; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Transformers +//----------------------------------------------------------------------------- + +struct Transformer1 { + Transformer1(double pixMin, double pltMin, double pltMax, double m, double scaMin, double scaMax, ImPlotTransform fwd, void* data) : + ScaMin(scaMin), + ScaMax(scaMax), + PltMin(pltMin), + PltMax(pltMax), + PixMin(pixMin), + M(m), + TransformFwd(fwd), + TransformData(data) + { } + + template IMPLOT_INLINE float operator()(T p) const { + if (TransformFwd != nullptr) { + double s = TransformFwd(p, TransformData); + double t = (s - ScaMin) / (ScaMax - ScaMin); + p = PltMin + (PltMax - PltMin) * t; + } + return (float)(PixMin + M * (p - PltMin)); + } + + double ScaMin, ScaMax, PltMin, PltMax, PixMin, M; + ImPlotTransform TransformFwd; + void* TransformData; +}; + +struct Transformer2 { + Transformer2(const ImPlotAxis& x_axis, const ImPlotAxis& y_axis) : + Tx(x_axis.PixelMin, + x_axis.Range.Min, + x_axis.Range.Max, + x_axis.ScaleToPixel, + x_axis.ScaleMin, + x_axis.ScaleMax, + x_axis.TransformForward, + x_axis.TransformData), + Ty(y_axis.PixelMin, + y_axis.Range.Min, + y_axis.Range.Max, + y_axis.ScaleToPixel, + y_axis.ScaleMin, + y_axis.ScaleMax, + y_axis.TransformForward, + y_axis.TransformData) + { } + + Transformer2(const ImPlotPlot& plot) : + Transformer2(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]) + { } + + Transformer2() : + Transformer2(*GImPlot->CurrentPlot) + { } + + template IMPLOT_INLINE ImVec2 operator()(const P& plt) const { + ImVec2 out; + out.x = Tx(plt.x); + out.y = Ty(plt.y); + return out; + } + + template IMPLOT_INLINE ImVec2 operator()(T x, T y) const { + ImVec2 out; + out.x = Tx(x); + out.y = Ty(y); + return out; + } + + Transformer1 Tx; + Transformer1 Ty; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Renderers +//----------------------------------------------------------------------------- + +struct RendererBase { + RendererBase(int prims, int idx_consumed, int vtx_consumed) : + Prims(prims), + IdxConsumed(idx_consumed), + VtxConsumed(vtx_consumed) + { } + const int Prims; + Transformer2 Transformer; + const int IdxConsumed; + const int VtxConsumed; +}; + +template +struct RendererLineStrip : RendererBase { + RendererLineStrip(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineStripSkip : RendererBase { + RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + if (!ImNan(P2.x) && !ImNan(P2.y)) + P1 = P2; + return false; + } + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + if (!ImNan(P2.x) && !ImNan(P2.y)) + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineSegments1 : RendererBase { + RendererLineSegments1(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count / 2, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P1 = this->Transformer(Getter(prim*2+0)); + ImVec2 P2 = this->Transformer(Getter(prim*2+1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineSegments2 : RendererBase { + RendererLineSegments2(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + {} + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P1 = this->Transformer(Getter1(prim)); + ImVec2 P2 = this->Transformer(Getter2(prim)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererBarsFillV : RendererBase { + RendererBarsFillV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWidth(width/2) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.x += HalfWidth; + p2.x -= HalfWidth; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float width_px = ImAbs(P1.x-P2.x); + if (width_px < 1.0f) { + P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2; + P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectFill(draw_list,PMin,PMax,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfWidth; + mutable ImVec2 UV; +}; + +template +struct RendererBarsFillH : RendererBase { + RendererBarsFillH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfHeight(height/2) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.y += HalfHeight; + p2.y -= HalfHeight; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float height_px = ImAbs(P1.y-P2.y); + if (height_px < 1.0f) { + P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2; + P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectFill(draw_list,PMin,PMax,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfHeight; + mutable ImVec2 UV; +}; + +template +struct RendererBarsLineV : RendererBase { + RendererBarsLineV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWidth(width/2), + Weight(weight) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.x += HalfWidth; + p2.x -= HalfWidth; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float width_px = ImAbs(P1.x-P2.x); + if (width_px < 1.0f) { + P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2; + P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfWidth; + const float Weight; + mutable ImVec2 UV; +}; + +template +struct RendererBarsLineH : RendererBase { + RendererBarsLineH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfHeight(height/2), + Weight(weight) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.y += HalfHeight; + p2.y -= HalfHeight; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float height_px = ImAbs(P1.y-P2.y); + if (height_px < 1.0f) { + P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2; + P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfHeight; + const float Weight; + mutable ImVec2 UV; +}; + + +template +struct RendererStairsPre : RendererBase { + RendererStairsPre(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 12, 8), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, ImVec2(P1.x - HalfWeight, P1.y), ImVec2(P1.x + HalfWeight, P2.y), Col, UV); + PrimRectFill(draw_list, ImVec2(P1.x, P2.y + HalfWeight), ImVec2(P2.x, P2.y - HalfWeight), Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPost : RendererBase { + RendererStairsPost(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 12, 8), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight) * 0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, ImVec2(P1.x, P1.y + HalfWeight), ImVec2(P2.x, P1.y - HalfWeight), Col, UV); + PrimRectFill(draw_list, ImVec2(P2.x - HalfWeight, P2.y), ImVec2(P2.x + HalfWeight, P1.y), Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPreShaded : RendererBase { + RendererStairsPreShaded(const _Getter& getter, ImU32 col) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col) + { + P1 = this->Transformer(Getter(0)); + Y0 = this->Transformer(ImPlotPoint(0,0)).y; + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(Y0, P2.y)); + ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(Y0, P2.y)); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, PMin, PMax, Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + float Y0; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPostShaded : RendererBase { + RendererStairsPostShaded(const _Getter& getter, ImU32 col) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col) + { + P1 = this->Transformer(Getter(0)); + Y0 = this->Transformer(ImPlotPoint(0,0)).y; + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(P1.y, Y0)); + ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(P1.y, Y0)); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, PMin, PMax, Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + float Y0; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + + + +template +struct RendererShaded : RendererBase { + RendererShaded(const _Getter1& getter1, const _Getter2& getter2, ImU32 col) : + RendererBase(ImMin(getter1.Count, getter2.Count) - 1, 6, 5), + Getter1(getter1), + Getter2(getter2), + Col(col) + { + P11 = this->Transformer(Getter1(0)); + P12 = this->Transformer(Getter2(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P21 = this->Transformer(Getter1(prim+1)); + ImVec2 P22 = this->Transformer(Getter2(prim+1)); + ImRect rect(ImMin(ImMin(ImMin(P11,P12),P21),P22), ImMax(ImMax(ImMax(P11,P12),P21),P22)); + if (!cull_rect.Overlaps(rect)) { + P11 = P21; + P12 = P22; + return false; + } + const int intersect = (P11.y > P12.y && P22.y > P21.y) || (P12.y > P11.y && P21.y > P22.y); + ImVec2 intersection = Intersection(P11,P21,P12,P22); + draw_list._VtxWritePtr[0].pos = P11; + draw_list._VtxWritePtr[0].uv = UV; + draw_list._VtxWritePtr[0].col = Col; + draw_list._VtxWritePtr[1].pos = P21; + draw_list._VtxWritePtr[1].uv = UV; + draw_list._VtxWritePtr[1].col = Col; + draw_list._VtxWritePtr[2].pos = intersection; + draw_list._VtxWritePtr[2].uv = UV; + draw_list._VtxWritePtr[2].col = Col; + draw_list._VtxWritePtr[3].pos = P12; + draw_list._VtxWritePtr[3].uv = UV; + draw_list._VtxWritePtr[3].col = Col; + draw_list._VtxWritePtr[4].pos = P22; + draw_list._VtxWritePtr[4].uv = UV; + draw_list._VtxWritePtr[4].col = Col; + draw_list._VtxWritePtr += 5; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1 + intersect); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3 - intersect); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 5; + P11 = P21; + P12 = P22; + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + mutable ImVec2 P11; + mutable ImVec2 P12; + mutable ImVec2 UV; +}; + +struct RectC { + ImPlotPoint Pos; + ImPlotPoint HalfSize; + ImU32 Color; +}; + +template +struct RendererRectC : RendererBase { + RendererRectC(const _Getter& getter) : + RendererBase(getter.Count, 6, 4), + Getter(getter) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + RectC rect = Getter(prim); + ImVec2 P1 = this->Transformer(rect.Pos.x - rect.HalfSize.x , rect.Pos.y - rect.HalfSize.y); + ImVec2 P2 = this->Transformer(rect.Pos.x + rect.HalfSize.x , rect.Pos.y + rect.HalfSize.y); + if ((rect.Color & IM_COL32_A_MASK) == 0 || !cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimRectFill(draw_list,P1,P2,rect.Color,UV); + return true; + } + const _Getter& Getter; + mutable ImVec2 UV; +}; + +//----------------------------------------------------------------------------- +// [SECTION] RenderPrimitives +//----------------------------------------------------------------------------- + +/// Renders primitive shapes in bulk as efficiently as possible. +template +void RenderPrimitivesEx(const _Renderer& renderer, ImDrawList& draw_list, const ImRect& cull_rect) { + unsigned int prims = renderer.Prims; + unsigned int prims_culled = 0; + unsigned int idx = 0; + renderer.Init(draw_list); + while (prims) { + // find how many can be reserved up to end of current draw command's limit + unsigned int cnt = ImMin(prims, (MaxIdx::Value - draw_list._VtxCurrentIdx) / renderer.VtxConsumed); + // make sure at least this many elements can be rendered to avoid situations where at the end of buffer this slow path is not taken all the time + if (cnt >= ImMin(64u, prims)) { + if (prims_culled >= cnt) + prims_culled -= cnt; // reuse previous reservation + else { + // add more elements to previous reservation + draw_list.PrimReserve((cnt - prims_culled) * renderer.IdxConsumed, (cnt - prims_culled) * renderer.VtxConsumed); + prims_culled = 0; + } + } + else + { + if (prims_culled > 0) { + draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed); + prims_culled = 0; + } + cnt = ImMin(prims, (MaxIdx::Value - 0/*draw_list._VtxCurrentIdx*/) / renderer.VtxConsumed); + // reserve new draw command + draw_list.PrimReserve(cnt * renderer.IdxConsumed, cnt * renderer.VtxConsumed); + } + prims -= cnt; + for (unsigned int ie = idx + cnt; idx != ie; ++idx) { + if (!renderer.Render(draw_list, cull_rect, idx)) + prims_culled++; + } + } + if (prims_culled > 0) + draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed); +} + +template