Merge pull request #1 from OneLoneCoder/master

update
pull/269/head
FalcoDJ 3 years ago committed by GitHub
commit de1842aed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      Extensions/olcPGEX_TransformedView.h
  2. 493
      Videos/OneLoneCoder_PGE_QuadTree1.cpp
  3. 369
      olcPixelGameEngine.h

@ -3,7 +3,7 @@
+-------------------------------------------------------------+ +-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension | | OneLoneCoder Pixel Game Engine Extension |
| Transformed View v1.05 | | Transformed View v1.06 |
+-------------------------------------------------------------+ +-------------------------------------------------------------+
NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES
@ -70,6 +70,7 @@
Removed unused "range" facility in TileTransformView Removed unused "range" facility in TileTransformView
1.04: Added DrawPolygonDecal() for arbitrary polygons 1.04: Added DrawPolygonDecal() for arbitrary polygons
1.05: Clipped DrawSprite() to visible area, massive performance increase 1.05: Clipped DrawSprite() to visible area, massive performance increase
1.06: Fixed error in DrawLine() - Thanks CraisyDaisyRecords (& Fern)!
*/ */
#pragma once #pragma once
@ -375,7 +376,7 @@ namespace olc
void TransformedView::DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p, uint32_t pattern) void TransformedView::DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p, uint32_t pattern)
{ {
DrawLine({ x1, y2 }, { x2, y2 }, p, pattern); DrawLine({ x1, y1 }, { x2, y2 }, p, pattern);
} }
void TransformedView::DrawLine(const olc::vf2d & pos1, const olc::vf2d & pos2, olc::Pixel p, uint32_t pattern) void TransformedView::DrawLine(const olc::vf2d & pos1, const olc::vf2d & pos2, olc::Pixel p, uint32_t pattern)

