From 12c8638c152e00e47a3708602aba99c12d69792d Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Tue, 20 Sep 2022 21:35:46 +0100 Subject: [PATCH] Added Utility Palette, and Utility Quadtree --- extensions/olcPGEX_QuickGUI.h | 2 +- utilities/coming_soon.txt | 0 utilities/olcUTIL_Palette.h | 238 +++++++++++++++++++++++ utilities/olcUTIL_QuadTree.h | 353 ++++++++++++++++++++++++++++++++++ 4 files changed, 592 insertions(+), 1 deletion(-) delete mode 100644 utilities/coming_soon.txt create mode 100644 utilities/olcUTIL_Palette.h create mode 100644 utilities/olcUTIL_QuadTree.h diff --git a/extensions/olcPGEX_QuickGUI.h b/extensions/olcPGEX_QuickGUI.h index 664d0c2..cbf5647 100644 --- a/extensions/olcPGEX_QuickGUI.h +++ b/extensions/olcPGEX_QuickGUI.h @@ -649,7 +649,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) diff --git a/utilities/coming_soon.txt b/utilities/coming_soon.txt deleted file mode 100644 index e69de29..0000000 diff --git a/utilities/olcUTIL_Palette.h b/utilities/olcUTIL_Palette.h new file mode 100644 index 0000000..68b1755 --- /dev/null +++ b/utilities/olcUTIL_Palette.h @@ -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& 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& p1, std::pair& p2) + { + return p2.first > p1.first; + }); + } + + ReconstructIndex(); + } + + private: + // Vector stores all palette milestones + std::vector> vColours; + // Array stores fast LUT + std::array aIndexedPalette; + + inline void ReconstructIndex() + { + // Reconstruct Indexed palette + for (int i = 0; i < 256; i++) + { + double t = double(i) / 255.0; + aIndexedPalette[i] = Sample(t); + } + } + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL_QuadTree.h b/utilities/olcUTIL_QuadTree.h new file mode 100644 index 0000000..3dbccbb --- /dev/null +++ b/utilities/olcUTIL_QuadTree.h @@ -0,0 +1,353 @@ +/* + OneLoneCoder - QuadTree v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A Dynamic geom2d::rect-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" + +constexpr size_t MAX_DEPTH = 8; + +namespace olc::utils +{ + //struct geom2d::rect + //{ + // olc::vf2d pos; + // olc::vf2d size; + + // geom2d::rect(const olc::vf2d& p = { 0.0f, 0.0f }, const olc::vf2d& s = { 1.0f, 1.0f }) : pos(p), size(s) + // { + + // } + + // constexpr bool contains(const olc::vf2d& p) const + // { + // return !(p.x < pos.x || p.y < pos.y || p.x >= (pos.x + size.x) || p.y >= (pos.y + size.y)); + // } + + // constexpr bool contains(const geom2d::rect& r) const + // { + // return (r.pos.x >= pos.x) && (r.pos.x + r.size.x < pos.x + size.x) && + // (r.pos.y >= pos.y) && (r.pos.y + r.size.y < pos.y + size.y); + // } + + // constexpr bool overlaps(const geom2d::rect& r) const + // { + // return (pos.x < r.pos.x + r.size.x && pos.x + size.x >= r.pos.x && pos.y < r.pos.y + r.size.y && pos.y + size.y >= r.pos.y); + // } + //}; + + template + struct QuadTreeItemLocation + { + typename std::list, T>>* container = nullptr; + typename std::list, T>>::iterator iterator; + }; + + template + class DynamicQuadTree + { + public: + DynamicQuadTree(const geom2d::rect& size, const size_t nDepth = 0) + { + m_depth = nDepth; + m_rect = size; + resize(m_rect); + } + + // Insert a region into this area + QuadTreeItemLocation insert(const pT item, const geom2d::rect& itemsize) + { + for (int i = 0; i < 4; i++) + { + if (geom2d::contains(m_rChild[i], itemsize)) + { + // Have we reached depth limit? + if (m_depth + 1 < MAX_DEPTH) + { + // No, so does child exist? + if (!m_pChild[i]) + { + // No, so create it + m_pChild[i] = std::make_shared>(m_rChild[i], m_depth + 1); + } + + // Yes, so add item to it + return m_pChild[i]->insert(item, itemsize); + } + } + } + + // It didnt fit, so item must belong to this geom2d::rect + m_pItems.push_back({ itemsize, item }); + return { &m_pItems, std::prev(m_pItems.end()) }; + } + + void relocate(pT item, const geom2d::rect& 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& rArea, std::list& 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& 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& rArea) + { + clear(); + m_rect = rArea; + olc::vf2d vChildSize = m_rect.size / 2.0f; + m_rChild = + { + geom2d::rect(m_rect.pos, vChildSize), + geom2d::rect({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize), + geom2d::rect({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize), + geom2d::rect(m_rect.pos + vChildSize, vChildSize) + }; + } + + + const geom2d::rect& area() + { + return m_rect; + } + + protected: + size_t m_depth = 0; + + // Area of this quadnode + geom2d::rect m_rect; + + // 4 child areas of this quadnode + std::array, 4> m_rChild{}; + + // 4 potential children of this quadnode + std::array>, 4> m_pChild{}; + + // Items which belong to this quadnode + std::list, pT>> m_pItems; + + }; + + template + 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>::iterator> pItem; + }; + + template + class QuadTreeContainer + { + using IQuadtreeContainer = std::list>; + + public: + QuadTreeContainer(const geom2d::rect& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0) : root(size, nDepth) + { + + } + + // Sets the spatial coverage area of teh quadtree + void resize(const geom2d::rect& rArea) + { + root.resize(rArea); + } + + // Inserts an item into the quadtree + void insert(const T& item, const geom2d::rect& itemsize) + { + QuadTreeItem newItem; + newItem.item = item; + + // Item i stored in container + m_allItems.emplace_back(newItem); + + // Pointer/Area of item is stored in geom2d::rect 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 search(const geom2d::rect& rArea) const + { + std::list 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& 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 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& area() + { + return root.area(); + } + + protected: + DynamicQuadTree root; + IQuadtreeContainer m_allItems; + }; +}