From f467e7dce2930fe8fa7b3025668ec5902f964878 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 15 Oct 2022 14:10:07 +0100 Subject: [PATCH] PGE 2.20, Added Animate2D Utilities, and unleashed Geom2D as well, though work in progress --- examples/TEST_Animate2D.cpp | 278 +++++++++++++++++++++++++++ examples/TEST_QuickGUI.cpp | 226 ++++++++++++++++++++++ extensions/olcPGEX_QuickGUI.h | 170 ++++++++++++++-- extensions/olcPGEX_TransformedView.h | 17 +- olcPixelGameEngine.h | 47 ++++- utilities/olcUTIL_Animate2D.h | 212 ++++++++++++++++++++ utilities/olcUTIL_Geometry2D.h | 24 ++- 7 files changed, 947 insertions(+), 27 deletions(-) create mode 100644 examples/TEST_Animate2D.cpp create mode 100644 examples/TEST_QuickGUI.cpp create mode 100644 utilities/olcUTIL_Animate2D.h diff --git a/examples/TEST_Animate2D.cpp b/examples/TEST_Animate2D.cpp new file mode 100644 index 0000000..94197fa --- /dev/null +++ b/examples/TEST_Animate2D.cpp @@ -0,0 +1,278 @@ +/* + Example file for olcUTIL_Animate2D.h + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + +*/ + + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include "utilities/olcUTIL_Animate2D.h" + +class TEST_Animate2D : public olc::PixelGameEngine +{ +public: + TEST_Animate2D() + { + sAppName = "Animate2D Utility Test"; + } + + // These are the states the dude can exist in, we'll need these for physics + // and animation. The physics side of things moves the dude around according to state, + // while the animator chooses the frames based upon state and time + enum class DudeState: uint8_t + { + WALK_N, WALK_S, WALK_E, WALK_W, IDLE_STAND, LAUGH, CHEER, YES, NO + }; + + // !! - IMPORTANT - !! + // The animation set + olc::utils::Animate2D::Animation animDude; + + // One big sprite containing all the graphics + olc::Renderable gfxAll; + + // Small object to reprsent a dude walking around doing things + struct sWalkingDude + { + // Which dude overall out of graophic? + int32_t id = 0; + // Where are they? + olc::vf2d pos; + // What are they doing? + DudeState UserState = DudeState::IDLE_STAND; + // For how long should they do it? + float fTick = 0.0f; + + // !! - IMPORTANT - !! + // Animation Token - links this object's state to animator. Note + // there is no 'ownership' or memory issues here, this is a token + // that the animator can use to quickly get to where it needs + // in order to return frames upon request for this object. + olc::utils::Animate2D::AnimationState animstate; + }; + + // Introducing.... The dudes! + size_t nDudes = 500; + std::vector vDudes; + +public: + bool OnUserCreate() override + { + // For this appliaction I have a single image that contains + // 28x2 unique characters, each character contains 8 animations of 3 + // frames each. Each frame is 26x36 pixels + gfxAll.Load("./assets/MegaSprite1.png"); + + // Thats A LOT of individual graphics, but they all follow a similar pattern + // because the asset was created usefully (take note certain popular asset creators) + // which means we can reuse the animation without needing to define it + // individually for all the "dudes" - the "cookie cutter" approach + + // Manually construct sequences - gfxAll could in fact be nullptr for this + // application, but ive kept it here for convenience + olc::utils::Animate2D::FrameSequence anim_fs_walk_s; + anim_fs_walk_s.AddFrame({ &gfxAll, {{0,0}, {26,36}} }); + anim_fs_walk_s.AddFrame({ &gfxAll, {{26,0}, {26,36}} }); + anim_fs_walk_s.AddFrame({ &gfxAll, {{52,0}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_walk_w; + anim_fs_walk_w.AddFrame({ &gfxAll, {{ 0,36}, {26,36}} }); + anim_fs_walk_w.AddFrame({ &gfxAll, {{26,36}, {26,36}} }); + anim_fs_walk_w.AddFrame({ &gfxAll, {{52,36}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_walk_e; + anim_fs_walk_e.AddFrame({ &gfxAll, {{ 0,72}, {26,36}} }); + anim_fs_walk_e.AddFrame({ &gfxAll, {{26,72}, {26,36}} }); + anim_fs_walk_e.AddFrame({ &gfxAll, {{52,72}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_walk_n; + anim_fs_walk_n.AddFrame({ &gfxAll, {{ 0,108}, {26,36}} }); + anim_fs_walk_n.AddFrame({ &gfxAll, {{26,108}, {26,36}} }); + anim_fs_walk_n.AddFrame({ &gfxAll, {{52,108}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_yes; + anim_fs_yes.AddFrame({ &gfxAll, {{ 0,144}, {26,36}} }); + anim_fs_yes.AddFrame({ &gfxAll, {{26,144}, {26,36}} }); + anim_fs_yes.AddFrame({ &gfxAll, {{52,144}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_no; + anim_fs_no.AddFrame({ &gfxAll, {{ 0,180}, {26,36}} }); + anim_fs_no.AddFrame({ &gfxAll, {{26,180}, {26,36}} }); + anim_fs_no.AddFrame({ &gfxAll, {{52,180}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_laugh; + anim_fs_laugh.AddFrame({ &gfxAll, {{ 0,216}, {26,36}} }); + anim_fs_laugh.AddFrame({ &gfxAll, {{26,216}, {26,36}} }); + anim_fs_laugh.AddFrame({ &gfxAll, {{52,216}, {26,36}} }); + + olc::utils::Animate2D::FrameSequence anim_fs_cheer; + anim_fs_cheer.AddFrame({ &gfxAll, {{ 0,252}, {26,36}} }); + anim_fs_cheer.AddFrame({ &gfxAll, {{26,252}, {26,36}} }); + anim_fs_cheer.AddFrame({ &gfxAll, {{52,252}, {26,36}} }); + + // A special "idle" animation just consists of a single frame + olc::utils::Animate2D::FrameSequence anim_fs_idle; + anim_fs_idle.AddFrame({ &gfxAll, {{26,0}, {26,36}} }); + + // We have constructed teh individual sequences, now its time + // to add them to an animation, along with a state name/enum. + // + // I have chosen to use the enum shown earlier. You could use + // std::string too, which is conveninent if you need to display + // the states, though potentially far less performant + + animDude.AddState(DudeState::WALK_S, anim_fs_walk_s); + animDude.AddState(DudeState::WALK_W, anim_fs_walk_w); + animDude.AddState(DudeState::WALK_E, anim_fs_walk_e); + animDude.AddState(DudeState::WALK_N, anim_fs_walk_n); + animDude.AddState(DudeState::IDLE_STAND, anim_fs_idle); + animDude.AddState(DudeState::YES, anim_fs_yes); + animDude.AddState(DudeState::NO, anim_fs_no); + animDude.AddState(DudeState::LAUGH, anim_fs_laugh); + animDude.AddState(DudeState::CHEER, anim_fs_cheer); + + // Initialise the dudes + for (size_t n = 0; n < nDudes; n++) + { + sWalkingDude dude; + + // Random dude + dude.id = rand() % (28 * 2); + + // Begin in idle state, at random location + dude.UserState = DudeState::IDLE_STAND; + dude.pos = { float(rand() % ScreenWidth()), float(rand() % ScreenHeight()) }; + + // The animation token needs to be updated too + animDude.ChangeState(dude.animstate, dude.UserState); + + vDudes.push_back(dude); + } + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Update Dudes + float fSpeed = 32.0f; + + for (auto& dude : vDudes) + { + // If a dude's tick reaches 0, it will select a new state + dude.fTick -= fElapsedTime; + if (dude.fTick < 0.0f) + { + // Choose one out of the 9 randomly + int nAction = rand() % 9; + + // Choose for how long it should do it in seconds + dude.fTick = (float(rand()) / float(RAND_MAX)) * 5.0f; + + // Assign the state depending on the dice roll - (since enum + // we could cast here too, but, well, meh...) + if (nAction == 0) dude.UserState = DudeState::IDLE_STAND; + if (nAction == 1) dude.UserState = DudeState::WALK_S; + if (nAction == 2) dude.UserState = DudeState::WALK_N; + if (nAction == 3) dude.UserState = DudeState::WALK_E; + if (nAction == 4) dude.UserState = DudeState::WALK_W; + if (nAction == 5) dude.UserState = DudeState::YES; + if (nAction == 6) dude.UserState = DudeState::NO; + if (nAction == 7) dude.UserState = DudeState::LAUGH; + if (nAction == 8) dude.UserState = DudeState::CHEER; + + // State has changed, so update animation token + // !! - IMPORTANT - !! + animDude.ChangeState(dude.animstate, dude.UserState); + } + + // Update "physics", if walking move in that direction at speed + if (dude.UserState == DudeState::WALK_S) dude.pos += olc::vf2d(0, +1) * fSpeed * fElapsedTime; + if (dude.UserState == DudeState::WALK_N) dude.pos += olc::vf2d(0, -1) * fSpeed * fElapsedTime; + if (dude.UserState == DudeState::WALK_E) dude.pos += olc::vf2d(+1, 0) * fSpeed * fElapsedTime; + if (dude.UserState == DudeState::WALK_W) dude.pos += olc::vf2d(-1, 0) * fSpeed * fElapsedTime; + + // If walk off screen, wrap around to other side + if (dude.pos.x > ScreenWidth()) dude.pos.x -= ScreenWidth(); + if (dude.pos.y > ScreenHeight()) dude.pos.x -= ScreenHeight(); + if (dude.pos.x < 0) dude.pos.x += ScreenWidth(); + if (dude.pos.y < 0) dude.pos.y += ScreenHeight(); + + // Update animation token every frame + // !! - IMPORTANT - !! + animDude.UpdateState(dude.animstate, fElapsedTime); + } + + // Render Dudes + for (const auto& dude : vDudes) + { + // Get the frame, this contains both source image and source location rectangle + // !! - IMPORTANT - !! + const auto& frame = animDude.GetFrame(dude.animstate); + + // Thats cool, but there are 28x2 dudes on the sprite sheet, so using the ID, construct + // an offset to the correct dude + olc::vi2d vOffset = { (dude.id % 28) * 78, (dude.id / 28) * 288 }; + + // Use DrawPartialDecal to chop out the correct dude frm the image source + DrawPartialDecal(dude.pos, frame.GetSourceImage()->Decal(), frame.GetSourceRect().pos + vOffset, frame.GetSourceRect().size); + } + + // That's it + return true; + } +}; + +int main() +{ + TEST_Animate2D demo; + if (demo.Construct(640, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/examples/TEST_QuickGUI.cpp b/examples/TEST_QuickGUI.cpp new file mode 100644 index 0000000..2e8761b --- /dev/null +++ b/examples/TEST_QuickGUI.cpp @@ -0,0 +1,226 @@ +/* + Example file for olcPGEX_QuickGUI.h + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// PGEX Require the presence of olc::PixelGameEngine +#define OLC_PGEX_QUICKGUI +#include "extensions/olcPGEX_QuickGUI.h" + + +class olcDemo_QuickGUI : public olc::PixelGameEngine +{ +public: + olcDemo_QuickGUI() + { + sAppName = "olcDemo_QuickGUI"; + } + +protected: + olc::QuickGUI::Manager guiManager; + + olc::QuickGUI::Slider* guiSlider1 = nullptr; + olc::QuickGUI::Slider* guiSlider2 = nullptr; + olc::QuickGUI::Slider* guiSlider3 = nullptr; + + olc::QuickGUI::Button* guiButton1 = nullptr; + olc::QuickGUI::Button* guiButton2 = nullptr; + olc::QuickGUI::Button* guiButton3 = nullptr; + + olc::QuickGUI::Slider* guiThemeColourR = nullptr; + olc::QuickGUI::Slider* guiThemeColourG = nullptr; + olc::QuickGUI::Slider* guiThemeColourB = nullptr; + + olc::QuickGUI::Label* guiLabelR = nullptr; + olc::QuickGUI::Label* guiLabelG = nullptr; + olc::QuickGUI::Label* guiLabelB = nullptr; + + olc::QuickGUI::CheckBox* guiCheckBox1 = nullptr; + + olc::QuickGUI::TextBox* guiTextBox1 = nullptr; + olc::QuickGUI::TextBox* guiTextBox2 = nullptr; + + + std::vector listExample; + olc::QuickGUI::ListBox* guiListBox = nullptr; + +public: + bool OnUserCreate() override + { + // Horizontal Slider + guiSlider1 = new olc::QuickGUI::Slider(guiManager, + { 30.0f, 10.0f }, { 246.0f, 10.0f }, 0, 100, 50); + // Diagonal Slider! + guiSlider2 = new olc::QuickGUI::Slider(guiManager, + { 20.0f, 20.0f }, { 120.0f, 120.0f }, 0, 100, 50); + // Vertical Slider + guiSlider3 = new olc::QuickGUI::Slider(guiManager, + { 10.0f, 30.0f }, { 10.0f, 230.0f }, 0, 100, 50); + + // Theme colour slider - Red + guiThemeColourR = new olc::QuickGUI::Slider(guiManager, + { 150.0f, 30.0f }, { 246.0f, 30.0f }, 0, 255, 0); + // Theme colour slider - Green + guiThemeColourG = new olc::QuickGUI::Slider(guiManager, + { 150.0f, 50.0f }, { 246.0f, 50.0f }, 0, 255, 0); + // Theme colour slider - Blue + guiThemeColourB = new olc::QuickGUI::Slider(guiManager, + { 150.0f, 70.0f }, { 246.0f, 70.0f }, 0, 255, 128); + + // Labels for theme colour sliders + guiLabelR = new olc::QuickGUI::Label(guiManager, + "Red:", { 80.0f, 22.0f }, { 50.0f, 16.0f }); + guiLabelG = new olc::QuickGUI::Label(guiManager, + "Green:", { 80.0f, 42.0f }, { 50.0f, 16.0f }); + guiLabelB = new olc::QuickGUI::Label(guiManager, + "Blue:", { 80.0f, 62.0f }, { 50.0f, 16.0f }); + + // Customize how the labels look + guiLabelB->nAlign = olc::QuickGUI::Label::Alignment::Right; + guiLabelG->nAlign = olc::QuickGUI::Label::Alignment::Right; + guiLabelG->bHasBorder = true; + guiLabelR->nAlign = olc::QuickGUI::Label::Alignment::Right; + guiLabelR->bHasBorder = true; + guiLabelR->bHasBackground = true; + + // Some Buttons, 1 is just a thing, 2 has its text updated and 3 resets the theme + guiButton1 = new olc::QuickGUI::Button(guiManager, + "Button 1", { 30.0f, 150.0f }, { 100.0f, 16.0f }); + guiButton2 = new olc::QuickGUI::Button(guiManager, + "Button 2", { 30.0f, 170.0f }, { 100.0f, 16.0f }); + guiButton3 = new olc::QuickGUI::Button(guiManager, + "Reset Theme", { 30.0f, 190.0f }, { 100.0f, 16.0f }); + + // A CheckBox, switches between sprite or decal drawing + guiCheckBox1 = new olc::QuickGUI::CheckBox(guiManager, + "Use Decals", false, { 30.0f, 210.0f }, { 100.0f, 16.0f }); + + // TextBox, allows basic text entry + guiTextBox1 = new olc::QuickGUI::TextBox(guiManager, + "", { 150.0f, 140.0f }, { 100.0f, 16.0f }); + guiTextBox2 = new olc::QuickGUI::TextBox(guiManager, + "0.04", { 150.0f, 160.0f }, { 100.0f, 16.0f }); + + listExample.push_back("Item 1"); + listExample.push_back("Item 2"); + listExample.push_back("Item 3"); + listExample.push_back("Item 4"); + listExample.push_back("Item 5"); + listExample.push_back("Item 6"); + + guiListBox = new olc::QuickGUI::ListBox(guiManager, + listExample, { 150.0f, 180.0f }, { 100.0f, 54.0f }); + + // but but but.... waaaaaaaaahaaaaaaaa.... where do I delete these horrible + // pointers??? I just can't accept that addressable memory exists and it makes + // me feel really insecure! + // + // By default olc::QuickGUI::Manager will delete any Controls added to it, so you + // dont have to. If you must obfuscate your program with smart pointers, or require + // that you are in rage-control of your memory at all times, construct the Manager + // with false as the argument - then its all up to you buddy. + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // We must update the manager at some point each frame. Values of controls + // are only valid AFTER this call to update() + guiManager.Update(this); + + // Some silly examples... + + // 001) The "theme colour" can be set from slider values + guiManager.colNormal = olc::Pixel( + uint8_t(guiThemeColourR->fValue), + uint8_t(guiThemeColourG->fValue), + uint8_t(guiThemeColourB->fValue)); + + // 002) Display Slider 1 value on Button 2 + guiButton2->sText = "Button " + std::to_string(int32_t(guiSlider1->fValue)); + + // 003) Check if "Reset Theme" button is pressed, if it is, well, err... + if (guiButton3->bPressed) + { + // ...reset the theme! (which also updates the sliders) + guiThemeColourR->fValue = 0.0f; + guiThemeColourG->fValue = 0.0f; + guiThemeColourB->fValue = 128.0f; + } + + // 004) Link Slider 2 and Slider 3 together + if(guiSlider2->bHeld) + guiSlider3->fValue = 100.0f - guiSlider2->fValue; + if (guiSlider3->bHeld) + guiSlider2->fValue = 100.0f - guiSlider3->fValue; + + + // Draw Stuff! + Clear(olc::BLACK); + + // 005) Use checkbox to determine rendering mode + if (guiCheckBox1->bChecked) + guiManager.DrawDecal(this); + else + guiManager.Draw(this); + return true; + } +}; + +int main() +{ + olcDemo_QuickGUI demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/extensions/olcPGEX_QuickGUI.h b/extensions/olcPGEX_QuickGUI.h index cbf5647..e7c55ab 100644 --- a/extensions/olcPGEX_QuickGUI.h +++ b/extensions/olcPGEX_QuickGUI.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - QuickGUI v1.00 + OneLoneCoder - QuickGUI v1.01 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A semi-immediate mode GUI for very simple GUI stuff. Includes: @@ -55,6 +55,12 @@ ~~~~~~ David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + Changes + ~~~~~~~ + v1.01 +Moved Slider::fGrabRad into "theme" + +Manager::CopyThemeFrom() - copies theme attributes from a different manager + +ListBox - Displays a vector of strings + */ #ifndef OLC_PGEX_QUICKGUI_H @@ -143,6 +149,10 @@ namespace olc::QuickGUI float fHoverSpeedOn = 10.0f; // Speed to transiton from Hover -> Normal float fHoverSpeedOff = 4.0f; + // Size of grab handle + float fGrabRad = 8.0f; + // Copy all theme attributes into a different manager object + void CopyThemeFrom(const Manager& manager); private: // Should this manager call delete on the controls it opeerates? @@ -261,8 +271,7 @@ namespace olc::QuickGUI float fMax = +100.0f; // Current value float fValue = 0.0f; - // Size of grab handle - float fGrabRad = 8.0f; + // Location of minimum/start olc::vf2d vPosMin; // Location of maximum/end @@ -274,6 +283,38 @@ namespace olc::QuickGUI void DrawDecal(olc::PixelGameEngine* pge) override; }; + + class ListBox : public BaseControl + { + public: + ListBox(olc::QuickGUI::Manager& manager, // Associate with a Manager + std::vector& vList, + const olc::vf2d& pos, // Location of list top-left + const olc::vf2d& size); // Size of list + + // Position of list + olc::vf2d vPos; + // Size of list + olc::vf2d vSize; + // Show a border? + bool bHasBorder = true; + // Show a background? + bool bHasBackground = true; + + public: + Slider *m_pSlider = nullptr; + Manager m_group; + size_t m_nVisibleItems = 0; + std::vector& m_vList; + + public: + size_t nSelectedItem = 0; + + public: // BaseControl overrides + void Update(olc::PixelGameEngine* pge) override; + void Draw(olc::PixelGameEngine* pge) override; + void DrawDecal(olc::PixelGameEngine* pge) override; + }; } @@ -333,6 +374,19 @@ namespace olc::QuickGUI { for (auto& p : m_vControls) p->DrawDecal(pge); } + + void Manager::CopyThemeFrom(const Manager& manager) + { + this->colBorder = manager.colBorder; + this->colClick = manager.colClick; + this->colDisable = manager.colDisable; + this->colHover = manager.colHover; + this->colNormal = manager.colNormal; + this->colText = manager.colText; + this->fGrabRad = manager.fGrabRad; + this->fHoverSpeedOff = manager.fHoverSpeedOff; + this->fHoverSpeedOn = manager.fHoverSpeedOn; + } #pragma endregion #pragma region Label @@ -694,7 +748,7 @@ namespace olc::QuickGUI else { olc::vf2d vSliderPos = vPosMin + (vPosMax - vPosMin) * ((fValue - fMin) / (fMax - fMin)); - if ((vMouse - vSliderPos).mag2() <= int32_t(fGrabRad) * int32_t(fGrabRad)) + if ((vMouse - vSliderPos).mag2() <= int32_t(m_manager.fGrabRad) * int32_t(m_manager.fGrabRad)) { m_fTransition += fElapsedTime * m_manager.fHoverSpeedOn; m_state = State::Hover; @@ -736,19 +790,19 @@ namespace olc::QuickGUI switch (m_state) { case State::Disabled: - pge->FillCircle(vSliderPos, int32_t(fGrabRad), m_manager.colDisable); + pge->FillCircle(vSliderPos, int32_t(m_manager.fGrabRad), m_manager.colDisable); break; case State::Normal: case State::Hover: - pge->FillCircle(vSliderPos, int32_t(fGrabRad), olc::PixelLerp(m_manager.colNormal, m_manager.colHover, m_fTransition)); + pge->FillCircle(vSliderPos, int32_t(m_manager.fGrabRad), olc::PixelLerp(m_manager.colNormal, m_manager.colHover, m_fTransition)); break; case State::Click: - pge->FillCircle(vSliderPos, int32_t(fGrabRad), m_manager.colClick); + pge->FillCircle(vSliderPos, int32_t(m_manager.fGrabRad), m_manager.colClick); break; } - pge->DrawCircle(vSliderPos, int32_t(fGrabRad), m_manager.colBorder); + pge->DrawCircle(vSliderPos, int32_t(m_manager.fGrabRad), m_manager.colBorder); } void Slider::DrawDecal(olc::PixelGameEngine* pge) @@ -762,25 +816,117 @@ namespace olc::QuickGUI switch (m_state) { case State::Disabled: - pge->FillRectDecal(vSliderPos - olc::vf2d(fGrabRad, fGrabRad), olc::vf2d(fGrabRad, fGrabRad) * 2.0f, m_manager.colDisable); + pge->FillRectDecal(vSliderPos - olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad), olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad) * 2.0f, m_manager.colDisable); break; case State::Normal: case State::Hover: - pge->FillRectDecal(vSliderPos - olc::vf2d(fGrabRad, fGrabRad), olc::vf2d(fGrabRad, fGrabRad) * 2.0f, olc::PixelLerp(m_manager.colNormal, m_manager.colHover, m_fTransition)); + pge->FillRectDecal(vSliderPos - olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad), olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad) * 2.0f, olc::PixelLerp(m_manager.colNormal, m_manager.colHover, m_fTransition)); break; case State::Click: - pge->FillRectDecal(vSliderPos - olc::vf2d(fGrabRad, fGrabRad), olc::vf2d(fGrabRad, fGrabRad) * 2.0f, m_manager.colClick); + pge->FillRectDecal(vSliderPos - olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad), olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad) * 2.0f, m_manager.colClick); break; } pge->SetDecalMode(olc::DecalMode::WIREFRAME); - pge->FillRectDecal(vSliderPos - olc::vf2d(fGrabRad, fGrabRad), olc::vf2d(fGrabRad, fGrabRad) * 2.0f, m_manager.colBorder); + pge->FillRectDecal(vSliderPos - olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad), olc::vf2d(m_manager.fGrabRad, m_manager.fGrabRad) * 2.0f, m_manager.colBorder); pge->SetDecalMode(olc::DecalMode::NORMAL); } #pragma endregion +#pragma region ListBox + ListBox::ListBox(olc::QuickGUI::Manager& manager, std::vector& vList, const olc::vf2d& pos, const olc::vf2d& size) + : BaseControl(manager), m_vList(vList) + { + m_group.CopyThemeFrom(m_manager); + vPos = pos; + vSize = size; + m_pSlider = new Slider(m_group, { pos.x + size.x - m_manager.fGrabRad - 1, pos.y + m_manager.fGrabRad + 1 }, + { pos.x + size.x - m_manager.fGrabRad - 1, pos.y + size.y - m_manager.fGrabRad - 1 }, 0, float(m_vList.size()), 0); + } + + void ListBox::Update(olc::PixelGameEngine* pge) + { + if (m_state == State::Disabled || !bVisible) + return; + + olc::vf2d vMouse = pge->GetMousePos() - vPos + olc::vi2d(2,0); + if (pge->GetMouse(olc::Mouse::LEFT).bPressed) + { + if (vMouse.x >= 0 && vMouse.x < vSize.x - (m_group.fGrabRad * 2) && vMouse.y >= 0 && vMouse.y < vSize.y) + { + nSelectedItem = size_t(m_pSlider->fValue + vMouse.y / 10); + } + } + + nSelectedItem = std::clamp(nSelectedItem, size_t(0), m_vList.size()-1); + + m_pSlider->fMax = float(m_vList.size()); + m_group.Update(pge); + } + + void ListBox::Draw(olc::PixelGameEngine* pge) + { + if (!bVisible) + return; + + if (bHasBackground) + { + pge->FillRect(vPos + olc::vf2d(1, 1), vSize - olc::vf2d(2, 2), m_manager.colNormal); + } + + if (bHasBorder) + pge->DrawRect(vPos, vSize - olc::vf2d(1, 1), m_manager.colBorder); + + + size_t idx0 = size_t(m_pSlider->fValue); + size_t idx1 = std::min(idx0 + size_t((vSize.y - 4) / 10), m_vList.size()); + + olc::vf2d vTextPos = vPos + olc::vf2d(2,2); + for (size_t idx = idx0; idx < idx1; idx++) + { + if (idx == nSelectedItem) + pge->FillRect(vTextPos - olc::vi2d(1,1), {int32_t(vSize.x - m_group.fGrabRad * 2), 10}, m_group.colHover); + pge->DrawStringProp(vTextPos, m_vList[idx]); + vTextPos.y += 10; + } + + m_group.Draw(pge); + } + + void ListBox::DrawDecal(olc::PixelGameEngine* pge) + { + if (!bVisible) + return; + + if (bHasBackground) + pge->FillRectDecal(vPos + olc::vf2d(1, 1), vSize - olc::vf2d(2, 2), m_manager.colNormal); + + size_t idx0 = size_t(m_pSlider->fValue); + size_t idx1 = std::min(idx0 + size_t((vSize.y - 4) / 10), m_vList.size()); + + olc::vf2d vTextPos = vPos + olc::vf2d(2, 2); + for (size_t idx = idx0; idx < idx1; idx++) + { + if (idx == nSelectedItem) + pge->FillRectDecal(vTextPos - olc::vi2d(1, 1), { vSize.x - m_group.fGrabRad * 2.0f, 10.0f }, m_group.colHover); + pge->DrawStringPropDecal(vTextPos, m_vList[idx]); + vTextPos.y += 10; + } + + if (bHasBorder) + { + pge->SetDecalMode(olc::DecalMode::WIREFRAME); + pge->FillRectDecal(vPos + olc::vf2d(1, 1), vSize - olc::vf2d(2, 2), m_manager.colBorder); + pge->SetDecalMode(olc::DecalMode::NORMAL); + } + + m_group.DrawDecal(pge); + } +#pragma endregion + + } #endif // OLC_PGEX_QUICKGUI #endif // OLC_PGEX_QUICKGUI_H \ No newline at end of file diff --git a/extensions/olcPGEX_TransformedView.h b/extensions/olcPGEX_TransformedView.h index d5641ca..51bdc7b 100644 --- a/extensions/olcPGEX_TransformedView.h +++ b/extensions/olcPGEX_TransformedView.h @@ -3,7 +3,7 @@ +-------------------------------------------------------------+ | OneLoneCoder Pixel Game Engine Extension | - | Transformed View v1.06 | + | Transformed View v1.07 | +-------------------------------------------------------------+ NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES @@ -71,6 +71,8 @@ 1.04: Added DrawPolygonDecal() for arbitrary polygons 1.05: Clipped DrawSprite() to visible area, massive performance increase 1.06: Fixed error in DrawLine() - Thanks CraisyDaisyRecords (& Fern)! + 1.07: +DrawRectDecal() + +GetPGE() */ #pragma once @@ -89,6 +91,8 @@ namespace olc TransformedView() = default; virtual void Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale = { 1.0f, 1.0f }); + olc::PixelGameEngine* GetPGE(); + public: void SetWorldOffset(const olc::vf2d& vOffset); void MoveWorldOffset(const olc::vf2d& vDeltaOffset); @@ -179,6 +183,8 @@ namespace olc void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); // Draws a single shaded filled rectangle as a decal void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + // Draws a corner shaded rectangle as a decal void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); // Draws an arbitrary convex textured polygon using GPU @@ -219,6 +225,10 @@ namespace olc namespace olc { + olc::PixelGameEngine* TransformedView::GetPGE() + { + return pge; + } void TransformedView::Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale) { @@ -627,6 +637,11 @@ namespace olc pge->FillRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); } + void TransformedView::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + pge->DrawRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); + } + void TransformedView::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) { pge->DrawLineDecal(WorldToScreen(pos1), WorldToScreen(pos2), p); diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index b4b1b92..17b2d4a 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -3,7 +3,7 @@ olcPixelGameEngine.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v2.19 | + | OneLoneCoder Pixel Game Engine v2.20 | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ @@ -187,6 +187,8 @@ AlterEgo...........Final Fantasy XII - The Zodiac Age SlicEnDicE.........Noita, Inside TGD................Voucher Gift + Dragoneye..........Lucas Arts Adventure Game Pack + Anonymous Pirate...Return To Monkey Island Special thanks to my Patreons too - I wont name you on here, but I've certainly enjoyed my tea and flapjacks :D @@ -297,8 +299,10 @@ +ConsoleClear() - Clears built in command console output +ConsoleOut() - Stream strings to command console output +ConsoleCaptureStdOut() - Capture std::cout by redirecting to built-in console - +IsConsoleShowing() - Returns true if console is currently active +OnConsoleCommand() - Override is called when command is entered into built in console + 2.20: +DrawRectDecal() - Keeps OneSketchyGuy quiet + +GetScreenSize() + +olc::Sprite::Size() - returns size of sprite in vector format !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! @@ -378,7 +382,7 @@ int main() #include #pragma endregion -#define PGE_VER 219 +#define PGE_VER 220 // O------------------------------------------------------------------------------O // | COMPILER CONFIGURATION ODDITIES | @@ -771,6 +775,7 @@ namespace olc Pixel* GetData(); olc::Sprite* Duplicate(); olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); + olc::vi2d Size() const; std::vector pColData; Mode modeSample = Mode::NORMAL; @@ -980,6 +985,8 @@ namespace olc const olc::vi2d& GetPixelSize() const; // Gets actual pixel scale const olc::vi2d& GetScreenPixelSize() const; + // Gets "screen" size + const olc::vi2d& GetScreenSize() const; public: // CONFIGURATION ROUTINES // Layer targeting functions @@ -1074,6 +1081,7 @@ namespace olc void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); // Draws a single shaded filled rectangle as a decal + void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); // Draws a corner shaded rectangle as a decal void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); @@ -1506,6 +1514,11 @@ namespace olc return spr; } + olc::vi2d olc::Sprite::Size() const + { + return { width, height }; + } + // O------------------------------------------------------------------------------O // | olc::Decal IMPLEMENTATION | // O------------------------------------------------------------------------------O @@ -1942,6 +1955,9 @@ namespace olc const olc::vi2d& PixelGameEngine::GetScreenPixelSize() const { return vScreenPixelSize; } + const olc::vi2d& PixelGameEngine::GetScreenSize() const + { return vScreenSize; } + const olc::vi2d& PixelGameEngine::GetWindowMouse() const { return vMouseWindowPos; } @@ -2665,7 +2681,12 @@ namespace olc void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) { - DecalInstance di; + auto m = nDecalMode; + nDecalMode = olc::DecalMode::WIREFRAME; + DrawPolygonDecal(nullptr, { pos1, pos2 }, { {0, 0}, {0,0} }, p); + nDecalMode = m; + + /*DecalInstance di; di.decal = nullptr; di.points = uint32_t(2); di.pos.resize(di.points); @@ -2681,12 +2702,26 @@ namespace olc di.tint[1] = p; di.w[1] = 1.0f; di.mode = olc::DecalMode::WIREFRAME; - vLayers[nTargetLayer].vecDecalInstance.push_back(di); + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di);*/ + } + + void PixelGameEngine::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + auto m = nDecalMode; + SetDecalMode(olc::DecalMode::WIREFRAME); + olc::vf2d vNewSize = size;// (size - olc::vf2d(0.375f, 0.375f)).ceil(); + std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + SetDecalMode(m); + } void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { - olc::vf2d vNewSize = (size - olc::vf2d(0.375f, 0.375f)).ceil(); + olc::vf2d vNewSize = size;// (size - olc::vf2d(0.375f, 0.375f)).ceil(); std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; std::array cols = { {col, col, col, col} }; diff --git a/utilities/olcUTIL_Animate2D.h b/utilities/olcUTIL_Animate2D.h new file mode 100644 index 0000000..52ae051 --- /dev/null +++ b/utilities/olcUTIL_Animate2D.h @@ -0,0 +1,212 @@ +/* + OneLoneCoder - Animate2D v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Handles animated Sprites efficiently + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + +*/ + +#pragma once + +#include "olcPixelGameEngine.h" +#include "utilities/olcUTIL_Geometry2D.h" + +namespace olc::utils::Animate2D +{ + // This class rerpresents a valid "frame" of an animation. It could be from any image source, and + // any location withing that image source. Once it's constructed, it's advised not to change it, as + // this likely indicates a usage bug. + // + // "Sourceless" frames are valid too - this is useful if you have a particular animation set, but + // want to apply it to a variety of sources, for example sprite maps with common layouts. + class Frame + { + public: + inline Frame(const olc::Renderable* gfxSource, const geom2d::rect& rectSource = { {0,0},{0,0} }) + : gfxImageSource(gfxSource), rectFrameSource(rectSource) + { + // If no source rectangle specified then use whole image source. Ignore in the event + // that a frame is set up as source-less + if(gfxSource && rectFrameSource.size.x == 0) + rectFrameSource.size = gfxSource->Sprite()->Size(); + } + + inline const olc::Renderable* GetSourceImage() const + { + return gfxImageSource; + } + + inline const geom2d::rect& GetSourceRect() const + { + return rectFrameSource; + } + + private: + const olc::Renderable* gfxImageSource; + geom2d::rect rectFrameSource; + }; + + // Animation styles decide how the frames should be traversed in time + enum class Style : uint8_t + { + Repeat, // Cycle through, go back to beginning + OneShot, // Play once and suspend on final frame + PingPong, // Traverse through forwards, then backwards + Reverse, // Cycle through sequence backwards + }; + + class FrameSequence + { + public: + // Constructs a sequence of frames with a duration and a traversal style + inline FrameSequence(const float fFrameDuration = 0.1f, const Style nStyle = Style::Repeat) + { + m_fFrameDuration = fFrameDuration; + m_fFrameRate = 1.0f / m_fFrameDuration; + m_nStyle = nStyle; + } + + // Adds a frame to this sequence + inline void AddFrame(const Frame& frame) + { + m_vFrames.emplace_back(frame); + } + + // Returns a Frame Object for a given time into an animation + inline const Frame& GetFrame(const float fTime) const + { + return m_vFrames[ConvertTimeToFrame(fTime)]; + } + + private: + Style m_nStyle; + std::vector m_vFrames; + float m_fFrameDuration = 0.1f; + float m_fFrameRate = 10.0f; + + inline const size_t ConvertTimeToFrame(const float fTime) const + { + switch (m_nStyle) + { + case Style::Repeat: + return size_t(fTime * m_fFrameRate) % m_vFrames.size(); + break; + case Style::OneShot: + return std::clamp(size_t(fTime * m_fFrameRate), size_t(0), m_vFrames.size() - 1); + break; + case Style::PingPong: + // TODO + break; + case Style::Reverse: + return (m_vFrames.size() - 1) - (size_t(fTime * m_fFrameRate) % m_vFrames.size()); + break; + } + + return 0; + } + }; + + // A Animate2D::State is a lightweight token that can be attached to things + // that are animated. Under normal circumstances, it is never updated manually + struct AnimationState + { + private: + size_t nIndex = 0; + float fTime = 0.0f; + template + friend class Animation; + }; + + // Animation object holds a group of frame sequences and can mutate an AnimationState token + template + class Animation + { + public: + Animation() = default; + + // Change an animation state token to a new state + inline bool ChangeState(AnimationState& state, const StatesEnum& sStateName) const + { + size_t idx = m_mapStateIndices.at(sStateName); + if (state.nIndex != idx) + { + state.fTime = 0.0f; + state.nIndex = idx; + return true; + } + + return false; + } + + // Update an animation state token + inline void UpdateState(AnimationState& state, const float fElapsedTime) const + { + state.fTime += fElapsedTime; + } + + public: + // Retrieve the frame information for a given animation state + inline const Frame& GetFrame(const AnimationState& state) const + { + return m_vSequences[state.nIndex].GetFrame(state.fTime); + } + + public: + // Add a named Frame sequence as a state + inline void AddState(const StatesEnum& sStateName, const FrameSequence& sequence) + { + m_vSequences.emplace_back(sequence); + m_mapStateIndices[sStateName] = m_vSequences.size() - 1; + } + + private: + std::vector m_vSequences; + std::unordered_map m_mapStateIndices; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL_Geometry2D.h b/utilities/olcUTIL_Geometry2D.h index cd13a29..c625271 100644 --- a/utilities/olcUTIL_Geometry2D.h +++ b/utilities/olcUTIL_Geometry2D.h @@ -260,6 +260,12 @@ namespace olc::utils::geom2d }; + template + struct polygon + { + std::vector> vPoints; + }; + // ========================================================================================================================= // Closest(shape, point) =================================================================================================== @@ -274,25 +280,27 @@ namespace olc::utils::geom2d // Returns closest point on line to point template inline olc::v2d_generic closest(const line& l, const olc::v2d_generic& p) - { - // TODO: - return olc::v2d_generic(); + { + auto d = l.vector(); + double u = std::clamp(double(d.dot(p - l.start) / d.mag2()), 0.0, 1.0); + return l.start + d * u; } // Returns closest point on circle to point template inline olc::v2d_generic closest(const circle& c, const olc::v2d_generic& p) - { - // TODO: - return olc::v2d_generic(); + { + return c.pos + (p - c.pos).norm() * c.radius; } // Returns closest point on rectangle to point template inline olc::v2d_generic closest(const rect& r, const olc::v2d_generic& p) { - // TODO: - return olc::v2d_generic(); + // This could be a "constrain" function hmmmm + // TODO: Not quite what i wanted, should restrain to boundary + return olc::v2d_generic{ std::clamp(p.x, r.pos.x, r.pos.x + r.size.x), std::clamp(p.y, r.pos.y, r.pos.y + r.size.y) }; + } // Returns closest point on triangle to point