diff --git a/examples/TEST_Camera2D.cpp b/examples/TEST_Camera2D.cpp new file mode 100644 index 00000000..a253c6d9 --- /dev/null +++ b/examples/TEST_Camera2D.cpp @@ -0,0 +1,219 @@ +/* + Example file for olcUTIL_Camera2D.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" + +#define OLC_PGEX_TRANSFORMEDVIEW +#include "extensions/olcPGEX_TransformedView.h" + +#include "utilities/olcUTIL_Camera2D.h" + +/* + To demonstrate the camera, we need a world. In this case its a simle tile + world of 80x75 tiles, and each tile is 32x32 screen pixels. + + A transformed view is used to navigate the world manually via the middle + mouse button in "free roam" mode, or controlled by the camera. + + Specifically a Tile Transformed View is used, which means all units for + drawing and for the camera are specified in tile space, i.e. 1 tile is + 1x1 units (regardless of pixel size) + + No assets are used for this application, so the world is constructed + out of coloured squares so you can see you are moving through it. + + Pressing "TAB" key will swap between "free roam" and "play" modes. In + free roam mode, you can use middle mouse button to pan and zoom around + the world. The camera's visible area to the player is highlighted in red. + In play mode, the camera behaves as it would in a 2D game, depending upon + the selected mode. +*/ + +class TEST_Camera2D : public olc::PixelGameEngine +{ +public: + TEST_Camera2D() + { + sAppName = "Camera2D Utility Test"; + } + + // Transformed view object to make world offsetting simple + olc::TileTransformedView tv; + + // Conveninet constants to define tile map world + olc::vi2d m_vWorldSize = { 80, 75 }; + olc::vi2d m_vTileSize = { 32, 32 }; + + // The camera! + olc::utils::Camera2D camera; + + // The point that represents the player, it is "tracked" + // by the camera + olc::vf2d vTrackedPoint; + + // Flag whether we are in "free roam" or "play" mode + bool bFreeRoam = false; + + // The world map, stored as a 1D array + std::vector vWorldMap; + +public: + bool OnUserCreate() override + { + // Construct transform view + tv = olc::TileTransformedView(GetScreenSize(), m_vTileSize); + + // Construct Camera + vTrackedPoint = { 20.0f, 20.0f }; + camera = olc::utils::Camera2D(GetScreenSize() / m_vTileSize, vTrackedPoint); + + // Configure Camera + camera.SetTarget(vTrackedPoint); + camera.SetMode(olc::utils::Camera2D::Mode::Simple); + camera.SetWorldBoundary({ 0.0f, 0.0f }, m_vWorldSize); + camera.EnableWorldBoundary(true); + + // Create "tile map" world with just two tiles + vWorldMap.resize(m_vWorldSize.x * m_vWorldSize.y); + for (int i = 0; i < vWorldMap.size(); i++) + vWorldMap[i] = ((rand() % 20) == 1) ? 1 : 0; + + // Set background colour + Clear(olc::CYAN); + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // In free roam, middle mouse button pans & zooms + if (bFreeRoam) + tv.HandlePanAndZoom(); + + // Handle player "physics" in response to key presses + olc::vf2d vVel = { 0.0f, 0.0f }; + if (GetKey(olc::Key::W).bHeld) vVel += {0, -1}; + if (GetKey(olc::Key::S).bHeld) vVel += {0, +1}; + if (GetKey(olc::Key::A).bHeld) vVel += {-1, 0}; + if (GetKey(olc::Key::D).bHeld) vVel += {+1, 0}; + vTrackedPoint += vVel * 8.0f * fElapsedTime; + + // Switch between "free roam" and "play" mode with TAB key + if (GetKey(olc::Key::TAB).bPressed) + { + // Always setup camera to play mode when tab key pressed + tv.SetWorldOffset(camera.GetViewPosition()); + tv.SetWorldScale(m_vTileSize); + bFreeRoam = !bFreeRoam; + } + + // Switch camera mode in operation + if (GetKey(olc::Key::K1).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::Simple); + if (GetKey(olc::Key::K2).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::EdgeMove); + if (GetKey(olc::Key::K3).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow); + if (GetKey(olc::Key::K4).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::FixedScreens); + + // Update the camera, if teh tracked object remains visible, + // true is returned + bool bOnScreen = camera.Update(fElapsedTime); + + // In "play" mode, set the transformed view to that required by + // the camera + if (!bFreeRoam) + tv.SetWorldOffset(camera.GetViewPosition()); + + // Render "tile map", by getting visible tiles + olc::vi2d vTileTL = tv.GetTopLeftTile().max({ 0,0 }); + olc::vi2d vTileBR = tv.GetBottomRightTile().min(m_vWorldSize); + olc::vi2d vTile; + // Then looping through them and drawing them + for (vTile.y = vTileTL.y; vTile.y < vTileBR.y; vTile.y++) + for (vTile.x = vTileTL.x; vTile.x < vTileBR.x; vTile.x++) + { + int idx = vTile.y * m_vWorldSize.x + vTile.x; + + if (vWorldMap[idx] == 0) + tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(40, 40, 40)); + + if (vWorldMap[idx] == 1) + tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(60, 60, 60)); + } + + // Draw the "player" as a 1x1 cell + tv.FillRectDecal(vTrackedPoint - olc::vf2d(0.5f, 0.5f), {1.0f, 1.0f}, olc::BLUE); + + // Overlay with information + if (bFreeRoam) + { + tv.FillRectDecal(camera.GetViewPosition(), camera.GetViewSize(), olc::PixelF(1.0f, 0.0f, 0.0f, 0.5f)); + DrawStringPropDecal({ 2, 2 }, "TAB: Free Mode, M-Btn to Pan & Zoom", olc::YELLOW); + } + else + DrawStringPropDecal({ 2,2 }, "TAB: Play Mode", olc::YELLOW); + + DrawStringPropDecal({ 2,12 }, "WASD : Move", olc::YELLOW); + DrawStringPropDecal({ 2,22 }, "CAMERA: 1) Simple 2) EdgeMove 3) LazyFollow 4) Screens", olc::YELLOW); + DrawStringPropDecal({ 2,42 }, vTrackedPoint.str(), olc::YELLOW); + return true; + } +}; + +int main() +{ + TEST_Camera2D demo; + if (demo.Construct(512, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/extensions/olcPGEX_QuickGUI.h b/extensions/olcPGEX_QuickGUI.h index e7c55ab6..6b0c4ef8 100644 --- a/extensions/olcPGEX_QuickGUI.h +++ b/extensions/olcPGEX_QuickGUI.h @@ -1,13 +1,16 @@ /* - OneLoneCoder - QuickGUI v1.01 + OneLoneCoder - QuickGUI v1.02 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A semi-immediate mode GUI for very simple GUI stuff. Includes: - Label - Displays a single-line string - TextBox - Click to enter/edit single-line text - Button - A clickable labelled rectangle - CheckBox - A clickable labelled rectangle that retains state - Slider - An omnidirectional draggable handle between two values + Label - Displays a single-line string + TextBox - Click to enter/edit single-line text + Button - A clickable labelled rectangle + CheckBox - A clickable labelled rectangle that retains state + ImageButton - A Button with an image instead of text + ImageCheckBox- A CheckBox with an image instead of text + Slider - An omnidirectional draggable handle between two values + ListBox - A list of strings, that can be scrolled and an item selected License (OLC-3) ~~~~~~~~~~~~~~~ @@ -60,6 +63,11 @@ v1.01 +Moved Slider::fGrabRad into "theme" +Manager::CopyThemeFrom() - copies theme attributes from a different manager +ListBox - Displays a vector of strings + v1.02 +ImageButton + +ImageCheckBox + +ListBox::bSelectionChanged flag, true when list selected item changes + =Fix - Text box mouse behaviours, mouse release is now meaningless + +CheckBox Fix for decal display */ @@ -252,6 +260,40 @@ namespace olc::QuickGUI void DrawDecal(olc::PixelGameEngine* pge) override; }; + class ImageButton : public Button + { + public: + ImageButton(olc::QuickGUI::Manager& manager, // Associate with a Manager + const olc::Renderable &icon, // Text to display + const olc::vf2d& pos, // Location of button top-left + const olc::vf2d& size); // Size of button + + public: + const olc::Renderable& pIcon; + + public: + void Draw(olc::PixelGameEngine* pge) override; + void DrawDecal(olc::PixelGameEngine* pge) override; + }; + + class ImageCheckBox : public ImageButton + { + public: + ImageCheckBox(olc::QuickGUI::Manager& manager, // Associate with a Manager + const olc::Renderable& icon, // Text to display + const bool check, // Is checked or not? + const olc::vf2d& pos, // Location of button top-left + const olc::vf2d& size); // Size of button + + public: + bool bChecked = false; + + public: + void Update(olc::PixelGameEngine* pge) override; + void Draw(olc::PixelGameEngine* pge) override; + void DrawDecal(olc::PixelGameEngine* pge) override; + }; + // Creates a Slider Control - a grabbable handle that slides between two locations class Slider : public BaseControl @@ -300,7 +342,7 @@ namespace olc::QuickGUI bool bHasBorder = true; // Show a background? bool bHasBackground = true; - + public: Slider *m_pSlider = nullptr; Manager m_group; @@ -308,13 +350,43 @@ namespace olc::QuickGUI std::vector& m_vList; public: + // Item currently selected size_t nSelectedItem = 0; + size_t nPreviouslySelectedItem = 0; + // Has selection changed? + bool bSelectionChanged = false; public: // BaseControl overrides void Update(olc::PixelGameEngine* pge) override; void Draw(olc::PixelGameEngine* pge) override; void DrawDecal(olc::PixelGameEngine* pge) override; }; + + + class ModalDialog : public olc::PGEX + { + public: + ModalDialog(); + + public: + void ShowFileOpen(const std::string& sPath); + + protected: + virtual bool OnBeforeUserUpdate(float& fElapsedTime) override; + + private: + bool m_bShowDialog = false; + + Manager m_manFileSelect; + ListBox* m_listVolumes = nullptr; + ListBox* m_listDirectory = nullptr; + ListBox* m_listFiles = nullptr; + + std::vector m_vVolumes; + std::vector m_vDirectory; + std::vector m_vFiles; + std::filesystem::path m_path; + }; } @@ -487,13 +559,15 @@ namespace olc::QuickGUI { // Released inside box does nothing to me, but i may have // to finish off the neighbours... oo err + bPressed = pge->GetMouse(olc::Mouse::LEFT).bPressed; bReleased = pge->GetMouse(olc::Mouse::LEFT).bReleased; - if (bReleased && pge->IsTextEntryEnabled() && !m_bTextEdit) + + if (bPressed && pge->IsTextEntryEnabled() && !m_bTextEdit) { pge->TextEntryEnable(false); } - bPressed = pge->GetMouse(olc::Mouse::LEFT).bPressed; + if (bPressed && !pge->IsTextEntryEnabled() && !m_bTextEdit) { pge->TextEntryEnable(true, sText); @@ -507,8 +581,11 @@ namespace olc::QuickGUI else { // Released outside box + bPressed = pge->GetMouse(olc::Mouse::LEFT).bPressed; bReleased = pge->GetMouse(olc::Mouse::LEFT).bReleased; - if (bReleased && m_bTextEdit) + bHeld = pge->GetMouse(olc::Mouse::LEFT).bHeld; + + if (bPressed && m_bTextEdit) { sText = pge->TextEntryGetString(); pge->TextEntryEnable(false); @@ -679,6 +756,68 @@ namespace olc::QuickGUI #pragma endregion +#pragma region ImageButton + ImageButton::ImageButton(olc::QuickGUI::Manager& manager, const olc::Renderable& icon, const olc::vf2d& pos, const olc::vf2d& size) + : Button(manager, "", pos, size), pIcon(icon) + { + + } + + void ImageButton::Draw(olc::PixelGameEngine* pge) + { + Button::Draw(pge); + pge->DrawSprite(vPos + olc::vi2d(4, 4), pIcon.Sprite()); + } + + void ImageButton::DrawDecal(olc::PixelGameEngine* pge) + { + Button::DrawDecal(pge); + pge->DrawDecal(vPos + olc::vi2d(4, 4), pIcon.Decal()); + } +#pragma endregion + + +#pragma region ImageCheckBox + ImageCheckBox::ImageCheckBox(olc::QuickGUI::Manager& manager, const olc::Renderable& gfx, const bool check, const olc::vf2d& pos, const olc::vf2d& size) + : ImageButton(manager, gfx, pos, size) + { + bChecked = check; + } + + void ImageCheckBox::Update(olc::PixelGameEngine* pge) + { + if (m_state == State::Disabled || !bVisible) + return; + + ImageButton::Update(pge); + if (bPressed) bChecked = !bChecked; + } + + void ImageCheckBox::Draw(olc::PixelGameEngine* pge) + { + ImageButton::Draw(pge); + + if (bChecked) + pge->DrawRect(vPos + olc::vf2d(2, 2), vSize - olc::vi2d(5, 5), m_manager.colBorder); + } + + void ImageCheckBox::DrawDecal(olc::PixelGameEngine* pge) + { + if (!bVisible) + return; + + ImageButton::DrawDecal(pge); + + pge->SetDecalMode(olc::DecalMode::WIREFRAME); + pge->FillRectDecal(vPos + olc::vf2d(2, 2), vSize - olc::vf2d(4, 4), m_manager.colBorder); + pge->SetDecalMode(olc::DecalMode::NORMAL); + + olc::vf2d vText = pge->GetTextSizeProp(sText); + pge->DrawStringPropDecal(vPos + (vSize - vText) * 0.5f, sText, m_manager.colText); + } +#pragma endregion + + #pragma region CheckBox CheckBox::CheckBox(olc::QuickGUI::Manager& manager, const std::string& text, const bool check, const olc::vf2d& pos, const olc::vf2d& size) : Button(manager, text, pos, size) @@ -692,7 +831,8 @@ namespace olc::QuickGUI return; Button::Update(pge); - if (bPressed) bChecked = !bChecked; + if (bPressed) + bChecked = !bChecked; } void CheckBox::Draw(olc::PixelGameEngine* pge) @@ -713,9 +853,12 @@ namespace olc::QuickGUI Button::DrawDecal(pge); - pge->SetDecalMode(olc::DecalMode::WIREFRAME); - pge->FillRectDecal(vPos + olc::vf2d(2,2), vSize - olc::vf2d(4, 4), m_manager.colBorder); - pge->SetDecalMode(olc::DecalMode::NORMAL); + if (bChecked) + { + pge->SetDecalMode(olc::DecalMode::WIREFRAME); + pge->FillRectDecal(vPos + olc::vf2d(2, 2), vSize - olc::vf2d(4, 4), m_manager.colBorder); + pge->SetDecalMode(olc::DecalMode::NORMAL); + } olc::vf2d vText = pge->GetTextSizeProp(sText); pge->DrawStringPropDecal(vPos + (vSize - vText) * 0.5f, sText, m_manager.colText); @@ -851,17 +994,23 @@ namespace olc::QuickGUI if (m_state == State::Disabled || !bVisible) return; + + nPreviouslySelectedItem = nSelectedItem; 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); + bSelectionChanged = nSelectedItem != nPreviouslySelectedItem; + + m_pSlider->fMax = float(m_vList.size()); m_group.Update(pge); } @@ -927,6 +1076,95 @@ namespace olc::QuickGUI #pragma endregion + +#pragma region Modal + ModalDialog::ModalDialog() : olc::PGEX(true) + { + + // Create File Open Dialog + olc::vi2d vScreenSize = pge->GetScreenSize(); + + m_listDirectory = new ListBox(m_manFileSelect, m_vDirectory, olc::vf2d(20, 20), olc::vf2d(300, 500)); + m_listFiles = new ListBox(m_manFileSelect, m_vFiles, olc::vf2d(330, 20), olc::vf2d(300, 500)); + + m_path = "/"; + for (auto const& dir_entry : std::filesystem::directory_iterator{ m_path }) + { + if(dir_entry.is_directory()) + m_vDirectory.push_back(dir_entry.path().filename().string()); + else + m_vFiles.push_back(dir_entry.path().filename().string()); + } + } + + void ModalDialog::ShowFileOpen(const std::string& sPath) + { + m_bShowDialog = true; + } + + bool ModalDialog::OnBeforeUserUpdate(float& fElapsedTime) + { + if(!m_bShowDialog) return false; + + m_manFileSelect.Update(this->pge); + + if (pge->GetKey(olc::Key::BACK).bPressed) + { + m_path = m_path.parent_path().string() + "/"; + //m_listDirectory->bSelectionChanged = true; + //m_listDirectory->nSelectedItem = 0; + } + + if (m_listDirectory->bSelectionChanged) + { + std::string sDirectory = m_vDirectory[m_listDirectory->nSelectedItem]; + /*if (sDirectory == "..") + m_path = m_path.parent_path().string() + "/"; + else + m_path += sDirectory+ "/";*/ + + + m_path += sDirectory + "/"; + // Reconstruct Lists + m_vDirectory.clear(); + + m_vFiles.clear(); + + + for (auto const& dir_entry : std::filesystem::directory_iterator{ m_path }) + { + if (dir_entry.is_directory() || dir_entry.is_other()) + m_vDirectory.push_back(dir_entry.path().filename().string()); + else + m_vFiles.push_back(dir_entry.path().filename().string()); + } + + //m_vDirectory.push_back(".."); + + //m_listFiles->nSelectedItem = 0; + //m_listDirectory->nSelectedItem = 0; + + } + + pge->DrawStringDecal({ 0,0 }, m_path.string()); + + + + + m_manFileSelect.DrawDecal(this->pge); + + + + if (pge->GetKey(olc::Key::ESCAPE).bPressed) + { + m_bShowDialog = false; + return false; + } + + return true; + } +#pragma endregion + } #endif // OLC_PGEX_QUICKGUI #endif // OLC_PGEX_QUICKGUI_H \ No newline at end of file diff --git a/extensions/olcPGEX_TransformedView.h b/extensions/olcPGEX_TransformedView.h index 51bdc7ba..3745d1ab 100644 --- a/extensions/olcPGEX_TransformedView.h +++ b/extensions/olcPGEX_TransformedView.h @@ -3,7 +3,7 @@ +-------------------------------------------------------------+ | OneLoneCoder Pixel Game Engine Extension | - | Transformed View v1.07 | + | Transformed View v1.08 | +-------------------------------------------------------------+ NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES @@ -73,6 +73,7 @@ 1.06: Fixed error in DrawLine() - Thanks CraisyDaisyRecords (& Fern)! 1.07: +DrawRectDecal() +GetPGE() + 1.08: +DrawPolygonDecal() with tint overload, akin to PGE */ #pragma once @@ -190,7 +191,8 @@ namespace olc // Draws an arbitrary convex textured polygon using GPU void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); - void DrawPolygonDecal(olc::Decal * decal, const std::vector&pos, const std::vector&uv, const std::vector &tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector&pos, const std::vector&uv, const std::vector &tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); #if defined(OLC_PGEX_SHADER) @@ -668,6 +670,15 @@ namespace olc pge->DrawPolygonDecal(decal, vTransformed, uv, tint); } + void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) + { + std::vector vTransformed(pos.size()); + for (uint32_t n = 0; n < pos.size(); n++) + vTransformed[n] = WorldToScreen(pos[n]); + pge->DrawPolygonDecal(decal, vTransformed, uv, colours, tint); + } + + #if defined (OLC_PGEX_SHADER) diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index 17b2d4ae..5480a153 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -3,7 +3,7 @@ olcPixelGameEngine.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v2.20 | + | OneLoneCoder Pixel Game Engine v2.21 | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ @@ -197,7 +197,7 @@ Author ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018, 2019, 2020, 2021, 2022 + David Barr, aka javidx9, ďż˝OneLoneCoder 2018, 2019, 2020, 2021, 2022 */ #pragma endregion @@ -303,6 +303,18 @@ 2.20: +DrawRectDecal() - Keeps OneSketchyGuy quiet +GetScreenSize() +olc::Sprite::Size() - returns size of sprite in vector format + 2.21: Emscripten Overhaul - Thanks Moros! + +DrawPolygonDecal() tint overload, can now tint a polygon accounting for vertex colours + +Multiplicative Pixel overload + +v2d_generic clamp() + +v2d_generic lerp() + +GetDroppedFiles() - returns files dropped onto engine window for that frame (MSW only) + +GetDroppedFilesPoint() - returns location of dropped files (MSW only) + +Exposed OpenGL33 Loader interface so the typedefs can be shared with PGEX & user + +Fix OGL33 DecalStructure types - wow, how did that one get missed?? lol + +FillTexturedTriangle() - Software rasterizes a textured, coloured, triangle + +FillTexturedPolygon() - Hijacks DecalStructure for configuration + +olc::vf2d arguments for Sprite::Sample() functions !! 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 !! @@ -382,7 +394,7 @@ int main() #include #pragma endregion -#define PGE_VER 220 +#define PGE_VER 221 // O------------------------------------------------------------------------------O // | COMPILER CONFIGURATION ODDITIES | @@ -428,8 +440,16 @@ int main() // | PLATFORM SELECTION CODE, Thanks slavka! | // O------------------------------------------------------------------------------O +#if defined(OLC_PGE_HEADLESS) + #define OLC_PLATFORM_HEADLESS + #define OLC_GFX_HEADLESS + #if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) + #define OLC_IMAGE_HEADLESS + #endif +#endif + // Platform -#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) && !defined(OLC_PLATFORM_HEADLESS) #if !defined(OLC_PLATFORM_CUSTOM_EX) #if defined(_WIN32) #define OLC_PLATFORM_WINAPI @@ -452,8 +472,10 @@ int main() #define PGE_USE_CUSTOM_START #endif + + // Renderer -#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) && !defined(OLC_GFX_HEADLESS) #if !defined(OLC_GFX_CUSTOM_EX) #if defined(OLC_PLATFORM_EMSCRIPTEN) #define OLC_GFX_OPENGL33 @@ -464,7 +486,7 @@ int main() #endif // Image loader -#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) && !defined(OLC_IMAGE_HEADLESS) #if !defined(OLC_IMAGE_CUSTOM_EX) #if defined(_WIN32) #define OLC_IMAGE_GDI @@ -522,6 +544,15 @@ int main() #endif #endif #endif + +#if defined(OLC_PGE_HEADLESS) +#if defined max +#undef max +#endif +#if defined min +#undef min +#endif +#endif #pragma endregion // O------------------------------------------------------------------------------O @@ -538,6 +569,7 @@ namespace olc constexpr uint8_t nDefaultAlpha = 0xFF; constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); constexpr uint8_t nTabSizeInSpaces = 4; + constexpr size_t OLC_MAX_VERTS = 128; enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; // O------------------------------------------------------------------------------O @@ -567,6 +599,8 @@ namespace olc Pixel operator - (const Pixel& p) const; Pixel& operator +=(const Pixel& p); Pixel& operator -=(const Pixel& p); + Pixel operator * (const Pixel& p) const; + Pixel& operator *=(const Pixel& p); Pixel inv() const; }; @@ -648,6 +682,8 @@ namespace olc v2d_generic min(const v2d_generic& v) const { return v2d_generic(std::min(x, v.x), std::min(y, v.y)); } v2d_generic cart() { return { std::cos(y) * x, std::sin(y) * x }; } v2d_generic polar() { return { mag(), std::atan2(y, x) }; } + v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1)->min(v2); } + v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1.0 - t)) + (v1 * T(t)); } T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } @@ -771,7 +807,9 @@ namespace olc Pixel GetPixel(const olc::vi2d& a) const; bool SetPixel(const olc::vi2d& a, Pixel p); Pixel Sample(float x, float y) const; + Pixel Sample(const olc::vf2d& uv) const; Pixel SampleBL(float u, float v) const; + Pixel SampleBL(const olc::vf2d& uv) const; Pixel* GetData(); olc::Sprite* Duplicate(); olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); @@ -939,6 +977,7 @@ namespace olc // Called when a console command is executed virtual bool OnConsoleCommand(const std::string& sCommand); + public: // Hardware Interfaces // Returns true if window is currently in focus bool IsFocused() const; @@ -987,6 +1026,9 @@ namespace olc const olc::vi2d& GetScreenPixelSize() const; // Gets "screen" size const olc::vi2d& GetScreenSize() const; + // Gets any files dropped this frame + const std::vector& GetDroppedFiles() const; + const olc::vi2d& GetDroppedFilesPoint() const; public: // CONFIGURATION ROUTINES // Layer targeting functions @@ -1040,6 +1082,9 @@ namespace olc // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); void FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Fill a textured and coloured triangle + void FillTexturedTriangle(const std::vector& vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex); + void FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure = olc::DecalStructure::LIST); // Draws an entire sprite at location (x,y) void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); @@ -1089,7 +1134,7 @@ namespace olc void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint = olc::WHITE); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& tint); - + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); // Draws a line in Decal Space void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); void DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); @@ -1192,6 +1237,10 @@ namespace olc std::function funcPixelMode; std::chrono::time_point m_tp1, m_tp2; std::vector vFontSpacing; + std::vector vDroppedFiles; + std::vector vDroppedFilesCache; + olc::vi2d vDroppedFilesPoint; + olc::vi2d vDroppedFilesPointCache; // Command Console Specific bool bConsoleShow = false; @@ -1246,6 +1295,7 @@ namespace olc void olc_UpdateMouseFocus(bool state); void olc_UpdateKeyFocus(bool state); void olc_Terminate(); + void olc_DropFiles(int32_t x, int32_t y, const std::vector& vFiles); void olc_Reanimate(); bool olc_IsRunning(); @@ -1289,6 +1339,100 @@ namespace olc #pragma endregion + +#pragma region opengl33_iface +// In order to facilitate more advanced graphics features, some PGEX +// will rely on shaders. Instead of having each PGEX responsible for +// managing this, for convenience, this interface exists. + +#if defined(OLC_GFX_OPENGL33) + + #if defined(OLC_PLATFORM_WINAPI) + #include + #define CALLSTYLE __stdcall + #endif + + #if defined(__linux__) || defined(__FreeBSD__) + #include + #endif + + #if defined(OLC_PLATFORM_X11) + namespace X11 + {#include } + #define CALLSTYLE + #endif + + #if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include + #endif + + #if defined(OLC_PLATFORM_EMSCRIPTEN) + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #include + #define CALLSTYLE + #define GL_CLAMP GL_CLAMP_TO_EDGE + #endif + +namespace olc +{ + typedef char GLchar; + typedef ptrdiff_t GLsizeiptr; + + typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); + typedef GLuint CALLSTYLE locCreateProgram_t(void); + typedef void CALLSTYLE locDeleteShader_t(GLuint shader); + typedef void CALLSTYLE locCompileShader_t(GLuint shader); + typedef void CALLSTYLE locLinkProgram_t(GLuint program); + typedef void CALLSTYLE locDeleteProgram_t(GLuint program); + typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); + typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); + typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); + typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); + typedef void CALLSTYLE locUseProgram_t(GLuint program); + typedef void CALLSTYLE locBindVertexArray_t(GLuint array); + typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); + typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); + typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); + typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); + typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); + typedef void CALLSTYLE locActiveTexture_t(GLenum texture); + typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); + typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); + typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); + typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); + typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); + typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + +#if defined(OLC_PLATFORM_WINAPI) + typedef void __stdcall locSwapInterval_t(GLsizei n); +#endif + +#if defined(OLC_PLATFORM_X11) + typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); + typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); +#else + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +#endif + +} // olc namespace +#endif // OpenGL33 Definitions +#pragma endregion + + #endif // OLC_PGE_DEF @@ -1387,6 +1531,24 @@ namespace olc return *this; } + Pixel Pixel::operator * (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * float(p.r) / 255.0f))); + uint8_t nG = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * float(p.g) / 255.0f))); + uint8_t nB = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * float(p.b) / 255.0f))); + uint8_t nA = uint8_t(std::min(255.0f, std::max(0.0f, float(a) * float(p.a) / 255.0f))); + return Pixel(nR, nG, nB, nA); + } + + Pixel& Pixel::operator *=(const Pixel& p) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * float(p.r) / 255.0f))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * float(p.g) / 255.0f))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * float(p.b) / 255.0f))); + this->a = uint8_t(std::min(255.0f, std::max(0.0f, float(a) * float(p.a) / 255.0f))); + return *this; + } + Pixel Pixel::inv() const { uint8_t nR = uint8_t(std::min(255, std::max(0, 255 - int(r)))); @@ -1465,6 +1627,11 @@ namespace olc return GetPixel(sx, sy); } + Pixel Sprite::Sample(const olc::vf2d& uv) const + { + return Sample(uv.x, uv.y); + } + Pixel Sprite::SampleBL(float u, float v) const { u = u * width - 0.5f; @@ -1487,6 +1654,11 @@ namespace olc (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); } + Pixel Sprite::SampleBL(const olc::vf2d& uv) const + { + return SampleBL(uv.x, uv.y); + } + Pixel* Sprite::GetData() { return pColData.data(); } @@ -2404,6 +2576,168 @@ namespace olc } } + void PixelGameEngine::FillTexturedTriangle(const std::vector& vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex) + { + olc::vi2d p1 = vPoints[0]; + olc::vi2d p2 = vPoints[1]; + olc::vi2d p3 = vPoints[2]; + + if (p2.y < p1.y){std::swap(p1.y, p2.y); std::swap(p1.x, p2.x); std::swap(vTex[0].x, vTex[1].x); std::swap(vTex[0].y, vTex[1].y); std::swap(vColour[0], vColour[1]);} + if (p3.y < p1.y){std::swap(p1.y, p3.y); std::swap(p1.x, p3.x); std::swap(vTex[0].x, vTex[2].x); std::swap(vTex[0].y, vTex[2].y); std::swap(vColour[0], vColour[2]);} + if (p3.y < p2.y){std::swap(p2.y, p3.y); std::swap(p2.x, p3.x); std::swap(vTex[1].x, vTex[2].x); std::swap(vTex[1].y, vTex[2].y); std::swap(vColour[1], vColour[2]);} + + olc::vi2d dPos1 = p2 - p1; + olc::vf2d dTex1 = vTex[1] - vTex[0]; + int dcr1 = vColour[1].r - vColour[0].r; + int dcg1 = vColour[1].g - vColour[0].g; + int dcb1 = vColour[1].b - vColour[0].b; + int dca1 = vColour[1].a - vColour[0].a; + + olc::vi2d dPos2 = p3 - p1; + olc::vf2d dTex2 = vTex[2] - vTex[0]; + int dcr2 = vColour[2].r - vColour[0].r; + int dcg2 = vColour[2].g - vColour[0].g; + int dcb2 = vColour[2].b - vColour[0].b; + int dca2 = vColour[2].a - vColour[0].a; + + float dax_step = 0, dbx_step = 0, dcr1_step = 0, dcr2_step = 0, dcg1_step = 0, dcg2_step = 0, dcb1_step = 0, dcb2_step = 0, dca1_step = 0, dca2_step = 0; + olc::vf2d vTex1Step, vTex2Step; + + if (dPos1.y) + { + dax_step = dPos1.x / (float)abs(dPos1.y); + vTex1Step = dTex1 / (float)abs(dPos1.y); + dcr1_step = dcr1 / (float)abs(dPos1.y); + dcg1_step = dcg1 / (float)abs(dPos1.y); + dcb1_step = dcb1 / (float)abs(dPos1.y); + dca1_step = dca1 / (float)abs(dPos1.y); + } + + if (dPos2.y) + { + dbx_step = dPos2.x / (float)abs(dPos2.y); + vTex2Step = dTex2 / (float)abs(dPos2.y); + dcr2_step = dcr2 / (float)abs(dPos2.y); + dcg2_step = dcg2 / (float)abs(dPos2.y); + dcb2_step = dcb2 / (float)abs(dPos2.y); + dca2_step = dca2 / (float)abs(dPos2.y); + } + + olc::vi2d vStart; + olc::vi2d vEnd; + int vStartIdx; + + for (int pass = 0; pass < 2; pass++) + { + if (pass == 0) + { + vStart = p1; vEnd = p2; vStartIdx = 0; + } + else + { + dPos1 = p3 - p2; + dTex1 = vTex[2] - vTex[1]; + dcr1 = vColour[2].r - vColour[1].r; + dcg1 = vColour[2].g - vColour[1].g; + dcb1 = vColour[2].b - vColour[1].b; + dca1 = vColour[2].a - vColour[1].a; + dcr1_step = 0; dcg1_step = 0; dcb1_step = 0; dca1_step = 0; + + if (dPos2.y) dbx_step = dPos2.x / (float)abs(dPos2.y); + if (dPos1.y) + { + dax_step = dPos1.x / (float)abs(dPos1.y); + vTex1Step = dTex1 / (float)abs(dPos1.y); + dcr1_step = dcr1 / (float)abs(dPos1.y); + dcg1_step = dcg1 / (float)abs(dPos1.y); + dcb1_step = dcb1 / (float)abs(dPos1.y); + dca1_step = dca1 / (float)abs(dPos1.y); + } + + vStart = p2; vEnd = p3; vStartIdx = 1; + } + + if (dPos1.y) + { + for (int i = vStart.y; i <= vEnd.y; i++) + { + int ax = int(vStart.x + (float)(i - vStart.y) * dax_step); + int bx = int(p1.x + (float)(i - p1.y) * dbx_step); + + olc::vf2d tex_s(vTex[vStartIdx].x + (float)(i - vStart.y) * vTex1Step.x, vTex[vStartIdx].y + (float)(i - vStart.y) * vTex1Step.y); + olc::vf2d tex_e(vTex[0].x + (float)(i - p1.y) * vTex2Step.x, vTex[0].y + (float)(i - p1.y) * vTex2Step.y); + + olc::Pixel col_s(vColour[vStartIdx].r + uint8_t((float)(i - vStart.y) * dcr1_step), vColour[vStartIdx].g + uint8_t((float)(i - vStart.y) * dcg1_step), + vColour[vStartIdx].b + uint8_t((float)(i - vStart.y) * dcb1_step), vColour[vStartIdx].a + uint8_t((float)(i - vStart.y) * dca1_step)); + + olc::Pixel col_e(vColour[0].r + uint8_t((float)(i - p1.y) * dcr2_step), vColour[0].g + uint8_t((float)(i - p1.y) * dcg2_step), + vColour[0].b + uint8_t((float)(i - p1.y) * dcb2_step), vColour[0].a + uint8_t((float)(i - p1.y) * dca2_step)); + + if (ax > bx) { std::swap(ax, bx); std::swap(tex_s, tex_e); std::swap(col_s, col_e); } + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + olc::Pixel pixel = PixelLerp(col_s, col_e, t); + if (sprTex != nullptr) pixel *= sprTex->Sample(tex_s.lerp(tex_e, t)); + Draw(j, i, pixel); + t += tstep; + } + } + } + } + } + + void PixelGameEngine::FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure) + { + if (structure == olc::DecalStructure::LINE) + { + return; // Meaningless, so do nothing + } + + if (vPoints.size() < 3 || vTex.size() < 3 || vColour.size() < 3) + return; + + if (structure == olc::DecalStructure::LIST) + { + for (int tri = 0; tri < vPoints.size() / 3; tri++) + { + std::vector vP = { vPoints[tri * 3 + 0], vPoints[tri * 3 + 1], vPoints[tri * 3 + 2] }; + std::vector vT = { vTex[tri * 3 + 0], vTex[tri * 3 + 1], vTex[tri * 3 + 2] }; + std::vector vC = { vColour[tri * 3 + 0], vColour[tri * 3 + 1], vColour[tri * 3 + 2] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + + if (structure == olc::DecalStructure::STRIP) + { + for (int tri = 2; tri < vPoints.size(); tri++) + { + std::vector vP = { vPoints[tri - 2], vPoints[tri-1], vPoints[tri] }; + std::vector vT = { vTex[tri - 2], vTex[tri - 1], vTex[tri] }; + std::vector vC = { vColour[tri - 2], vColour[tri - 1], vColour[tri] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + + if (structure == olc::DecalStructure::FAN) + { + for (int tri = 2; tri < vPoints.size(); tri++) + { + std::vector vP = { vPoints[0], vPoints[tri - 1], vPoints[tri] }; + std::vector vT = { vTex[0], vTex[tri - 1], vTex[tri] }; + std::vector vC = { vColour[0], vColour[tri - 1], vColour[tri] }; + FillTexturedTriangle(vP, vT, vC, sprTex); + } + return; + } + } + + void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip) { DrawSprite(pos.x, pos.y, sprite, scale, flip); } @@ -2635,6 +2969,15 @@ namespace olc vLayers[nTargetLayer].vecDecalInstance.push_back(di); } + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) + { + std::vector newColours(colours.size(), olc::WHITE); + std::transform(colours.begin(), colours.end(), newColours.begin(), + [&tint](const olc::Pixel pin) { return pin * tint; }); + DrawPolygonDecal(decal, pos, uv, newColours); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint) { DecalInstance di; @@ -3210,6 +3553,12 @@ namespace olc } + const std::vector& PixelGameEngine::GetDroppedFiles() const + { return vDroppedFiles; } + + const olc::vi2d& PixelGameEngine::GetDroppedFilesPoint() const + { return vDroppedFilesPoint; } + void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) { @@ -3328,7 +3677,6 @@ namespace olc 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() { @@ -3394,6 +3742,19 @@ namespace olc void PixelGameEngine::olc_UpdateKeyFocus(bool state) { bHasInputFocus = state; } + void PixelGameEngine::olc_DropFiles(int32_t x, int32_t y, const std::vector& vFiles) + { + x -= vViewPos.x; + y -= vViewPos.y; + vDroppedFilesPointCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vDroppedFilesPointCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vDroppedFilesPointCache.x >= (int32_t)vScreenSize.x) vDroppedFilesPointCache.x = vScreenSize.x - 1; + if (vDroppedFilesPointCache.y >= (int32_t)vScreenSize.y) vDroppedFilesPointCache.y = vScreenSize.y - 1; + if (vDroppedFilesPointCache.x < 0) vDroppedFilesPointCache.x = 0; + if (vDroppedFilesPointCache.y < 0) vDroppedFilesPointCache.y = 0; + vDroppedFilesCache = vFiles; + } + void PixelGameEngine::olc_Reanimate() { bAtomActive = true; } @@ -3501,17 +3862,22 @@ namespace olc nMouseWheelDelta = nMouseWheelDeltaCache; nMouseWheelDeltaCache = 0; + vDroppedFiles = vDroppedFilesCache; + vDroppedFilesPoint = vDroppedFilesPointCache; + vDroppedFilesCache.clear(); + if (bTextEntryEnable) { UpdateTextEntry(); } // Handle Frame Update - bool bExtensionBlockFrame = false; + bool bExtensionBlockFrame = false; for (auto& ext : vExtensions) bExtensionBlockFrame |= ext->OnBeforeUserUpdate(fElapsedTime); if (!bExtensionBlockFrame) { if (!OnUserUpdate(fElapsedTime)) bAtomActive = false; + } for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); @@ -3521,6 +3887,8 @@ namespace olc UpdateConsole(); } + + // Display Frame renderer->UpdateViewport(vViewPos, vViewSize); renderer->ClearBuffer(olc::BLACK, true); @@ -3679,10 +4047,118 @@ namespace olc }; #pragma endregion + +#pragma region platform_headless +namespace olc +{ +#if defined(OLC_GFX_HEADLESS) + class Renderer_Headless : public olc::Renderer + { + public: + virtual void PrepareDevice() {}; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) { return olc::rcode::OK; } + virtual olc::rcode DestroyDevice() { return olc::rcode::OK; } + virtual void DisplayFrame() {} + virtual void PrepareDrawing() {} + virtual void SetDecalMode(const olc::DecalMode& mode) {} + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) {} + virtual void DrawDecal(const olc::DecalInstance& decal) {} + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) {return 1;}; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) {} + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) {} + virtual uint32_t DeleteTexture(const uint32_t id) {return 1;} + virtual void ApplyTexture(uint32_t id) {} + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) {} + virtual void ClearBuffer(olc::Pixel p, bool bDepth) {} + }; +#endif +#if defined(OLC_PLATFORM_HEADLESS) + class Platform_Headless : public olc::Platform + { + public: + virtual olc::rcode ApplicationStartUp() { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadCleanUp() { return olc::rcode::OK; } + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) { return olc::rcode::OK; } + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) { return olc::rcode::OK; } + virtual olc::rcode SetWindowTitle(const std::string& s) { return olc::rcode::OK; } + virtual olc::rcode StartSystemEventLoop() { return olc::rcode::OK; } + virtual olc::rcode HandleSystemEvent() { return olc::rcode::OK; } + }; +#endif +} +#pragma endregion + // O------------------------------------------------------------------------------O // | olcPixelGameEngine Renderers - the draw-y bits | // O------------------------------------------------------------------------------O +#pragma region image_stb +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h, all systems, very fast | +// O------------------------------------------------------------------------------O +// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h +// MIT License - Copyright(c) 2017 Sean Barrett + +// Note you need to download the above file into your project folder, and +// #define OLC_IMAGE_STB +// #define OLC_PGE_APPLICATION +// #include "olcPixelGameEngine.h" + +#if defined(OLC_IMAGE_STB) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +namespace olc +{ + class ImageLoader_STB : public olc::ImageLoader + { + public: + ImageLoader_STB() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + // clear out existing sprite + spr->pColData.clear(); + // Open file + stbi_uc* bytes = nullptr; + int w = 0, h = 0, cmp = 0; + if (pack != nullptr) + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); + } + + if (!bytes) return olc::rcode::FAIL; + spr->width = w; spr->height = h; + spr->pColData.resize(spr->width * spr->height); + std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); + delete[] bytes; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h | +// O------------------------------------------------------------------------------O +#pragma endregion + + + #if !defined(OLC_PGE_HEADLESS) #pragma region renderer_ogl10 @@ -4084,79 +4560,91 @@ namespace olc #if defined(OLC_PLATFORM_WINAPI) #include - #include + //#include #if !defined(__MINGW32__) #pragma comment(lib, "Dwmapi.lib") #endif - typedef void __stdcall locSwapInterval_t(GLsizei n); + //typedef void __stdcall locSwapInterval_t(GLsizei n); typedef HDC glDeviceContext_t; typedef HGLRC glRenderContext_t; - #define CALLSTYLE __stdcall + //#define CALLSTYLE __stdcall #define OGL_LOAD(t, n) (t*)wglGetProcAddress(#n) #endif - -#if defined(__linux__) || defined(__FreeBSD__) - #include -#endif +// +//#if defined(__linux__) || defined(__FreeBSD__) +// #include +//#endif #if defined(OLC_PLATFORM_X11) - namespace X11 + /*namespace X11 { #include } - typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval);*/ typedef X11::GLXContext glDeviceContext_t; typedef X11::GLXContext glRenderContext_t; - #define CALLSTYLE + //#define CALLSTYLE #define OGL_LOAD(t, n) (t*)glXGetProcAddress((unsigned char*)#n); #endif -#if defined(__APPLE__) - #define GL_SILENCE_DEPRECATION - #include - #include - #include -#endif - -#if defined(OLC_PLATFORM_EMSCRIPTEN) - #include - #include - #define GL_GLEXT_PROTOTYPES - #include - #include - #define CALLSTYLE - typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); - #define GL_CLAMP GL_CLAMP_TO_EDGE - #define OGL_LOAD(t, n) n; -#endif +//#if defined(__APPLE__) +// #define GL_SILENCE_DEPRECATION +// #include +// #include +// #include +//#endif + +//#if defined(OLC_PLATFORM_EMSCRIPTEN) +// #include +// #include +// #define GL_GLEXT_PROTOTYPES +// #include +// #include +// #define CALLSTYLE +// typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); +// #define GL_CLAMP GL_CLAMP_TO_EDGE +// #define OGL_LOAD(t, n) n; +//#endif namespace olc { - typedef char GLchar; - typedef ptrdiff_t GLsizeiptr; - typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); - typedef GLuint CALLSTYLE locCreateProgram_t(void); - typedef void CALLSTYLE locDeleteShader_t(GLuint shader); -#if defined(OLC_PLATFORM_EMSCRIPTEN) - typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); -#else - typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); -#endif - typedef void CALLSTYLE locCompileShader_t(GLuint shader); - typedef void CALLSTYLE locLinkProgram_t(GLuint program); - typedef void CALLSTYLE locDeleteProgram_t(GLuint program); - typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); - typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); - typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); - typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); - typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); - typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); - typedef void CALLSTYLE locUseProgram_t(GLuint program); - typedef void CALLSTYLE locBindVertexArray_t(GLuint array); - typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); - typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); +// typedef char GLchar; +// typedef ptrdiff_t GLsizeiptr; +// typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); +// typedef GLuint CALLSTYLE locCreateProgram_t(void); +// typedef void CALLSTYLE locDeleteShader_t(GLuint shader); +//#if defined(OLC_PLATFORM_EMSCRIPTEN) +// typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); +//#else +// typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +//#endif +// typedef void CALLSTYLE locCompileShader_t(GLuint shader); +// typedef void CALLSTYLE locLinkProgram_t(GLuint program); +// typedef void CALLSTYLE locDeleteProgram_t(GLuint program); +// typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); +// typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); +// typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); +// typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); +// typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); +// typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); +// typedef void CALLSTYLE locUseProgram_t(GLuint program); +// typedef void CALLSTYLE locBindVertexArray_t(GLuint array); +// typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); +// typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); +// typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); +// typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); +// typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); +// typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); +// typedef void CALLSTYLE locActiveTexture_t(GLenum texture); +// typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); +// typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); +// typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); +// typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); +// typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +// typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); +// typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); - constexpr size_t OLC_MAX_VERTS = 128; + class Renderer_OGL33 : public olc::Renderer { @@ -4261,7 +4749,7 @@ namespace olc wglMakeCurrent(glDeviceContext, glRenderContext); // Set Vertical Sync - locSwapInterval = OGL_LOAD(locSwapInterval_t, "wglSwapIntervalEXT"); + locSwapInterval = OGL_LOAD(locSwapInterval_t, wglSwapIntervalEXT); if (locSwapInterval && !bVSYNC) locSwapInterval(0); bSync = bVSYNC; #endif @@ -4281,7 +4769,7 @@ namespace olc XGetWindowAttributes(olc_Display, *olc_Window, &gwa); glViewport(0, 0, gwa.width, gwa.height); - locSwapInterval = OGL_LOAD(locSwapInterval_t, "glXSwapIntervalEXT"); + locSwapInterval = OGL_LOAD(locSwapInterval_t, glXSwapIntervalEXT); if (locSwapInterval == nullptr && !bVSYNC) { @@ -4518,7 +5006,14 @@ namespace olc if (nDecalMode == DecalMode::WIREFRAME) glDrawArrays(GL_LINE_LOOP, 0, decal.points); else - glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + { + if (decal.structure == olc::DecalStructure::FAN) + glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + else if (decal.structure == olc::DecalStructure::STRIP) + glDrawArrays(GL_TRIANGLE_STRIP, 0, decal.points); + else if (decal.structure == olc::DecalStructure::LIST) + glDrawArrays(GL_TRIANGLES, 0, decal.points); + } } uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override @@ -4846,68 +5341,6 @@ namespace olc // O------------------------------------------------------------------------------O #pragma endregion -#pragma region image_stb -// O------------------------------------------------------------------------------O -// | START IMAGE LOADER: stb_image.h, all systems, very fast | -// O------------------------------------------------------------------------------O -// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h -// MIT License - Copyright(c) 2017 Sean Barrett - -// Note you need to download the above file into your project folder, and -// #define OLC_IMAGE_STB -// #define OLC_PGE_APPLICATION -// #include "olcPixelGameEngine.h" - -#if defined(OLC_IMAGE_STB) -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" -namespace olc -{ - class ImageLoader_STB : public olc::ImageLoader - { - public: - ImageLoader_STB() : ImageLoader() - {} - - olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override - { - UNUSED(pack); - // clear out existing sprite - spr->pColData.clear(); - // Open file - stbi_uc* bytes = nullptr; - int w = 0, h = 0, cmp = 0; - if (pack != nullptr) - { - ResourceBuffer rb = pack->GetFileBuffer(sImageFile); - bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); - } - else - { - // Check file exists - if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; - bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); - } - - if (!bytes) return olc::rcode::FAIL; - spr->width = w; spr->height = h; - spr->pColData.resize(spr->width * spr->height); - std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); - delete[] bytes; - return olc::rcode::OK; - } - - olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override - { - return olc::rcode::OK; - } - }; -} -#endif -// O------------------------------------------------------------------------------O -// | START IMAGE LOADER: stb_image.h | -// O------------------------------------------------------------------------------O -#pragma endregion // O------------------------------------------------------------------------------O // | olcPixelGameEngine Platforms | @@ -4949,6 +5382,8 @@ namespace olc return w; } + + public: virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } @@ -5015,6 +5450,8 @@ namespace olc olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + DragAcceptFiles(olc_hWnd, true); + // Create Keyboard Mapping mapKeys[0x00] = Key::NONE; mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; @@ -5112,6 +5549,44 @@ namespace olc case WM_RBUTTONUP: ptrPGE->olc_UpdateMouseState(1, false); return 0; case WM_MBUTTONDOWN:ptrPGE->olc_UpdateMouseState(2, true); return 0; case WM_MBUTTONUP: ptrPGE->olc_UpdateMouseState(2, false); return 0; + case WM_DROPFILES: + { + // This is all eww... + HDROP drop = (HDROP)wParam; + + uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); + std::vector vFiles; + for (uint32_t i = 0; i < nFiles; i++) + { + TCHAR dfbuffer[256]{}; + uint32_t len = DragQueryFile(drop, i, nullptr, 0); + DragQueryFile(drop, i, dfbuffer, 256); +#ifdef UNICODE + #ifdef __MINGW32__ + char* buffer = new char[len + 1]; + wcstombs(buffer, dfbuffer, len); + buffer[len] = '\0'; + #else + int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); + char* buffer = new char[count]; + WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); + #endif + vFiles.push_back(std::string(buffer)); + delete[] buffer; +#else + vFiles.push_back(std::string(dbuffer)); +#endif + } + + // Even more eww... + POINT p; DragQueryPoint(drop, &p); + ptrPGE->olc_DropFiles(p.x, p.y, vFiles); + DragFinish(drop); + return 0; + } + break; + + case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; } @@ -5681,6 +6156,9 @@ namespace olc { #pragma endregion + + + #pragma region platform_emscripten // O------------------------------------------------------------------------------O // | START PLATFORM: Emscripten - Totally Game Changing... | @@ -5817,118 +6295,100 @@ namespace olc // the giant web baby. - // Fullscreen and Resize Observers EM_ASM({ - // cache for reuse - Module._olc_EmscriptenShellCss = "width: 100%; height: 70vh; margin-left: auto; margin-right: auto;"; + // olc_ApsectRatio + // + // Used by olc_ResizeHandler to calculate the viewport from the + // dimensions of the canvas container's element. + Module.olc_AspectRatio = $0 / $1; + + // HACK ALERT! + // + // Here we assume any html shell that uses 3 or more instance of the class "emscripten" + // is using one of the default or minimal emscripten page layouts + Module.olc_AssumeDefaultShells = (document.querySelectorAll('.emscripten').length >= 3) ? true : false; + + // olc_ResizeHandler + // + // Used by olc_Init, and is called when a resize observer and fullscreenchange event is triggered. + var olc_ResizeHandler = function() + { + // are we in fullscreen mode? + let isFullscreen = (document.fullscreenElement != null); - // width / height = aspect ratio - Module._olc_WindowAspectRatio = $0 / $1; - Module.canvas.parentNode.addEventListener("resize", function(e) { - - if (e.defaultPrevented) { e.stopPropagation(); return; } - var viewWidth = e.detail.width; - var viewHeight = e.detail.width / Module._olc_WindowAspectRatio; - if (viewHeight > e.detail.height) + // get the width of the containing element + let width = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerWidth : Module.canvas.parentNode.clientWidth; + let height = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerHeight : Module.canvas.parentNode.clientHeight; + + // calculate the expected viewport size + let viewWidth = width; + let viewHeight = width / Module.olc_AspectRatio; + + // if we're taller than the containing element, recalculate based on height + if(viewHeight > height) { - viewHeight = e.detail.height; - viewWidth = e.detail.height * Module._olc_WindowAspectRatio; + viewWidth = height * Module.olc_AspectRatio; + viewHeight = height; } - - if (Module.canvas.parentNode.className == 'emscripten_border') - Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss + " width: " + viewWidth.toString() + "px; height: " + viewHeight.toString() + "px;"; - - Module.canvas.setAttribute("width", viewWidth); - Module.canvas.setAttribute("height", viewHeight); - - if (document.fullscreenElement != null) - { - var top = (e.detail.height - viewHeight) / 2; - var left = (e.detail.width - viewWidth) / 2; - Module.canvas.style.position = "fixed"; - Module.canvas.style.top = top.toString() + "px"; - Module.canvas.style.left = left.toString() + "px"; - Module.canvas.style.width = ""; - Module.canvas.style.height = ""; - } - - // trigger PGE update - Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); - // this is really only needed when enter/exiting fullscreen - Module.canvas.focus(); - // prevent this event from ever affecting the document beyond this element - e.stopPropagation(); - }); + + // ensure resulting viewport is in integer space + viewWidth = parseInt(viewWidth); + viewHeight = parseInt(viewHeight); - // helper function to prevent repeating the same code everywhere - Module._olc_ResizeCanvas = function() - { - // yes, we still have to wait, sigh.. setTimeout(function() { - // if default template, stretch width as well - if (Module.canvas.parentNode.className == 'emscripten_border') - Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; - - // override it's styling so we can get it's stretched size - Module.canvas.style.cssText = "width: 100%; height: 100%; outline: none;"; - - // setup custom resize event - var resizeEvent = new CustomEvent('resize', - { - detail: { - width: Module.canvas.clientWidth, - height : Module.canvas.clientHeight - }, - bubbles : true, - cancelable : true - }); - - // trigger custom resize event on canvas element - Module.canvas.dispatchEvent(resizeEvent); - }, 50); + // if default shells, apply default styles + if(Module.olc_AssumeDefaultShells) + Module.canvas.parentNode.setAttribute('style', 'width: 100%; height: 70vh; margin-left: auto; margin-right: auto;'); + + // apply viewport dimensions to teh canvas + Module.canvas.setAttribute('width', viewWidth); + Module.canvas.setAttribute('height', viewHeight); + Module.canvas.setAttribute('style', `width: ${viewWidth}px; height: ${viewHeight}px;`); + + // update the PGE window size + Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); + + // force focus on our PGE canvas + Module.canvas.focus(); + }, 200); }; - - // Disable Refresh Gesture on mobile - document.body.style.cssText += " overscroll-behavior-y: contain;"; - - if (Module.canvas.parentNode.className == 'emscripten_border') + + // olc_Init + // + // set up resize observer and fullscreenchange event handler + var olc_Init = function() { - // force body to have no margin in emscripten's minimal shell - document.body.style.margin = "0"; - Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; - } - - Module._olc_ResizeCanvas(); + if(Module.olc_AspectRatio === undefined) + { + setTimeout(function() { Module.olc_Init(); }, 50); + return; + } + + let resizeObserver = new ResizeObserver(function(entries) + { + Module.olc_ResizeHandler(); + }).observe(Module.canvas.parentNode); - // observe and react to resizing of the container element - var resizeObserver = new ResizeObserver(function(entries) {Module._olc_ResizeCanvas();}).observe(Module.canvas.parentNode); + let mutationObserver = new MutationObserver(function(mutationsList, observer) + { + setTimeout(function() { Module.olc_ResizeHandler(); }, 200); + }).observe(Module.canvas.parentNode, { attributes: false, childList: true, subtree: false }); - // observe and react to changes that occur when entering/exiting fullscreen - var mutationObserver = new MutationObserver(function(mutationsList, observer) - { - // a change has occurred, let's check them out! - for (var i = 0; i < mutationsList.length; i++) + window.addEventListener('fullscreenchange', function(e) { - // cycle through all of the newly added elements - for (var j = 0; j < mutationsList[i].addedNodes.length; j++) - { - // if this element is a our canvas, trigger resize - if (mutationsList[i].addedNodes[j].id == 'canvas') - Module._olc_ResizeCanvas(); - } - } - }).observe(Module.canvas.parentNode, - { - attributes: false, - childList : true, - subtree : false - }); + setTimeout(function() { Module.olc_ResizeHandler();}, 200); + }); + }; + + // set up hooks + Module.olc_ResizeHandler = (Module.olc_ResizeHandler != undefined) ? Module.olc_ResizeHandler : olc_ResizeHandler; + Module.olc_Init = (Module.olc_Init != undefined) ? Module.olc_Init : olc_Init; - // add resize listener on window - window.addEventListener("resize", function(e) { Module._olc_ResizeCanvas(); }); + // run everything! + Module.olc_Init(); }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers #pragma warning restore format @@ -6138,7 +6598,9 @@ namespace olc void PixelGameEngine::olc_ConfigureSystem() { -#if !defined(OLC_PGE_HEADLESS) +//#if !defined(OLC_PGE_HEADLESS) + + olc::Sprite::loader = nullptr; #if defined(OLC_IMAGE_GDI) olc::Sprite::loader = std::make_unique(); @@ -6157,7 +6619,9 @@ namespace olc #endif - +#if defined(OLC_PLATFORM_HEADLESS) + platform = std::make_unique(); +#endif #if defined(OLC_PLATFORM_WINAPI) platform = std::make_unique(); @@ -6179,7 +6643,9 @@ namespace olc platform = std::make_unique(); #endif - +#if defined(OLC_GFX_HEADLESS) + renderer = std::make_unique(); +#endif #if defined(OLC_GFX_OPENGL10) renderer = std::make_unique(); @@ -6208,11 +6674,11 @@ namespace olc // Associate components with PGE instance platform->ptrPGE = this; renderer->ptrPGE = this; -#else - olc::Sprite::loader = nullptr; - platform = nullptr; - renderer = nullptr; -#endif +//#else +// olc::Sprite::loader = nullptr; +// platform = nullptr; +// renderer = nullptr; +//#endif } } diff --git a/utilities/olcUTIL_Camera2D.h b/utilities/olcUTIL_Camera2D.h new file mode 100644 index 00000000..a482b78c --- /dev/null +++ b/utilities/olcUTIL_Camera2D.h @@ -0,0 +1,258 @@ +/* + OneLoneCoder - Camera2D v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 2D world camera with various modes + + + 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" + +namespace olc::utils +{ + class Camera2D + { + public: + enum class Mode : uint8_t + { + Simple, // No motion, just directly settable + EdgeMove, // Moves as target crosses boundary + LazyFollow, // Lazily follows the target + FixedScreens, // Moves statically between "screens" + }; + + public: + inline Camera2D() : m_pTarget(&m_vLocalTarget) {} + + // Construct a camera with a viewable area size, and an optional starting position + inline Camera2D(const olc::vf2d& vViewSize, const olc::vf2d& vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget) + { + m_vViewSize = vViewSize; + m_vViewPos = vViewPos; + } + + // Set the operational mode of this camera + inline void SetMode(const Mode t) + { + m_nMode = t; + } + + // Get the operational mode of this camera + inline Mode GetMode() const + { + return m_nMode; + } + + // Get the position of the target being tracked by this camera + inline const olc::vf2d& GetTarget() const + { + return *m_pTarget; + } + + // Get the position of the cameras focus point + inline const olc::vf2d& GetPosition() const + { + return m_vPosition; + } + + // Get the top left of teh cameras visible area in world space + inline const olc::vf2d& GetViewPosition() const + { + return m_vViewPos; + } + + // Get the camera's visible area + inline const olc::vf2d& GetViewSize() const + { + return m_vViewSize; + } + + // Set tracked point via pointer + inline void SetTarget(olc::vf2d& vTarget) + { + m_pTarget = &vTarget; + } + + // Set tracked point via const ref - {10, 35} for example + inline void SetTarget(const olc::vf2d&& vTarget) + { + m_vLocalTarget = vTarget; + m_pTarget = &m_vLocalTarget; + } + + // Set world boundary rectangle + inline void SetWorldBoundary(const olc::vf2d& vPos, const olc::vf2d& vSize) + { + m_vWorldBoundaryPos = vPos; + m_vWorldBoundarySize = vSize; + } + + // Instruct camera to respect world boundaries + inline void EnableWorldBoundary(const bool bEnable) + { + m_bWorldBoundary = bEnable; + } + + // Are we using a world boundary? + inline bool IsWorldBoundaryEnabled() const + { + return m_bWorldBoundary; + } + + // Get the world boundary rectangle position + inline const olc::vf2d& GetWorldBoundaryPosition() const + { + return m_vWorldBoundaryPos; + } + + // Get the world boundary rectangle size + inline const olc::vf2d& GetWorldBoundarySize() const + { + return m_vWorldBoundarySize; + } + + // Set the velocity at which the lazy follower reaches tracked point + inline void SetLazyFollowRate(const float fRate) + { + m_fLazyFollowRate = fRate; + } + + // Get the velocity at which the lazy follower reaches tracked point + inline float GetLazyFollowRate() const + { + return m_fLazyFollowRate; + } + + // Set distance from tracked point to start nudging screen + inline void SetEdgeTriggerDistance(const olc::vf2d& vEdge) + { + m_vEdgeTriggerDistance = vEdge; + } + + // Return disance from tracked point that screen will nudge + inline const olc::vf2d& GetEdgeTriggerDistance() const + { + return m_vEdgeTriggerDistance; + } + + // Update camera, animating if necessary, obeying world boundary rules + // returns true if target is visible + inline virtual bool Update(const float fElapsedTime) + { + switch (m_nMode) + { + case Mode::Simple: + { + m_vPosition = GetTarget(); + } + break; + + case Mode::EdgeMove: + { + olc::vf2d vOverlap = GetTarget() - m_vPosition; + if (vOverlap.x > m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x - m_vEdgeTriggerDistance.x; + if (vOverlap.x < -m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x + m_vEdgeTriggerDistance.x; + if (vOverlap.y > m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y - m_vEdgeTriggerDistance.y; + if (vOverlap.y < -m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y + m_vEdgeTriggerDistance.y; + } + break; + + case Mode::LazyFollow: + { + m_vPosition += (GetTarget() - m_vPosition) * m_fLazyFollowRate * fElapsedTime; + } + break; + + case Mode::FixedScreens: + { + m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); + } + break; + } + + // Make camera target the middle of the view + m_vViewPos = m_vPosition - (m_vViewSize * 0.5f); + + // Clamp to World Boundary (if in place) + if (m_bWorldBoundary) + { + m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize); + } + + return GetTarget().x >= m_vViewPos.x && GetTarget().x < (m_vViewPos.x + m_vViewSize.x) && + GetTarget().y >= m_vViewPos.y && GetTarget().y < (m_vViewPos.y + m_vViewSize.y); + } + + protected: + // Position of camera focus point in the world + olc::vf2d m_vPosition; + // Rectangular size of camera viewing area + olc::vf2d m_vViewSize; + // Top left coordinate of camera viewing area + olc::vf2d m_vViewPos; + // Camera movement mode + Mode m_nMode = Mode::Simple; + + // Target Vector2D object camera should follow (either ref or ptr) + olc::vf2d* m_pTarget = nullptr; + olc::vf2d m_vLocalTarget; + + // World Boundary + bool m_bWorldBoundary = false; + olc::vf2d m_vWorldBoundaryPos = { 0.0f, 0.0f }; + olc::vf2d m_vWorldBoundarySize = { 256.0f, 240.0f }; + + // Mode specific + olc::vf2d m_vEdgeTriggerDistance = { 1.0f, 1.0f }; + float m_fLazyFollowRate = 4.0f; + olc::vi2d m_vScreenSize = { 16,15 }; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL_Container.h b/utilities/olcUTIL_Container.h new file mode 100644 index 00000000..3e866933 --- /dev/null +++ b/utilities/olcUTIL_Container.h @@ -0,0 +1,158 @@ +/* + OneLoneCoder - Container v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Assortment of std::container like objects with access specific mechanisms + + + 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 + +*/ + +/* + SingleSelection + ~~~~~~~~~~~~~~~ + This container behaves like a std::vector in all circumstances but features + additional methods that allow it to surve as a list of items, one of which + can be selected, and the items can be manipulated. + + An example of this would be the image layers in Photoshop. + + For convenience, all container operations operate on an item index rather + than an iterator +*/ + +#pragma once + +#include +#include + +namespace olc::utils::Container +{ + template + class SingleSelection : public std::vector + { + public: + + SingleSelection() : std::vector() + {} + + + SingleSelection(std::initializer_list list) : std::vector(list) + {} + + // Returns selected item in container + size_t selection() const + { + return m_nSelectedItem; + } + + // Return the item actually selected + const T& selected() const + { + return this->operator[](m_nSelectedItem); + } + + // Return the item actually selected + T& selected() + { + return this->operator[](m_nSelectedItem); + } + + // Select item in container + void select(const size_t item) + { + m_nSelectedItem = std::clamp(item, size_t(0), this->size() - size_t(1)); + } + + // Move selected item positively + void move_up() + { + if(move_up(m_nSelectedItem)) + m_nSelectedItem++; + } + + // Move selected item negatively + void move_down() + { + if(move_down(m_nSelectedItem)) + m_nSelectedItem--; + } + + // Move specified item negatively + bool move_down(const size_t item) + { + // Are there at least two items and not first one selected? + if (this->size() >= 2 && item > 0) + { + std::swap(this->operator[](item - 1), this->operator[](item)); + return true; + } + return false; + } + + // Move specified item positively + bool move_up(const size_t item) + { + // Are there at least two items and not last one selected? + if (this->size() >= 2 && item < this->size() - 1) + { + std::swap(this->operator[](item + 1), this->operator[](item)); + return true; + } + return false; + } + + void insert_after(const size_t idx, const T& value) + { + this->insert(idx, value); + } + + + protected: + size_t m_nSelectedItem = 0; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL_DataFile.h b/utilities/olcUTIL_DataFile.h new file mode 100644 index 00000000..ebd6e8ff --- /dev/null +++ b/utilities/olcUTIL_DataFile.h @@ -0,0 +1,435 @@ +/* + OneLoneCoder - DataFile v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + An "easy to use" serialisation/deserialisation class that yields + human readable hierachical files. + + 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 +#include +#include +#include +#include +#include +#include + +namespace olc::utils +{ + class datafile + { + public: + inline datafile() = default; + + public: + // Sets the String Value of a Property (for a given index) + inline void SetString(const std::string& sString, const size_t nItem = 0) + { + if (nItem >= m_vContent.size()) + m_vContent.resize(nItem + 1); + + m_vContent[nItem] = sString; + } + + // Retrieves the String Value of a Property (for a given index) or "" + inline const std::string GetString(const size_t nItem = 0) const + { + if (nItem >= m_vContent.size()) + return ""; + else + return m_vContent[nItem]; + } + + // Retrieves the Real Value of a Property (for a given index) or 0.0 + inline const double GetReal(const size_t nItem = 0) const + { + return std::atof(GetString(nItem).c_str()); + } + + // Sets the Real Value of a Property (for a given index) + inline void SetReal(const double d, const size_t nItem = 0) + { + SetString(std::to_string(d), nItem); + } + + // Retrieves the Integer Value of a Property (for a given index) or 0 + inline const int32_t GetInt(const size_t nItem = 0) const + { + return std::atoi(GetString(nItem).c_str()); + } + + // Sets the Integer Value of a Property (for a given index) + inline void SetInt(const int32_t n, const size_t nItem = 0) + { + SetString(std::to_string(n), nItem); + } + + // Returns the number of Values a property consists of + inline size_t GetValueCount() const + { + return m_vContent.size(); + } + + // Checks if a property exists - useful to avoid creating properties + // via reading them, though non-essential + inline bool HasProperty(const std::string& sName) const + { + return m_mapObjects.count(sName) > 0; + } + + // Access a datafile via a convenient name - "root.node.something.property" + inline datafile& GetProperty(const std::string& name) + { + size_t x = name.find_first_of('.'); + if (x != std::string::npos) + { + std::string sProperty = name.substr(0, x); + if (HasProperty(sProperty)) + return operator[](sProperty).GetProperty(name.substr(x + 1, name.size())); + else + return operator[](sProperty); + } + else + { + return operator[](name); + } + } + + // Access a numbered element - "node[23]", or "root[56].node" + inline datafile& GetIndexedProperty(const std::string& name, const size_t nIndex) + { + return GetProperty(name + "[" + std::to_string(nIndex) + "]"); + } + + public: + // Writes a "datafile" node (and all of its child nodes and properties) recursively + // to a file. + inline static bool Write(const datafile& n, const std::string& sFileName, const std::string& sIndent = "\t", const char sListSep = ',') + { + // Cache indentation level + size_t nIndentCount = 0; + // Cache sperator string for convenience + std::string sSeperator = std::string(1, sListSep) + " "; + + // Fully specified lambda, because this lambda is recursive! + std::function write = [&](const datafile& n, std::ofstream& file) + { + // Lambda creates string given indentation preferences + auto indent = [&](const std::string& sString, const size_t nCount) + { + std::string sOut; + for (size_t n = 0; n < nCount; n++) sOut += sString; + return sOut; + }; + + // Iterate through each property of this node + for (auto const& property : n.m_vecObjects) + { + // Does property contain any sub objects? + if (property.second.m_vecObjects.empty()) + { + // No, so it's an assigned field and should just be written. If the property + // is flagged as comment, it has no assignment potential. First write the + // property name + file << indent(sIndent, nIndentCount) << property.first << (property.second.m_bIsComment ? "" : " = "); + + // Second, write the property value (or values, seperated by provided + // separation charater + size_t nItems = property.second.GetValueCount(); + for (size_t i = 0; i < property.second.GetValueCount(); i++) + { + // If the Value being written, in string form, contains the separation + // character, then the value must be written inside quotation marks. Note, + // that if the Value is the last of a list of Values for a property, it is + // not suffixed with the separator + size_t x = property.second.GetString(i).find_first_of(sListSep); + if (x != std::string::npos) + { + // Value contains separator, so wrap in quotes + file << "\"" << property.second.GetString(i) << "\"" << ((nItems > 1) ? sSeperator : ""); + } + else + { + // Value does not contain separator, so just write out + file << property.second.GetString(i) << ((nItems > 1) ? sSeperator : ""); + } + nItems--; + } + + // Property written, move to next line + file << "\n"; + } + else + { + // Yes, property has properties of its own, so it's a node + // Force a new line and write out the node's name + file << "\n" << indent(sIndent, nIndentCount) << property.first << "\n"; + // Open braces, and update indentation + file << indent(sIndent, nIndentCount) << "{\n"; + nIndentCount++; + // Recursively write that node + write(property.second, file); + // Node written, so close braces + file << indent(sIndent, nIndentCount) << "}\n\n"; + } + } + + // We've finished writing out a node, regardless of state, our indentation + // must decrease, unless we're top level + if (nIndentCount > 0) nIndentCount--; + }; + + // Start Here! Open the file for writing + std::ofstream file(sFileName); + if (file.is_open()) + { + // Write the file starting form the supplied node + write(n, file); + return true; + } + return false; + } + + inline static bool Read(datafile& n, const std::string& sFileName, const char sListSep = ',') + { + // Open the file! + std::ifstream file(sFileName); + if (file.is_open()) + { + // These variables are outside of the read loop, as we will + // need to refer to previous iteration values in certain conditions + std::string sPropName = ""; + std::string sPropValue = ""; + + // The file is fundamentally structured as a stack, so we will read it + // in a such, but note the data structure in memory is not explicitly + // stored in a stack, but one is constructed implicitly via the nodes + // owning other nodes (aka a tree) + + // I dont want to accidentally create copies all over the place, nor do + // I want to use pointer syntax, so being a bit different and stupidly + // using std::reference_wrapper, so I can store references to datafile + // nodes in a std::container. + std::stack> stkPath; + stkPath.push(n); + + + // Read file line by line and process + while (!file.eof()) + { + // Read line + std::string line; + std::getline(file, line); + + // This little lambda removes whitespace from + // beginning and end of supplied string + auto trim = [](std::string& s) + { + s.erase(0, s.find_first_not_of(" \t\n\r\f\v")); + s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1); + }; + + trim(line); + + // If line has content + if (!line.empty()) + { + // Test if its a comment... + if (line[0] == '#') + { + // ...it is a comment, so ignore + datafile comment; + comment.m_bIsComment = true; + stkPath.top().get().m_vecObjects.push_back({ line, comment }); + } + else + { + // ...it is content, so parse. Firstly, find if the line + // contains an assignment. If it does then it's a property... + size_t x = line.find_first_of('='); + if (x != std::string::npos) + { + // ...so split up the property into a name, and its values! + + // Extract the property name, which is all characters up to + // first assignment, trim any whitespace from ends + sPropName = line.substr(0, x); + trim(sPropName); + + // Extract the property value, which is all characters after + // the first assignment operator, trim any whitespace from ends + sPropValue = line.substr(x + 1, line.size()); + trim(sPropValue); + + // The value may be in list form: a, b, c, d, e, f etc and some of those + // elements may exist in quotes a, b, c, "d, e", f. So we need to iterate + // character by character and break up the value + bool bInQuotes = false; + std::string sToken; + size_t nTokenCount = 0; + for (const auto c : sPropValue) + { + // Is character a quote... + if (c == '\"') + { + // ...yes, so toggle quote state + bInQuotes = !bInQuotes; + } + else + { + // ...no, so proceed creating token. If we are in quote state + // then just append characters until we exit quote state. + if (bInQuotes) + { + sToken.append(1, c); + } + else + { + // Is the character our seperator? If it is + if (c == sListSep) + { + // Clean up the token + trim(sToken); + // Add it to the vector of values for this property + stkPath.top().get()[sPropName].SetString(sToken, nTokenCount); + // Reset our token state + sToken.clear(); + nTokenCount++; + } + else + { + // It isnt, so just append to token + sToken.append(1, c); + } + } + } + } + + // Any residual characters at this point just make up the final token, + // so clean it up and add it to the vector of values + if (!sToken.empty()) + { + trim(sToken); + stkPath.top().get()[sPropName].SetString(sToken, nTokenCount); + } + } + else + { + // ...but if it doesnt, then it's something structural + if (line[0] == '{') + { + // Open brace, so push this node to stack, subsequent properties + // will belong to the new node + stkPath.push(stkPath.top().get()[sPropName]); + } + else + { + if (line[0] == '}') + { + // Close brace, so this node has been defined, pop it from the + // stack + stkPath.pop(); + } + else + { + // Line is a property with no assignment. Who knows whether this is useful, + // but we can simply add it as a valueless property... + sPropName = line; + // ...actually it is useful, as valuless properties are typically + // going to be the names of new datafile nodes on the next iteration + } + } + } + } + } + } + + // Close and exit! + file.close(); + return true; + } + + // File not found, so fail + return false; + } + + public: + inline datafile& operator[](const std::string& name) + { + // Check if this "node"'s map already contains an object with this name... + if (m_mapObjects.count(name) == 0) + { + // ...it did not! So create this object in the map. First get a vector id + // and link it with the name in the unordered_map + m_mapObjects[name] = m_vecObjects.size(); + // then creating the new, blank object in the vector of objects + m_vecObjects.push_back({ name, datafile() }); + } + + // ...it exists! so return the object, by getting its index from the map, and using that + // index to look up a vector element. + return m_vecObjects[m_mapObjects[name]].second; + } + + private: + // The "list of strings" that make up a property value + std::vector m_vContent; + + // Linkage to create "ordered" unordered_map. We have a vector of + // "properties", and the index to a specific element is mapped. + std::vector> m_vecObjects; + std::unordered_map m_mapObjects; + + protected: + // Used to identify if a property is a comment or not, not user facing + bool m_bIsComment = false; + }; +} diff --git a/utilities/olcUTIL_Geometry2D.h b/utilities/olcUTIL_Geometry2D.h index c6252719..801c1d34 100644 --- a/utilities/olcUTIL_Geometry2D.h +++ b/utilities/olcUTIL_Geometry2D.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - Geometry 2D v1.00 + OneLoneCoder - Geometry 2D v1.01 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A collection of 2D Geometric primitives and functions to work with and between them. @@ -51,17 +51,22 @@ ~~~~~~ David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + Changes: + v1.01: +Made constants inline + +Header guards (lol... sigh...) + */ +#pragma once #include "olcPixelGameEngine.h" namespace olc::utils::geom2d { // Lemon Meringue - const double pi = 3.141592653589793238462643383279502884; + inline const double pi = 3.141592653589793238462643383279502884; // Floating point error margin - const double epsilon = 0.001; + inline const double epsilon = 0.001; //https://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c template diff --git a/utilities/olcUTIL_Palette.h b/utilities/olcUTIL_Palette.h index 68b17550..b0c38f5f 100644 --- a/utilities/olcUTIL_Palette.h +++ b/utilities/olcUTIL_Palette.h @@ -235,4 +235,5 @@ namespace olc::utils } } }; + } \ No newline at end of file diff --git a/utilities/olcUTIL_QuadTree.h b/utilities/olcUTIL_QuadTree.h index 636e5a20..2a52a6dd 100644 --- a/utilities/olcUTIL_QuadTree.h +++ b/utilities/olcUTIL_QuadTree.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - QuadTree v1.00 + OneLoneCoder - QuadTree v1.01 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A Dynamic Quad-Tree implementation to store objects in a 2D space with a fast retrieval. @@ -50,6 +50,24 @@ ~~~~~~ David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + + Changes + ~~~~~~~ + v1.01: Bug fix clear() function + +*/ + + +/* + WARNING! VECTORS OF QUAD-TREES + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + This quad tree implementation will suffer when stored inside a container + that can reallocate its contents. Upon reallocation, all of the iterators + used to link the quad tree to its items will be invalidated. + + Currently, I advise caution when using std::vector of this quad tree, and + should you choose to, then reserve space in advance so reallocation cannot + occur. */ @@ -313,6 +331,7 @@ namespace olc::utils void clear() { + m_allItems.clear(); root.clear(); }