@ -0,0 +1,493 @@
/*
Quirky Quad Trees Part #1 - Static Quad Tree Implementation
"War... huh... What is it good for? Absolutely nothin..." - javidx9
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.
Video:
~~~~~~
https://youtu.be/ASAowY6yJII
Pan & Zoom with middle mouse, TAB to switch between methods
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
https://www.youtube.com/javidx9extra
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"
#define OLC_PGEX_TRANSFORMEDVIEW
#include "olcPGEX_TransformedView.h"
namespace olc
{
struct rect
{
olc::vf2d pos;
olc::vf2d size;
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 olc::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 olc::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);
}
};
};
// Constrain depth of Quad Tree. Since its floating point, it could in principle sub-divide for
// a very long time, consuming far more time and memory than is sensible
constexpr size_t MAX_DEPTH = 8;
template <typename OBJECT_TYPE>
class StaticQuadTree
{
public:
StaticQuadTree(const olc::rect& size = { {0.0f, 0.0f}, {100.0f, 100.0f} }, const size_t nDepth = 0)
{
m_depth = nDepth;
resize(size);
}
// Force area change on Tree, invalidates this and all child layers
void resize(const olc::rect& rArea)
{
// Erase this layer
clear();
// Recalculate area of children
m_rect = rArea;
olc::vf2d vChildSize = m_rect.size / 2.0f;
// Cache child areas local to this layer
m_rChild =
{
// Top Left
olc::rect(m_rect.pos, vChildSize),
// Top Right
olc::rect({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize),
// Bottom Left
olc::rect({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize),
// Bottom Right
olc::rect(m_rect.pos + vChildSize, vChildSize)
};
}
// Clears the contents of this layer, and all child layers
void clear()
{
// Erase any items stored in this layer
m_pItems.clear();
// Iterate through children, erase them too
for (int i = 0; i < 4; i++)
{
if (m_pChild[i])
m_pChild[i]->clear();
m_pChild[i].reset();
}
}
// Returns a count of how many items are stored in this layer, and all children of this layer
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;
}
// Inserts an object into this layer (or appropriate child layer), given the area the item occupies
void insert(const OBJECT_TYPE& item, const olc::rect& itemsize)
{
// Check each child
for (int i = 0; i < 4; i++)
{
// If the child can wholly contain the item being inserted
if (m_rChild[i].contains(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<StaticQuadTree<OBJECT_TYPE>>(m_rChild[i], m_depth + 1);
}
// Yes, so add item to it
m_pChild[i]->insert(item, itemsize);
return;
}
}
}
// It didnt fit, so item must belong to this quad
m_pItems.push_back({ itemsize, item });
}
// Returns a list of objects in the given search area
std::list<OBJECT_TYPE> search(const olc::rect& rArea) const
{
std::list<OBJECT_TYPE> listItems;
search(rArea, listItems);
return listItems;
}
// Returns the objects in the given search area, by adding to supplied list
void search(const olc::rect& rArea, std::list<OBJECT_TYPE>& 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 (rArea.overlaps(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 (rArea.contains(m_rChild[i]))
m_pChild[i]->items(listItems);
// If child overlaps with search area then checks need
// to be made
else if (m_rChild[i].overlaps(rArea))
m_pChild[i]->search(rArea, listItems);
}
}
}
void items(std::list<OBJECT_TYPE>& listItems) const
{
// No questions asked, just return child items
for (const auto& p : m_pItems)
listItems.push_back(p.second);
// Now add children of this layer's items
for (int i = 0; i < 4; i++)
if (m_pChild[i]) m_pChild[i]->items(listItems);
}
std::list<OBJECT_TYPE> items() const
{
// No questions asked, just return child items
std::list<OBJECT_TYPE> listItems;
items(listItems);
return listItems;
}
// Returns area of this layer
const olc::rect& area()
{
return m_rect;
}
protected:
// Depth of this StaticQuadTree layer
size_t m_depth = 0;
// Area of this StaticQuadTree
olc::rect m_rect;
// 4 child areas of this StaticQuadTree
std::array<olc::rect, 4> m_rChild{};
// 4 potential children of this StaticQuadTree
std::array<std::shared_ptr<StaticQuadTree<OBJECT_TYPE>>, 4> m_pChild{};
// Items which belong to this StaticQuadTree
std::vector<std::pair<olc::rect, OBJECT_TYPE>> m_pItems;
};
template <typename OBJECT_TYPE>
class StaticQuadTreeContainer
{
// Using a std::list as we dont want pointers to be invalidated to objects stored in the
// tree should the contents of the tree change
using QuadTreeContainer = std::list<OBJECT_TYPE>;
protected:
// The actual container
QuadTreeContainer m_allItems;
// Use our StaticQuadTree to store "pointers" instead of objects - this reduces
// overheads when moving or copying objects
StaticQuadTree<typename QuadTreeContainer::iterator> root;
public:
StaticQuadTreeContainer(const olc::rect& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0) : root(size, nDepth)
{
}
// Sets the spatial coverage area of the quadtree
// Invalidates tree
void resize(const olc::rect& rArea)
{
root.resize(rArea);
}
// Returns number of items within tree
size_t size() const
{
return m_allItems.size();
}
// Returns true if tree is empty
bool empty() const
{
return m_allItems.empty();
}
// Removes all items from tree
void clear()
{
root.clear();
m_allItems.clear();
}
// Convenience functions for ranged for loop
typename QuadTreeContainer::iterator begin()
{
return m_allItems.begin();
}
typename QuadTreeContainer::iterator end()
{
return m_allItems.end();
}
typename QuadTreeContainer::const_iterator cbegin()
{
return m_allItems.cbegin();
}
typename QuadTreeContainer::const_iterator cend()
{
return m_allItems.cend();
}
// Insert item into tree in specified area
void insert(const OBJECT_TYPE& item, const olc::rect& itemsize)
{
// Item is stored in container
m_allItems.push_back(item);
// Pointer/Area of item is stored in quad tree
root.insert(std::prev(m_allItems.end()), itemsize);
}
// Returns a std::list of pointers to items within the search area
std::list<typename QuadTreeContainer::iterator> search(const olc::rect& rArea) const
{
std::list<typename QuadTreeContainer::iterator> listItemPointers;
root.search(rArea, listItemPointers);
return listItemPointers;
}
};
// The Example!
class Example_StaticQuadTree : public olc::PixelGameEngine
{
public:
Example_StaticQuadTree()
{
sAppName = "Static QuadTree";
}
protected:
olc::TransformedView tv;
// An example object of something in 2D space
struct SomeObjectWithArea
{
olc::vf2d vPos;
olc::vf2d vVel;
olc::vf2d vSize;
olc::Pixel colour;
};
// A regular list of the objects
std::list<SomeObjectWithArea> vecObjects;
// An equivalent quad tree of the objects
StaticQuadTreeContainer<SomeObjectWithArea> treeObjects;
// The "length" of one side of the "world" the objects reside in
float fArea = 100000.0f;
bool bUseQuadTree = true;
public:
bool OnUserCreate() override
{
// Transform View - enables Pan & Zoom
tv.Initialise({ ScreenWidth(), ScreenHeight() });
// Create the tree, and size it to the world
treeObjects.resize(olc::rect({ 0.0f, 0.0f }, { fArea, fArea }));
// Dirty random float generator
auto rand_float = [](const float a, const float b)
{
return float(rand()) / float(RAND_MAX) * (b - a) + a;
};
// Create 1,000,000 objects, push into both containers (so 2,000,000 I suppose :P )
for (int i = 0; i < 1000000; i++)
{
SomeObjectWithArea ob;
ob.vPos = { rand_float(0.0f, fArea), rand_float(0.0f, fArea) };
ob.vSize = { rand_float(0.1f, 100.0f), rand_float(0.1f, 100.0f) };
ob.colour = olc::Pixel(rand() % 256, rand() % 256, rand() % 256);
treeObjects.insert(ob, olc::rect(ob.vPos, ob.vSize));
vecObjects.push_back(ob);
}
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Tab switches between modes
if (GetKey(olc::Key::TAB).bPressed)
bUseQuadTree = !bUseQuadTree;
tv.HandlePanAndZoom();
// Get rectangle that equates to screen in world space
olc::rect rScreen = { tv.GetWorldTL(), tv.GetWorldBR() - tv.GetWorldTL() };
size_t nObjectCount = 0;
if (bUseQuadTree)
{
// QUAD TREE MODE
auto tpStart = std::chrono::system_clock::now();
// Use search function to return list of pointers to objects in that area
for (const auto& object : treeObjects.search(rScreen))
{
tv.FillRectDecal(object->vPos, object->vSize, object->colour);
nObjectCount++;
}
std::chrono::duration<float> duration = std::chrono::system_clock::now() - tpStart;
std::string sOutput = "Quadtree " + std::to_string(nObjectCount) + "/" + std::to_string(vecObjects.size()) + " in " + std::to_string(duration.count());
DrawStringDecal({ 4, 4 }, sOutput, olc::BLACK, { 4.0f, 8.0f });
DrawStringDecal({ 2, 2 }, sOutput, olc::WHITE, { 4.0f, 8.0f });
}
else
{
// LINEAR SEARCH MODE
auto tpStart = std::chrono::system_clock::now();
// Blindly check all objects to see if they overlap with screen
for (const auto& object : vecObjects)
{
if (rScreen.overlaps({ object.vPos, object.vSize }))
{
tv.FillRectDecal(object.vPos, object.vSize, object.colour);
nObjectCount++;
}
}
std::chrono::duration<float> duration = std::chrono::system_clock::now() - tpStart;
std::string sOutput = "Linear " + std::to_string(nObjectCount) + "/" + std::to_string(vecObjects.size()) + " in " + std::to_string(duration.count());
DrawStringDecal({ 4, 4 }, sOutput, olc::BLACK, { 4.0f, 8.0f });
DrawStringDecal({ 2, 2 }, sOutput, olc::WHITE, { 4.0f, 8.0f });
}
return true;
}
};
int main()
{
Example_StaticQuadTree demo;
if (demo.Construct(1280, 960, 1, 1, false, false))
demo.Start();
return 0;
}

@ -3,7 +3,7 @@
olcPixelGameEngine.h olcPixelGameEngine.h
+-------------------------------------------------------------+ +-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine v2.17 | | OneLoneCoder Pixel Game Engine v2.19 |
| "What do you need? Pixels... Lots of Pixels..." - javidx9 | | "What do you need? Pixels... Lots of Pixels..." - javidx9 |
+-------------------------------------------------------------+ +-------------------------------------------------------------+
@ -282,7 +282,23 @@
+Reintroduced sub-pixel decals +Reintroduced sub-pixel decals
+Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses +Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses
+olc::Sprite::GetPixel() - Clamp Mode +olc::Sprite::GetPixel() - Clamp Mode
2.18: +Option to not "dirty" layers with SetDrawTarget() - Thanks TerasKasi!
=Detection for Mac M1, fix for scroll wheel interrogation - Thanks ruarq!
2.19: Textual Input(of)course Edition!
=Built in font is now olc::Renderable
+EnablePixelTransfer() - Gate if layer content transfers occur (speedup in decal only apps)
+TextEntryEnable() - Enables/Disables text entry mode
+TextEntryGetString() - Gets the current accumulated string in text entry mode
+TextEntryGetCursor() - Gets the current cursor position in text entry mode
+IsTextEntryEnabled() - Returns true if text entry mode is activated
+OnTextEntryComplete() - Override is called when user presses "ENTER" in text entry mode
+Potential for regional keyboard mappings - needs volunteers to do this
+ConsoleShow() - Opens built in command console
+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
!! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! !! 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 !! !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !!
@ -362,7 +378,7 @@ int main()
#include <cstring> #include <cstring>
#pragma endregion #pragma endregion
#define PGE_VER 217 #define PGE_VER 219
// O------------------------------------------------------------------------------O // O------------------------------------------------------------------------------O
// | COMPILER CONFIGURATION ODDITIES | // | COMPILER CONFIGURATION ODDITIES |
@ -380,6 +396,10 @@ int main()
#endif #endif
#endif #endif
#if !defined(OLC_KEYBOARD_UK)
#define OLC_KEYBOARD_UK
#endif
#if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) #if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS)
// C++14 // C++14
@ -909,6 +929,11 @@ namespace olc
// Called once on application termination, so you can be one clean coder // Called once on application termination, so you can be one clean coder
virtual bool OnUserDestroy(); virtual bool OnUserDestroy();
// Called when a text entry is confirmed with "enter" key
virtual void OnTextEntryComplete(const std::string& sText);
// Called when a console command is executed
virtual bool OnConsoleCommand(const std::string& sCommand);
public: // Hardware Interfaces public: // Hardware Interfaces
// Returns true if window is currently in focus // Returns true if window is currently in focus
bool IsFocused() const; bool IsFocused() const;
@ -958,7 +983,7 @@ namespace olc
public: // CONFIGURATION ROUTINES public: // CONFIGURATION ROUTINES
// Layer targeting functions // Layer targeting functions
void SetDrawTarget(uint8_t layer); void SetDrawTarget(uint8_t layer, bool bDirty = true);
void EnableLayer(uint8_t layer, bool b); void EnableLayer(uint8_t layer, bool b);
void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); void SetLayerOffset(uint8_t layer, const olc::vf2d& offset);
void SetLayerOffset(uint8_t layer, float x, float y); void SetLayerOffset(uint8_t layer, float x, float y);
@ -1071,6 +1096,30 @@ namespace olc
// Clip a line segment to visible area // Clip a line segment to visible area
bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2); bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2);
// Dont allow PGE to mark layers as dirty, so pixel graphics don't update
void EnablePixelTransfer(const bool bEnable = true);
// Command Console Routines
void ConsoleShow(const olc::Key &keyExit, bool bSuspendTime = true);
bool IsConsoleShowing() const;
void ConsoleClear();
std::stringstream& ConsoleOut();
void ConsoleCaptureStdOut(const bool bCapture);
// Text Entry Routines
void TextEntryEnable(const bool bEnable, const std::string& sText = "");
std::string TextEntryGetString() const;
int32_t TextEntryGetCursor() const;
bool IsTextEntryEnabled() const;
private:
void UpdateTextEntry();
void UpdateConsole();
public:
// Experimental Lightweight 3D Routines ================ // Experimental Lightweight 3D Routines ================
#ifdef OLC_ENABLE_EXPERIMENTAL #ifdef OLC_ENABLE_EXPERIMENTAL
// Set Manual View Matrix // Set Manual View Matrix
@ -1123,9 +1172,9 @@ namespace olc
bool bEnableVSYNC = false; bool bEnableVSYNC = false;
float fFrameTimer = 1.0f; float fFrameTimer = 1.0f;
float fLastElapsed = 0.0f; float fLastElapsed = 0.0f;
int nFrameCount = 0; int nFrameCount = 0;
Sprite* fontSprite = nullptr; bool bSuspendTextureTransfer = false;
Decal* fontDecal = nullptr; Renderable fontRenderable;
std::vector<LayerDesc> vLayers; std::vector<LayerDesc> vLayers;
uint8_t nTargetLayer = 0; uint8_t nTargetLayer = 0;
uint32_t nLastFPS = 0; uint32_t nLastFPS = 0;
@ -1136,6 +1185,27 @@ namespace olc
std::chrono::time_point<std::chrono::system_clock> m_tp1, m_tp2; std::chrono::time_point<std::chrono::system_clock> m_tp1, m_tp2;
std::vector<olc::vi2d> vFontSpacing; std::vector<olc::vi2d> vFontSpacing;
// Command Console Specific
bool bConsoleShow = false;
bool bConsoleSuspendTime = false;
olc::Key keyConsoleExit = olc::Key::F1;
std::stringstream ssConsoleOutput;
std::streambuf* sbufOldCout = nullptr;
olc::vi2d vConsoleSize;
olc::vi2d vConsoleCursor = { 0,0 };
olc::vf2d vConsoleCharacterScale = { 1.0f, 2.0f };
std::vector<std::string> sConsoleLines;
std::list<std::string> sCommandHistory;
std::list<std::string>::iterator sCommandHistoryIt;
// Text Entry Specific
bool bTextEntryEnable = false;
std::string sTextEntryString = "";
int32_t nTextEntryCursor = 0;
std::vector<std::tuple<olc::Key, std::string, std::string>> vKeyboardMap;
// State of keyboard // State of keyboard
bool pKeyNewState[256] = { 0 }; bool pKeyNewState[256] = { 0 };
bool pKeyOldState[256] = { 0 }; bool pKeyOldState[256] = { 0 };
@ -1769,12 +1839,12 @@ namespace olc
} }
} }
void PixelGameEngine::SetDrawTarget(uint8_t layer) void PixelGameEngine::SetDrawTarget(uint8_t layer, bool bDirty)
{ {
if (layer < vLayers.size()) if (layer < vLayers.size())
{ {
pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); pDrawTarget = vLayers[layer].pDrawTarget.Sprite();
vLayers[layer].bUpdate = true; vLayers[layer].bUpdate = bDirty;
nTargetLayer = layer; nTargetLayer = layer;
} }
} }
@ -2105,7 +2175,7 @@ namespace olc
{ renderer->ClearBuffer(p, bDepth); } { renderer->ClearBuffer(p, bDepth); }
olc::Sprite* PixelGameEngine::GetFontSprite() olc::Sprite* PixelGameEngine::GetFontSprite()
{ return fontSprite; } { return fontRenderable.Sprite(); }
bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2) bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2)
{ {
@ -2140,6 +2210,11 @@ namespace olc
return true; return true;
} }
void PixelGameEngine::EnablePixelTransfer(const bool bEnable)
{
bSuspendTextureTransfer = !bEnable;
}
void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p)
{ FillRect(pos.x, pos.y, size.x, size.y, p); } { FillRect(pos.x, pos.y, size.x, size.y, p); }
@ -2776,7 +2851,7 @@ namespace olc
{ {
int32_t ox = (c - 32) % 16; int32_t ox = (c - 32) % 16;
int32_t oy = (c - 32) / 16; int32_t oy = (c - 32) / 16;
DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); DrawPartialDecal(pos + spos, fontRenderable.Decal(), {float(ox) * 8.0f, float(oy) * 8.0f}, {8.0f, 8.0f}, scale, col);
spos.x += 8.0f * scale.x; spos.x += 8.0f * scale.x;
} }
} }
@ -2799,7 +2874,7 @@ namespace olc
{ {
int32_t ox = (c - 32) % 16; int32_t ox = (c - 32) % 16;
int32_t oy = (c - 32) / 16; int32_t oy = (c - 32) / 16;
DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col);
spos.x += float(vFontSpacing[c - 32].y) * scale.x; spos.x += float(vFontSpacing[c - 32].y) * scale.x;
} }
} }
@ -2822,7 +2897,7 @@ namespace olc
{ {
int32_t ox = (c - 32) % 16; int32_t ox = (c - 32) % 16;
int32_t oy = (c - 32) / 16; int32_t oy = (c - 32) / 16;
DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col);
spos.x -= 8.0f; spos.x -= 8.0f;
} }
} }
@ -2845,7 +2920,7 @@ namespace olc
{ {
int32_t ox = (c - 32) % 16; int32_t ox = (c - 32) % 16;
int32_t oy = (c - 32) / 16; int32_t oy = (c - 32) / 16;
DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col);
spos.x -= float(vFontSpacing[c - 32].y); spos.x -= float(vFontSpacing[c - 32].y);
} }
} }
@ -2899,7 +2974,7 @@ namespace olc
{ {
for (uint32_t i = 0; i < 8; i++) for (uint32_t i = 0; i < 8; i++)
for (uint32_t j = 0; j < 8; j++) for (uint32_t j = 0; j < 8; j++)
if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0)
for (uint32_t is = 0; is < scale; is++) for (uint32_t is = 0; is < scale; is++)
for (uint32_t js = 0; js < scale; js++) for (uint32_t js = 0; js < scale; js++)
Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col);
@ -2908,7 +2983,7 @@ namespace olc
{ {
for (uint32_t i = 0; i < 8; i++) for (uint32_t i = 0; i < 8; i++)
for (uint32_t j = 0; j < 8; j++) for (uint32_t j = 0; j < 8; j++)
if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0)
Draw(x + sx + i, y + sy + j, col); Draw(x + sx + i, y + sy + j, col);
} }
sx += 8 * scale; sx += 8 * scale;
@ -2967,7 +3042,7 @@ namespace olc
{ {
for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++)
for (int32_t j = 0; j < 8; j++) for (int32_t j = 0; j < 8; j++)
if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0)
for (int32_t is = 0; is < int(scale); is++) for (int32_t is = 0; is < int(scale); is++)
for (int32_t js = 0; js < int(scale); js++) for (int32_t js = 0; js < int(scale); js++)
Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col);
@ -2976,7 +3051,7 @@ namespace olc
{ {
for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++)
for (int32_t j = 0; j < 8; j++) for (int32_t j = 0; j < 8; j++)
if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0)
Draw(x + sx + i, y + sy + j, col); Draw(x + sx + i, y + sy + j, col);
} }
sx += vFontSpacing[c - 32].y * scale; sx += vFontSpacing[c - 32].y * scale;
@ -3004,6 +3079,204 @@ namespace olc
if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; if (fBlendFactor > 1.0f) fBlendFactor = 1.0f;
} }
std::stringstream& PixelGameEngine::ConsoleOut()
{ return ssConsoleOutput; }
bool PixelGameEngine::IsConsoleShowing() const
{ return bConsoleShow; }
void PixelGameEngine::ConsoleShow(const olc::Key& keyExit, bool bSuspendTime)
{
if (bConsoleShow)
return;
bConsoleShow = true;
bConsoleSuspendTime = bSuspendTime;
TextEntryEnable(true);
keyConsoleExit = keyExit;
pKeyboardState[keyConsoleExit].bHeld = false;
pKeyboardState[keyConsoleExit].bPressed = false;
pKeyboardState[keyConsoleExit].bReleased = true;
}
void PixelGameEngine::ConsoleClear()
{ sConsoleLines.clear(); }
void PixelGameEngine::ConsoleCaptureStdOut(const bool bCapture)
{
if(bCapture)
sbufOldCout = std::cout.rdbuf(ssConsoleOutput.rdbuf());
else
std::cout.rdbuf(sbufOldCout);
}
void PixelGameEngine::UpdateConsole()
{
if (GetKey(keyConsoleExit).bPressed)
{
TextEntryEnable(false);
bConsoleSuspendTime = false;
bConsoleShow = false;
return;
}
// Keep Console sizes based in real screen dimensions
vConsoleCharacterScale = olc::vf2d(1.0f, 2.0f) / (olc::vf2d(vViewSize) * vInvScreenSize);
vConsoleSize = (vViewSize / olc::vi2d(8, 16)) - olc::vi2d(2, 4);
// If console has changed size, simply reset it
if (vConsoleSize.y != sConsoleLines.size())
{
vConsoleCursor = { 0,0 };
sConsoleLines.clear();
sConsoleLines.resize(vConsoleSize.y);
}
auto TypeCharacter = [&](const char c)
{
if (c >= 32 && c < 127)
{
sConsoleLines[vConsoleCursor.y].append(1, c);
vConsoleCursor.x++;
}
if( c == '\n' || vConsoleCursor.x >= vConsoleSize.x)
{
vConsoleCursor.y++; vConsoleCursor.x = 0;
}
if (vConsoleCursor.y >= vConsoleSize.y)
{
vConsoleCursor.y = vConsoleSize.y - 1;
for (size_t i = 1; i < vConsoleSize.y; i++)
sConsoleLines[i - 1] = sConsoleLines[i];
sConsoleLines[vConsoleCursor.y].clear();
}
};
// Empty out "std::cout", parsing as we go
while (ssConsoleOutput.rdbuf()->sgetc() != -1)
{
char c = ssConsoleOutput.rdbuf()->sbumpc();
TypeCharacter(c);
}
// Draw Shadow
GradientFillRectDecal({ 0,0 }, olc::vf2d(vScreenSize), olc::PixelF(0, 0, 0.5f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f));
// Draw the console buffer
SetDecalMode(olc::DecalMode::NORMAL);
for (int32_t nLine = 0; nLine < vConsoleSize.y; nLine++)
DrawStringDecal(olc::vf2d( 1, 1 + float(nLine) ) * vConsoleCharacterScale * 8.0f, sConsoleLines[nLine], olc::WHITE, vConsoleCharacterScale);
// Draw Input State
FillRectDecal(olc::vf2d(1 + float((TextEntryGetCursor() + 1)), 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, olc::vf2d(8, 8) * vConsoleCharacterScale, olc::DARK_CYAN);
DrawStringDecal(olc::vf2d(1, 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, std::string(">") + TextEntryGetString(), olc::YELLOW, vConsoleCharacterScale);
}
void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText)
{
if (bEnable)
{
nTextEntryCursor = int32_t(sText.size());
sTextEntryString = sText;
bTextEntryEnable = true;
}
else
{
bTextEntryEnable = false;
}
}
std::string PixelGameEngine::TextEntryGetString() const
{ return sTextEntryString; }
int32_t PixelGameEngine::TextEntryGetCursor() const
{ return nTextEntryCursor; }
bool PixelGameEngine::IsTextEntryEnabled() const
{ return bTextEntryEnable; }
void PixelGameEngine::UpdateTextEntry()
{
// Check for typed characters
for (const auto& key : vKeyboardMap)
if (GetKey(std::get<0>(key)).bPressed)
{
sTextEntryString.insert(nTextEntryCursor, GetKey(olc::Key::SHIFT).bHeld ? std::get<2>(key) : std::get<1>(key));
nTextEntryCursor++;
}
// Check for command characters
if (GetKey(olc::Key::LEFT).bPressed)
nTextEntryCursor = std::max(0, nTextEntryCursor - 1);
if (GetKey(olc::Key::RIGHT).bPressed)
nTextEntryCursor = std::min(int32_t(sTextEntryString.size()), nTextEntryCursor + 1);
if (GetKey(olc::Key::BACK).bPressed && nTextEntryCursor > 0)
{
sTextEntryString.erase(nTextEntryCursor-1, 1);
nTextEntryCursor = std::max(0, nTextEntryCursor - 1);
}
if (GetKey(olc::Key::DEL).bPressed && nTextEntryCursor < sTextEntryString.size())
sTextEntryString.erase(nTextEntryCursor, 1);
if (GetKey(olc::Key::UP).bPressed)
{
if (!sCommandHistory.empty())
{
if (sCommandHistoryIt != sCommandHistory.begin())
sCommandHistoryIt--;
nTextEntryCursor = int32_t(sCommandHistoryIt->size());
sTextEntryString = *sCommandHistoryIt;
}
}
if (GetKey(olc::Key::DOWN).bPressed)
{
if (!sCommandHistory.empty())
{
if (sCommandHistoryIt != sCommandHistory.end())
{
sCommandHistoryIt++;
if (sCommandHistoryIt != sCommandHistory.end())
{
nTextEntryCursor = int32_t(sCommandHistoryIt->size());
sTextEntryString = *sCommandHistoryIt;
}
else
{
nTextEntryCursor = 0;
sTextEntryString = "";
}
}
}
}
if (GetKey(olc::Key::ENTER).bPressed)
{
if (bConsoleShow)
{
std::cout << ">" + sTextEntryString + "\n";
if (OnConsoleCommand(sTextEntryString))
{
sCommandHistory.push_back(sTextEntryString);
sCommandHistoryIt = sCommandHistory.end();
}
sTextEntryString.clear();
nTextEntryCursor = 0;
}
else
{
OnTextEntryComplete(sTextEntryString);
TextEntryEnable(false);
}
}
}
// User must override these functions as required. I have not made // User must override these functions as required. I have not made
// them abstract because I do need a default behaviour to occur if // them abstract because I do need a default behaviour to occur if
// they are not overwritten // they are not overwritten
@ -3016,7 +3289,12 @@ namespace olc
bool PixelGameEngine::OnUserDestroy() bool PixelGameEngine::OnUserDestroy()
{ return true; } { return true; }
void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); }
bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; }
// Externalised API
void PixelGameEngine::olc_UpdateViewport() void PixelGameEngine::olc_UpdateViewport()
{ {
int32_t ww = vScreenSize.x * vPixelSize.x; int32_t ww = vScreenSize.x * vPixelSize.x;
@ -3150,6 +3428,9 @@ namespace olc
float fElapsedTime = elapsedTime.count(); float fElapsedTime = elapsedTime.count();
fLastElapsed = fElapsedTime; fLastElapsed = fElapsedTime;
if (bConsoleSuspendTime)
fElapsedTime = 0.0f;
// Some platforms will need to check for events // Some platforms will need to check for events
platform->HandleSystemEvent(); platform->HandleSystemEvent();
@ -3185,7 +3466,10 @@ namespace olc
nMouseWheelDelta = nMouseWheelDeltaCache; nMouseWheelDelta = nMouseWheelDeltaCache;
nMouseWheelDeltaCache = 0; nMouseWheelDeltaCache = 0;
// renderer->ClearBuffer(olc::BLACK, true); if (bTextEntryEnable)
{
UpdateTextEntry();
}
// Handle Frame Update // Handle Frame Update
bool bExtensionBlockFrame = false; bool bExtensionBlockFrame = false;
@ -3196,6 +3480,12 @@ namespace olc
} }
for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime);
if (bConsoleShow)
{
SetDrawTarget((uint8_t)0);
UpdateConsole();
}
// Display Frame // Display Frame
renderer->UpdateViewport(vViewPos, vViewSize); renderer->UpdateViewport(vViewPos, vViewSize);
renderer->ClearBuffer(olc::BLACK, true); renderer->ClearBuffer(olc::BLACK, true);
@ -3213,7 +3503,7 @@ namespace olc
if (layer->funcHook == nullptr) if (layer->funcHook == nullptr)
{ {
renderer->ApplyTexture(layer->pDrawTarget.Decal()->id); renderer->ApplyTexture(layer->pDrawTarget.Decal()->id);
if (layer->bUpdate) if (!bSuspendTextureTransfer && layer->bUpdate)
{ {
layer->pDrawTarget.Decal()->Update(); layer->pDrawTarget.Decal()->Update();
layer->bUpdate = false; layer->bUpdate = false;
@ -3234,6 +3524,8 @@ namespace olc
} }
} }
// Present Graphics to screen // Present Graphics to screen
renderer->DisplayFrame(); renderer->DisplayFrame();
@ -3270,7 +3562,8 @@ namespace olc
data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`<O`000GP800000000"; data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`<O`000GP800000000";
data += "?P9PL020O`<`N3R0@E4HC7b0@ET<ATB0@@l6C4B0O`H3N7b0?P01L3R000000020"; data += "?P9PL020O`<`N3R0@E4HC7b0@ET<ATB0@@l6C4B0O`H3N7b0?P01L3R000000020";
fontSprite = new olc::Sprite(128, 48); fontRenderable.Create(128, 48);
int px = 0, py = 0; int px = 0, py = 0;
for (size_t b = 0; b < 1024; b += 4) for (size_t b = 0; b < 1024; b += 4)
{ {
@ -3283,12 +3576,12 @@ namespace olc
for (int i = 0; i < 24; i++) for (int i = 0; i < 24; i++)
{ {
int k = r & (1 << i) ? 255 : 0; int k = r & (1 << i) ? 255 : 0;
fontSprite->SetPixel(px, py, olc::Pixel(k, k, k, k)); fontRenderable.Sprite()->SetPixel(px, py, olc::Pixel(k, k, k, k));
if (++py == 48) { px++; py = 0; } if (++py == 48) { px++; py = 0; }
} }
} }
fontDecal = new olc::Decal(fontSprite); fontRenderable.Decal()->Update();
constexpr std::array<uint8_t, 96> vSpacing = { { constexpr std::array<uint8_t, 96> vSpacing = { {
0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08,
@ -3300,6 +3593,32 @@ namespace olc
for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 });
// UK Standard Layout
#ifdef OLC_KEYBOARD_UK
vKeyboardMap =
{
{olc::Key::A, "a", "A"}, {olc::Key::B, "b", "B"}, {olc::Key::C, "c", "C"}, {olc::Key::D, "d", "D"}, {olc::Key::E, "e", "E"},
{olc::Key::F, "f", "F"}, {olc::Key::G, "g", "G"}, {olc::Key::H, "h", "H"}, {olc::Key::I, "i", "I"}, {olc::Key::J, "j", "J"},
{olc::Key::K, "k", "K"}, {olc::Key::L, "l", "L"}, {olc::Key::M, "m", "M"}, {olc::Key::N, "n", "N"}, {olc::Key::O, "o", "O"},
{olc::Key::P, "p", "P"}, {olc::Key::Q, "q", "Q"}, {olc::Key::R, "r", "R"}, {olc::Key::S, "s", "S"}, {olc::Key::T, "t", "T"},
{olc::Key::U, "u", "U"}, {olc::Key::V, "v", "V"}, {olc::Key::W, "w", "W"}, {olc::Key::X, "x", "X"}, {olc::Key::Y, "y", "Y"},
{olc::Key::Z, "z", "Z"},
{olc::Key::K0, "0", ")"}, {olc::Key::K1, "1", "!"}, {olc::Key::K2, "2", "\""}, {olc::Key::K3, "3", "#"}, {olc::Key::K4, "4", "$"},
{olc::Key::K5, "5", "%"}, {olc::Key::K6, "6", "^"}, {olc::Key::K7, "7", "&"}, {olc::Key::K8, "8", "*"}, {olc::Key::K9, "9", "("},
{olc::Key::NP0, "0", "0"}, {olc::Key::NP1, "1", "1"}, {olc::Key::NP2, "2", "2"}, {olc::Key::NP3, "3", "3"}, {olc::Key::NP4, "4", "4"},
{olc::Key::NP5, "5", "5"}, {olc::Key::NP6, "6", "6"}, {olc::Key::NP7, "7", "7"}, {olc::Key::NP8, "8", "8"}, {olc::Key::NP9, "9", "9"},
{olc::Key::NP_MUL, "*", "*"}, {olc::Key::NP_DIV, "/", "/"}, {olc::Key::NP_ADD, "+", "+"}, {olc::Key::NP_SUB, "-", "-"}, {olc::Key::NP_DECIMAL, ".", "."},
{olc::Key::PERIOD, ".", ">"}, {olc::Key::EQUALS, "=", "+"}, {olc::Key::COMMA, ",", "<"}, {olc::Key::MINUS, "-", "_"}, {olc::Key::SPACE, " ", " "},
{olc::Key::OEM_1, ";", ":"}, {olc::Key::OEM_2, "/", "?"}, {olc::Key::OEM_3, "\'", "@"}, {olc::Key::OEM_4, "[", "{"},
{olc::Key::OEM_5, "\\", "|"}, {olc::Key::OEM_6, "]", "}"}, {olc::Key::OEM_7, "#", "~"},
// {olc::Key::TAB, "\t", "\t"}
};
#endif
} }
void PixelGameEngine::pgex_Register(olc::PGEX* pgex) void PixelGameEngine::pgex_Register(olc::PGEX* pgex)
@ -5086,7 +5405,11 @@ namespace olc {
static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) {
static const SEL deltaYSel = sel_registerName("deltaY"); static const SEL deltaYSel = sel_registerName("deltaY");
#if defined(__aarch64__) // Thanks ruarq!
double deltaY = ((double (*)(id, SEL))objc_msgSend)(theEvent, deltaYSel);
#else
double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel);
#endif
for (int i = 0; i < abs(deltaY); i++) { for (int i = 0; i < abs(deltaY); i++) {
if (deltaY > 0) { if (deltaY > 0) {

Loading…
Cancel
Save