commit
3280f71307
278
examples/TEST_Animate2D.cpp
Normal file
278
examples/TEST_Animate2D.cpp
Normal file
@ -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<DudeState> 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<sWalkingDude> 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;
|
||||
}
|
226
examples/TEST_QuickGUI.cpp
Normal file
226
examples/TEST_QuickGUI.cpp
Normal file
@ -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<std::string> 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;
|
||||
}
|
@ -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<std::string>& 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<std::string>& 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
|
||||
@ -649,7 +703,7 @@ namespace olc::QuickGUI
|
||||
Button::Draw(pge);
|
||||
|
||||
if (bChecked)
|
||||
pge->DrawRect(vPos + olc::vf2d(2, 2), vSize - olc::vi2d(4, 4), m_manager.colBorder);
|
||||
pge->DrawRect(vPos + olc::vf2d(2, 2), vSize - olc::vi2d(5, 5), m_manager.colBorder);
|
||||
}
|
||||
|
||||
void CheckBox::DrawDecal(olc::PixelGameEngine* pge)
|
||||
@ -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<std::string>& 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
|
@ -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);
|
||||
|
@ -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 <cstring>
|
||||
#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<olc::Pixel> 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<olc::vf2d, 4> points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } };
|
||||
std::array<olc::vf2d, 4> uvs = { {{0,0},{0,0},{0,0},{0,0}} };
|
||||
std::array<olc::Pixel, 4> 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<olc::vf2d, 4> points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } };
|
||||
std::array<olc::vf2d, 4> uvs = { {{0,0},{0,0},{0,0},{0,0}} };
|
||||
std::array<olc::Pixel, 4> cols = { {col, col, col, col} };
|
||||
|
212
utilities/olcUTIL_Animate2D.h
Normal file
212
utilities/olcUTIL_Animate2D.h
Normal file
@ -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<int32_t>& 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<int32_t>& GetSourceRect() const
|
||||
{
|
||||
return rectFrameSource;
|
||||
}
|
||||
|
||||
private:
|
||||
const olc::Renderable* gfxImageSource;
|
||||
geom2d::rect<int32_t> 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<Frame> 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<typename StatesEnum>
|
||||
friend class Animation;
|
||||
};
|
||||
|
||||
// Animation object holds a group of frame sequences and can mutate an AnimationState token
|
||||
template<typename StatesEnum>
|
||||
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<FrameSequence> m_vSequences;
|
||||
std::unordered_map<StatesEnum, size_t> m_mapStateIndices;
|
||||
};
|
||||
}
|
1024
utilities/olcUTIL_Geometry2D.h
Normal file
1024
utilities/olcUTIL_Geometry2D.h
Normal file
File diff suppressed because it is too large
Load Diff
238
utilities/olcUTIL_Palette.h
Normal file
238
utilities/olcUTIL_Palette.h
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
OneLoneCoder - Palette v1.00
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Convenient tool for creating interpolated palettes, that can be
|
||||
sampled discretely or continuously
|
||||
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
// Create palette object
|
||||
olc::Palette pal;
|
||||
|
||||
// Use a stock palette
|
||||
pal = Palette(Stock::Spectrum);
|
||||
|
||||
// Clearing a palete
|
||||
pal.Clear();
|
||||
|
||||
// Custom palette - normalised index [0.0 ... 1.0], Colour
|
||||
pal.SetColour(00.0 / 24.0, olc::Pixel(000, 000, 000));
|
||||
pal.SetColour(03.0 / 24.0, olc::Pixel(000, 000, 030));
|
||||
pal.SetColour(05.0 / 24.0, olc::Pixel(200, 240, 255));
|
||||
pal.SetColour(12.0 / 24.0, olc::Pixel(200, 240, 255));
|
||||
pal.SetColour(15.0 / 24.0, olc::Pixel(050, 100, 255));
|
||||
pal.SetColour(20.0 / 24.0, olc::Pixel(245, 000, 146));
|
||||
pal.SetColour(22.0 / 24.0, olc::Pixel(000, 000, 128));
|
||||
pal.SetColour(24.0 / 24.0, olc::Pixel(000, 000, 000));
|
||||
|
||||
// Access palette via interpolation [0.0 ... 1.0]
|
||||
// Continuous, Smooth, potentially slower
|
||||
olc::Pixel p1 = pal.Sample(0.2);
|
||||
olc::Pixel p2 = pal.Sample(0.6);
|
||||
olc::Pixel p3 = pal.Sample(1.0);
|
||||
|
||||
// Access palette via index [0 ... 255]
|
||||
// Discrete, Not Smooth, very fast
|
||||
olc::Pixel i1 = pal.Index(10);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "olcPixelGameEngine.h"
|
||||
|
||||
namespace olc::utils
|
||||
{
|
||||
class Palette
|
||||
{
|
||||
public:
|
||||
enum class Stock
|
||||
{
|
||||
Empty,
|
||||
Greyscale,
|
||||
ColdHot,
|
||||
Spectrum,
|
||||
};
|
||||
|
||||
public:
|
||||
// Construct empty palette (default) or populate with pre-defined
|
||||
inline Palette(const Stock stock = Stock::Empty)
|
||||
{
|
||||
switch (stock)
|
||||
{
|
||||
case Stock::Empty:
|
||||
Clear();
|
||||
break;
|
||||
case Stock::Greyscale:
|
||||
vColours =
|
||||
{
|
||||
{0.0, olc::BLACK}, {1.0, olc::WHITE}
|
||||
};
|
||||
break;
|
||||
case Stock::ColdHot:
|
||||
vColours =
|
||||
{
|
||||
{0.0, olc::CYAN}, {0.5, olc::BLACK}, {1.0, olc::YELLOW}
|
||||
};
|
||||
break;
|
||||
case Stock::Spectrum:
|
||||
vColours =
|
||||
{
|
||||
{0.0 / 6.0, olc::RED},
|
||||
{1.0 / 6.0, olc::YELLOW},
|
||||
{2.0 / 6.0, olc::GREEN},
|
||||
{3.0 / 6.0, olc::CYAN},
|
||||
{4.0 / 6.0, olc::BLUE},
|
||||
{5.0 / 6.0, olc::MAGENTA},
|
||||
{6.0 / 6.0, olc::RED}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
ReconstructIndex();
|
||||
}
|
||||
|
||||
public:
|
||||
// Sample continously from palette, where t is [0.0 ... 1.0]
|
||||
inline olc::Pixel Sample(const double t) const
|
||||
{
|
||||
// Return obvious sample values
|
||||
if (vColours.empty())
|
||||
return olc::BLACK;
|
||||
|
||||
if (vColours.size() == 1)
|
||||
return vColours.front().second;
|
||||
|
||||
// Iterate through colour entries until we find the first entry
|
||||
// with a location greater than our sample point
|
||||
double i = std::clamp(t, 0.0, 1.0);
|
||||
auto it = vColours.begin();
|
||||
while (i > it->first)
|
||||
++it;
|
||||
|
||||
// If that is the first entry, just return it
|
||||
if (it == std::begin(vColours))
|
||||
return it->second;
|
||||
else
|
||||
{
|
||||
// else get the preceeding entry, and lerp between the two
|
||||
// proportionally
|
||||
auto it_p = std::prev(it);
|
||||
return olc::PixelLerp(it_p->second, it->second,
|
||||
float((i - it_p->first) / (it->first - it_p->first)));
|
||||
}
|
||||
}
|
||||
|
||||
// Sample discretely from palette where idx is [0 ... 255]
|
||||
inline olc::Pixel Index(const uint8_t idx) const
|
||||
{
|
||||
return aIndexedPalette[idx];
|
||||
}
|
||||
|
||||
// Clear palette, defaulting to black
|
||||
inline void Clear()
|
||||
{
|
||||
vColours.clear();
|
||||
aIndexedPalette.fill(olc::BLACK);
|
||||
}
|
||||
|
||||
// Add a colour at a particular continuous location in palette
|
||||
inline void SetColour(const double d, const olc::Pixel col)
|
||||
{
|
||||
double i = std::clamp(d, 0.0, 1.0);
|
||||
|
||||
// If d already exists, replace it
|
||||
auto it = std::find_if(vColours.begin(), vColours.end(),
|
||||
[&i](const std::pair<double, olc::Pixel>& p)
|
||||
{
|
||||
return p.first == i;
|
||||
});
|
||||
|
||||
if (it != std::end(vColours))
|
||||
{
|
||||
// Palette entry was found, so replace colour entry
|
||||
it->second = col;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Palette entry not found, sp add it, and sort palette vector
|
||||
vColours.push_back({ i, col });
|
||||
std::sort(vColours.begin(), vColours.end(),
|
||||
[](const std::pair<double, olc::Pixel>& p1, std::pair<double, olc::Pixel>& p2)
|
||||
{
|
||||
return p2.first > p1.first;
|
||||
});
|
||||
}
|
||||
|
||||
ReconstructIndex();
|
||||
}
|
||||
|
||||
private:
|
||||
// Vector stores all palette milestones
|
||||
std::vector<std::pair<double, olc::Pixel>> vColours;
|
||||
// Array stores fast LUT
|
||||
std::array<olc::Pixel, 256> aIndexedPalette;
|
||||
|
||||
inline void ReconstructIndex()
|
||||
{
|
||||
// Reconstruct Indexed palette
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
double t = double(i) / 255.0;
|
||||
aIndexedPalette[i] = Sample(t);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
328
utilities/olcUTIL_QuadTree.h
Normal file
328
utilities/olcUTIL_QuadTree.h
Normal file
@ -0,0 +1,328 @@
|
||||
/*
|
||||
OneLoneCoder - QuadTree v1.00
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
A Dynamic Quad-Tree implementation to store objects in a 2D space
|
||||
with a fast retrieval.
|
||||
|
||||
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 "olcUTIL_Geometry2D.h"
|
||||
|
||||
|
||||
|
||||
namespace olc::utils
|
||||
{
|
||||
template <typename T, typename CTYPE = float>
|
||||
struct QuadTreeItemLocation
|
||||
{
|
||||
typename std::list<std::pair<geom2d::rect<CTYPE>, T>>* container = nullptr;
|
||||
typename std::list<std::pair<geom2d::rect<CTYPE>, T>>::iterator iterator;
|
||||
};
|
||||
|
||||
template <typename pT, typename CTYPE = float>
|
||||
class DynamicQuadTree
|
||||
{
|
||||
public:
|
||||
DynamicQuadTree(const geom2d::rect<CTYPE>& size, const size_t nDepth = 0, const size_t nMaxDepth = 8)
|
||||
{
|
||||
m_depth = nDepth;
|
||||
m_rect = size;
|
||||
m_maxdepth = nMaxDepth;
|
||||
resize(m_rect);
|
||||
}
|
||||
|
||||
// Insert a region into this area
|
||||
QuadTreeItemLocation<pT> insert(const pT item, const geom2d::rect<CTYPE>& itemsize)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (geom2d::contains(m_rChild[i], itemsize))
|
||||
{
|
||||
// Have we reached depth limit?
|
||||
if (m_depth + 1 < m_maxdepth)
|
||||
{
|
||||
// No, so does child exist?
|
||||
if (!m_pChild[i])
|
||||
{
|
||||
// No, so create it
|
||||
m_pChild[i] = std::make_shared<DynamicQuadTree<pT>>(m_rChild[i], m_depth + 1, m_maxdepth);
|
||||
}
|
||||
|
||||
// Yes, so add item to it
|
||||
return m_pChild[i]->insert(item, itemsize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It didnt fit, so item must belong to this geom2d::rect<CTYPE>
|
||||
m_pItems.push_back({ itemsize, item });
|
||||
return { &m_pItems, std::prev(m_pItems.end()) };
|
||||
}
|
||||
|
||||
void relocate(pT item, const geom2d::rect<CTYPE>& rArea)
|
||||
{
|
||||
// Remove it
|
||||
remove(item);
|
||||
|
||||
// Reinsert it with new location
|
||||
insert(item, rArea);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
size_t nCount = m_pItems.size();
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (m_pChild[i]) nCount += m_pChild[i]->size();
|
||||
return nCount;
|
||||
}
|
||||
|
||||
void search(const geom2d::rect<CTYPE>& rArea, std::list<pT>& listItems) const
|
||||
{
|
||||
// First, check for items belonging to this area, add them to the list
|
||||
// if there is overlap
|
||||
for (const auto& p : m_pItems)
|
||||
{
|
||||
if (geom2d::overlaps(rArea,p.first))
|
||||
listItems.push_back(p.second);
|
||||
}
|
||||
|
||||
// Second, recurse through children and see if they can
|
||||
// add to the list
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (m_pChild[i])
|
||||
{
|
||||
// If child is entirely contained within area, recursively
|
||||
// add all of its children, no need to check boundaries
|
||||
if (geom2d::contains(rArea,m_rChild[i]))
|
||||
m_pChild[i]->items(listItems);
|
||||
|
||||
// If child overlaps with search area then checks need
|
||||
// to be made
|
||||
else if (geom2d::overlaps(m_rChild[i],rArea))
|
||||
m_pChild[i]->search(rArea, listItems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void items(std::list<pT>& listItems) const
|
||||
{
|
||||
// No questions asked, just return child items
|
||||
for (const auto& p : m_pItems)
|
||||
listItems.push_back(p.second);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (m_pChild[i]) m_pChild[i]->items(listItems);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_pItems.clear();
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (m_pChild[i])
|
||||
m_pChild[i]->clear();
|
||||
else
|
||||
m_pChild[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
void resize(const geom2d::rect<CTYPE>& rArea)
|
||||
{
|
||||
clear();
|
||||
m_rect = rArea;
|
||||
olc::v2d_generic<CTYPE> vChildSize = m_rect.size / CTYPE(2);
|
||||
m_rChild =
|
||||
{
|
||||
geom2d::rect<CTYPE>(m_rect.pos, vChildSize),
|
||||
geom2d::rect<CTYPE>({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize),
|
||||
geom2d::rect<CTYPE>({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize),
|
||||
geom2d::rect<CTYPE>(m_rect.pos + vChildSize, vChildSize)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const geom2d::rect<CTYPE>& area()
|
||||
{
|
||||
return m_rect;
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t m_depth = 0;
|
||||
size_t m_maxdepth = 8;
|
||||
|
||||
// Area of this quadnode
|
||||
geom2d::rect<CTYPE> m_rect;
|
||||
|
||||
// 4 child areas of this quadnode
|
||||
std::array<geom2d::rect<CTYPE>, 4> m_rChild{};
|
||||
|
||||
// 4 potential children of this quadnode
|
||||
std::array<std::shared_ptr<DynamicQuadTree<pT>>, 4> m_pChild{};
|
||||
|
||||
// Items which belong to this quadnode
|
||||
std::list<std::pair<geom2d::rect<CTYPE>, pT>> m_pItems;
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename CTYPE = float>
|
||||
struct QuadTreeItem
|
||||
{
|
||||
// The item Itself
|
||||
T item;
|
||||
|
||||
// A "locator" to the container/iterator that points to this item's iterator in the
|
||||
// top level list - phew
|
||||
QuadTreeItemLocation<typename std::list<QuadTreeItem<T, CTYPE>>::iterator> pItem;
|
||||
};
|
||||
|
||||
template <typename T, typename CTYPE = float>
|
||||
class QuadTreeContainer
|
||||
{
|
||||
using IQuadtreeContainer = std::list<QuadTreeItem<T, CTYPE>>;
|
||||
|
||||
public:
|
||||
QuadTreeContainer(const geom2d::rect<CTYPE>& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0, const size_t nMaxDepth = 8) : root(size, nDepth, nMaxDepth)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Sets the spatial coverage area of teh quadtree
|
||||
void resize(const geom2d::rect<CTYPE>& rArea)
|
||||
{
|
||||
root.resize(rArea);
|
||||
}
|
||||
|
||||
// Inserts an item into the quadtree
|
||||
void insert(const T& item, const geom2d::rect<CTYPE>& itemsize)
|
||||
{
|
||||
QuadTreeItem<T> newItem;
|
||||
newItem.item = item;
|
||||
|
||||
// Item i stored in container
|
||||
m_allItems.emplace_back(newItem);
|
||||
|
||||
// Pointer/Area of item is stored in geom2d::rect<CTYPE> tree
|
||||
m_allItems.back().pItem = root.insert(std::prev(m_allItems.end()), itemsize);
|
||||
}
|
||||
|
||||
// Returns a std::list of pointers to items within the search area
|
||||
std::list<typename IQuadtreeContainer::iterator> search(const geom2d::rect<CTYPE>& rArea) const
|
||||
{
|
||||
std::list<typename IQuadtreeContainer::iterator> listItemPointers;
|
||||
root.search(rArea, listItemPointers);
|
||||
return listItemPointers;
|
||||
}
|
||||
|
||||
void remove(typename IQuadtreeContainer::iterator& item)
|
||||
{
|
||||
// Iterator points to a QuadTreeItem
|
||||
item->pItem.container->erase(item->pItem.iterator);
|
||||
|
||||
// Remove actual item from container
|
||||
m_allItems.erase(item);
|
||||
}
|
||||
|
||||
void relocate(typename IQuadtreeContainer::iterator& item, const geom2d::rect<CTYPE>& itemsize)
|
||||
{
|
||||
// Remove pointer to item from whichever container its stored in
|
||||
item->pItem.container->erase(item->pItem.iterator);
|
||||
|
||||
// Update the items pointer by reinsertion into geom2d::rect<CTYPE> tree
|
||||
item->pItem = root.insert(item, itemsize);
|
||||
|
||||
}
|
||||
|
||||
typename IQuadtreeContainer::iterator begin()
|
||||
{
|
||||
return m_allItems.begin();
|
||||
}
|
||||
|
||||
typename IQuadtreeContainer::iterator end()
|
||||
{
|
||||
return m_allItems.end();
|
||||
}
|
||||
|
||||
typename IQuadtreeContainer::const_iterator cbegin()
|
||||
{
|
||||
return m_allItems.cbegin();
|
||||
}
|
||||
|
||||
typename IQuadtreeContainer::const_iterator cend()
|
||||
{
|
||||
return m_allItems.cend();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return root.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
root.clear();
|
||||
}
|
||||
|
||||
const geom2d::rect<CTYPE>& area()
|
||||
{
|
||||
return root.area();
|
||||
}
|
||||
|
||||
protected:
|
||||
DynamicQuadTree<typename IQuadtreeContainer::iterator> root;
|
||||
IQuadtreeContainer m_allItems;
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user