From f512eb5f67259e5b31db994a07403768bb1b5235 Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Sun, 21 Aug 2022 21:45:06 -0500 Subject: [PATCH] Include C++ building for olcGameEngine Co-authored-by: sigonasr2 --- C++/scripts/build.sh | 7 + C++/scripts/commit.sh | 20 + CProjectTemplate | Bin 16696 -> 538784 bytes main.c | 5 - main.cpp | 37 + pixelGameEngine.h | 6191 +++++++++++++++++++++++++++++++++++++++++ sig | 4 +- utils/main.sh | 2 +- utils/md5 | 4 +- 9 files changed, 6260 insertions(+), 10 deletions(-) create mode 100755 C++/scripts/build.sh create mode 100755 C++/scripts/commit.sh delete mode 100644 main.c create mode 100644 main.cpp create mode 100644 pixelGameEngine.h diff --git a/C++/scripts/build.sh b/C++/scripts/build.sh new file mode 100755 index 0000000..074ffa4 --- /dev/null +++ b/C++/scripts/build.sh @@ -0,0 +1,7 @@ +#Compiles the entire program then runs it, producing an executable. +#C +printf "Running program...\n\n\n" +if g++ $(find . -type f -name "*.cpp") ${CUSTOM_PARAMS} -o ${PROJECT_NAME}; then + ./${PROJECT_NAME} "$@" +fi +printf "\n\n" diff --git a/C++/scripts/commit.sh b/C++/scripts/commit.sh new file mode 100755 index 0000000..0f56a8f --- /dev/null +++ b/C++/scripts/commit.sh @@ -0,0 +1,20 @@ +#Adds a commit message and pushes project to github repository. +#C +COMMIT_MESSAGE="$*" +FIRST_LINE=true +while IFS= read -r line +do +if [ "$FIRST_LINE" = true ]; then + COMMIT_MESSAGE+=" + +Co-authored-by: $line" +FIRST_LINE=false +else + COMMIT_MESSAGE+=" +Co-authored-by: $line" +fi +done < utils/.coauthors +git add -u +git add * +git commit -m "$COMMIT_MESSAGE" +git push \ No newline at end of file diff --git a/CProjectTemplate b/CProjectTemplate index deadea398858006a6c816b2cd5e892b6f935c76e..5a7c2f9de371a5bb7dc883c1ce778d08e0397735 100755 GIT binary patch literal 538784 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⁢90)IU*a`f`se1iP6lSg7>=2D5 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 uzi04g&uz~|OgJ?7ef`CD?OouonXvGHFjdOySf literal 16696 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 diff --git a/main.c b/main.c deleted file mode 100644 index e4b3f6b..0000000 --- a/main.c +++ /dev/null @@ -1,5 +0,0 @@ -#include - -int main(int argc,char**argv) { - printf("Hello World!"); -} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e53f82e --- /dev/null +++ b/main.cpp @@ -0,0 +1,37 @@ +#define OLC_PGE_APPLICATION +#include "pixelGameEngine.h" + +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } +}; + + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + + return 0; +} \ No newline at end of file diff --git a/pixelGameEngine.h b/pixelGameEngine.h new file mode 100644 index 0000000..37ffb75 --- /dev/null +++ b/pixelGameEngine.h @@ -0,0 +1,6191 @@ +#pragma region license_and_help +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v2.19 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + + 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 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + Community: https://community.onelonecoder.com + + + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17 + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64. + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + Add these libraries to "Linker Options": + user32 gdi32 opengl32 gdiplus Shlwapi dwmapi stdc++fs + + Set these compiler options: -std=c++17 + + + + Compiling on Mac - EXPERIMENTAL! PROBABLY HAS BUGS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Yes yes, people use Macs for C++ programming! Who knew? Anyway, enough + arguing, thanks to Mumflr the PGE is now supported on Mac. Now I know nothing + about Mac, so if you need support, I suggest checking out the instructions + here: https://github.com/MumflrFumperdink/olcPGEMac + + clang++ -arch x86_64 -std=c++17 -mmacosx-version-min=10.15 -Wall -framework OpenGL + -framework GLUT -framework Carbon -lpng YourSource.cpp -o YourProgName + + + + Compiling with Emscripten (New & Experimental) + ~~~~~~~~~~~~~~~~~~~~~~~~~ + Emscripten compiler will turn your awesome C++ PixelGameEngine project into WASM! + This means you can run your application in teh browser, great for distributing + and submission in to jams and things! It's a bit new at the moment. + + em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 ./YourSource.cpp -o pge.html + + + + Using stb_image.h + ~~~~~~~~~~~~~~~~~ + The PGE will load png images by default (with help from libpng on non-windows systems). + However, the excellent "stb_image.h" can be used instead, supporting a variety of + image formats, and has no library dependence - something we like at OLC studios ;) + To use stb_image.h, make sure it's in your code base, and simply: + + #define OLC_IMAGE_STB + + Before including the olcPixelGameEngine.h header file. stb_image.h works on many systems + and can be downloaded here: https://github.com/nothings/stb/blob/master/stb_image.h + + + + Multiple cpp file projects? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + As a single header solution, the OLC_PGE_APPLICATION definition is used to + insert the engine implementation at a project location of your choosing. + The simplest way to setup multifile projects is to create a file called + "olcPixelGameEngine.cpp" which includes the following: + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + + That's all it should include. You can also include PGEX includes and + defines in here too. With this in place, you dont need to + #define OLC_PGE_APPLICATION anywhere, and can simply include this + header file as an when you need to. + + + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Raspberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + + + Thanks + ~~~~~~ + I'd like to extend thanks to Ian McKay, Bispoo, Eremiell, slavka, Kwizatz77, gurkanctn, Phantim, + IProgramInCPP, JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice, + dandistine, Ralakus, Gorbit99, raoul, joshinils, benedani, Moros1138, Alexio, SaladinAkara + & MagetzUb for advice, ideas and testing, and I'd like to extend my appreciation to the + 250K YouTube followers, 80+ Patreons, 4.8K Twitch followers and 10K Discord server members + who give me the motivation to keep going with all this :D + + Significant Contributors: @Moros1138, @SaladinAkara, @MaGetzUb, @slavka, + @Dragoneye, @Gorbit99, @dandistine & @Mumflr + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest, Terraria, Spelunky 2, Skully + Marti Morta........Gris + Danicron...........Terraria + SaladinAkara.......Aseprite, Inside, Quern: Undying Thoughts, Outer Wilds + AlterEgo...........Final Fantasy XII - The Zodiac Age + SlicEnDicE.........Noita, Inside + TGD................Voucher Gift + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + + + Author + ~~~~~~ + David Barr, aka javidx9, �OneLoneCoder 2018, 2019, 2020, 2021, 2022 +*/ +#pragma endregion + +#pragma region version_history +/* + 2.01: Made renderer and platform static for multifile projects + 2.02: Added Decal destructor, optimised Pixel constructor + 2.03: Added FreeBSD flags, Added DrawStringDecal() + 2.04: Windows Full-Screen bug fixed + 2.05: +DrawPartialWarpedDecal() - draws a warped decal from a subset image + +DrawPartialRotatedDecal() - draws a rotated decal from a subset image + 2.06: +GetTextSize() - returns area occupied by multiline string + +GetWindowSize() - returns actual window size + +GetElapsedTime() - returns last calculated fElapsedTime + +GetWindowMouse() - returns actual mouse location in window + +DrawExplicitDecal() - bow-chikka-bow-bow + +DrawPartialDecal(pos, size) - draws a partial decal to specified area + +FillRectDecal() - draws a flat shaded rectangle as a decal + +GradientFillRectDecal() - draws a rectangle, with unique colour corners + +Modified DrawCircle() & FillCircle() - Thanks IanM-Matrix1 (#PR121) + +Gone someway to appeasing pedants + 2.07: +GetPixelSize() - returns user specified pixel size + +GetScreenPixelSize() - returns actual size in monitor pixels + +Pixel Cohesion Mode (flag in Construct()) - disallows arbitrary window scaling + +Working VSYNC in Windows windowed application - now much smoother + +Added string conversion for olc::vectors + +Added comparator operators for olc::vectors + +Added DestroyWindow() on windows platforms for serial PGE launches + +Added GetMousePos() to stop TarriestPython whinging + 2.08: Fix SetScreenSize() aspect ratio pre-calculation + Fix DrawExplicitDecal() - stupid oversight with multiple decals + Disabled olc::Sprite copy constructor + +olc::Sprite Duplicate() - produces a new clone of the sprite + +olc::Sprite Duplicate(pos, size) - produces a new sprite from the region defined + +Unary operators for vectors + +More pedant mollification - Thanks TheLandfill + +ImageLoader modules - user selectable image handling core, gdi+, libpng, stb_image + +Mac Support via GLUT - thanks Mumflr! + 2.09: Fix olc::Renderable Image load error - Thanks MaGetzUb & Zij-IT for finding and moaning about it + Fix file rejection in image loaders when using resource packs + Tidied Compiler defines per platform - Thanks slavka + +Pedant fixes, const correctness in parts + +DecalModes - Normal, Additive, Multiplicative blend modes + +Pixel Operators & Lerping + +Filtered Decals - If you hate pixels, then erase this file + +DrawStringProp(), GetTextSizeProp(), DrawStringPropDecal() - Draws non-monospaced font + 2.10: Fix PixelLerp() - oops my bad, lerped the wrong way :P + Fix "Shader" support for strings - thanks Megarev for crying about it + Fix GetTextSizeProp() - Height was just plain wrong... + +vec2d operator overloads (element wise *=, /=) + +vec2d comparison operators... :| yup... hmmmm... + +vec2d ceil(), floor(), min(), max() functions - surprising how often I do it manually + +DrawExplicitDecal(... uint32_t elements) - complete control over convex polygons and lines + +DrawPolygonDecal() - to keep Bispoo happy, required significant rewrite of EVERYTHING, but hey ho + +Complete rewrite of decal renderer + +OpenGL 3.3 Renderer (also supports Raspberry Pi) + +PGEX Break-In Hooks - with a push from Dandistine + +Wireframe Decal Mode - For debug overlays + 2.11: Made PGEX hooks optional - (provide true to super constructor) + 2.12: Fix for MinGW compiler non-compliance :( - why is its sdk structure different?? why??? + 2.13: +GetFontSprite() - allows access to font data + 2.14: Fix WIN32 Definition reshuffle + Fix DrawPartialDecal() - messed up dimension during renderer experiment, didnt remove junk code, thanks Alexio + Fix? Strange error regarding GDI+ Image Loader not knowing about COM, SDK change? + 2.15: Big Reformat + +WASM Platform (via Emscripten) - Big Thanks to OLC Community - See Platform for details + +Sample Mode for Decals + +Made olc_ConfigureSystem() accessible + +Added OLC_----_CUSTOM_EX for externalised platforms, renderers and image loaders + =Refactored olc::Sprite pixel data store + -Deprecating LoadFromPGESprFile() + -Deprecating SaveToPGESprFile() + Fix Pixel -= operator (thanks Au Lit) + 2.16: FIX Emscripten JS formatting in VS IDE (thanks Moros) + +"Headless" Mode + +DrawLineDecal() + +Mouse Button Constants + +Move Constructor for olc::Renderable + +Polar/Cartesian conversion for v2d_generic + +DrawRotatedStringDecal()/DrawRotatedStringPropDecal() (thanks Oso-Grande/Sopadeoso (PR #209)) + =Using olc::Renderable for layer surface + +Major Mac and GLUT Update (thanks Mumflr) + 2.17: +Clipping for DrawLine() functions + +Reintroduced sub-pixel decals + +Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses + +olc::Sprite::GetPixel() - Clamp Mode + 2.18: +Option to not "dirty" layers with SetDrawTarget() - Thanks TerasKasi! + =Detection for Mac M1, fix for scroll wheel interrogation - Thanks ruarq! + 2.19: Textual Input(of)course Edition! + =Built in font is now olc::Renderable + +EnablePixelTransfer() - Gate if layer content transfers occur (speedup in decal only apps) + +TextEntryEnable() - Enables/Disables text entry mode + +TextEntryGetString() - Gets the current accumulated string in text entry mode + +TextEntryGetCursor() - Gets the current cursor position in text entry mode + +IsTextEntryEnabled() - Returns true if text entry mode is activated + +OnTextEntryComplete() - Override is called when user presses "ENTER" in text entry mode + +Potential for regional keyboard mappings - needs volunteers to do this + +ConsoleShow() - Opens built in command console + +ConsoleClear() - Clears built in command console output + +ConsoleOut() - Stream strings to command console output + +ConsoleCaptureStdOut() - Capture std::cout by redirecting to built-in console + +IsConsoleShowing() - Returns true if console is currently active + +OnConsoleCommand() - Override is called when command is entered into built in console + + !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! + !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! +*/ +#pragma endregion + +#pragma region hello_world_example +// O------------------------------------------------------------------------------O +// | Example "Hello World" Program (main.cpp) | +// O------------------------------------------------------------------------------O +/* + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Override base class with your custom functionality +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + // Name your application + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand() % 256)); + return true; + } +}; + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} + +*/ +#pragma endregion + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#pragma region std_includes +// O------------------------------------------------------------------------------O +// | STANDARD INCLUDES | +// O------------------------------------------------------------------------------O +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma endregion + +#define PGE_VER 219 + +// O------------------------------------------------------------------------------O +// | COMPILER CONFIGURATION ODDITIES | +// O------------------------------------------------------------------------------O +#pragma region compiler_config +#define USE_EXPERIMENTAL_FS +#if defined(_WIN32) + #if _MSC_VER >= 1920 && _MSVC_LANG >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif +#if defined(__linux__) || defined(__MINGW32__) || defined(__EMSCRIPTEN__) || defined(__FreeBSD__) || defined(__APPLE__) + #if __cplusplus >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif + +#if !defined(OLC_KEYBOARD_UK) + #define OLC_KEYBOARD_UK +#endif + + +#if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) + // C++14 + #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + #include + namespace _gfs = std::experimental::filesystem::v1; +#else + // C++17 + #include + namespace _gfs = std::filesystem; +#endif + +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define UNUSED(x) (void)(x) + +// O------------------------------------------------------------------------------O +// | PLATFORM SELECTION CODE, Thanks slavka! | +// O------------------------------------------------------------------------------O + +// Platform +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) + #if !defined(OLC_PLATFORM_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_PLATFORM_WINAPI + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #define OLC_PLATFORM_X11 + #endif + #if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #define OLC_PLATFORM_GLUT + #endif + #if defined(__EMSCRIPTEN__) + #define OLC_PLATFORM_EMSCRIPTEN + #endif + #endif +#endif + +// Start Situation +#if defined(OLC_PLATFORM_GLUT) || defined(OLC_PLATFORM_EMSCRIPTEN) + #define PGE_USE_CUSTOM_START +#endif + +// Renderer +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) + #if !defined(OLC_GFX_CUSTOM_EX) + #if defined(OLC_PLATFORM_EMSCRIPTEN) + #define OLC_GFX_OPENGL33 + #else + #define OLC_GFX_OPENGL10 + #endif + #endif +#endif + +// Image loader +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) + #if !defined(OLC_IMAGE_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_IMAGE_GDI + #endif + #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + #define OLC_IMAGE_LIBPNG + #endif + #endif +#endif + + +// O------------------------------------------------------------------------------O +// | PLATFORM-SPECIFIC DEPENDENCIES | +// O------------------------------------------------------------------------------O +#if !defined(OLC_PGE_HEADLESS) +#if defined(OLC_PLATFORM_WINAPI) + #define _WINSOCKAPI_ // Thanks Cornchipss + #if !defined(VC_EXTRALEAN) + #define VC_EXTRALEAN + #endif + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + + // In Code::Blocks + #if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif + + #include + #undef _WINSOCKAPI_ +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + #include + } +#endif + +#if defined(OLC_PLATFORM_GLUT) + #if defined(__linux__) + #include + #include + #endif + #if defined(__APPLE__) + #include + #include + #include + #endif +#endif +#endif +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE DECLARATION | +// O------------------------------------------------------------------------------O +#pragma region pge_declaration +namespace olc +{ + class PixelGameEngine; + class Sprite; + + // Pixel Game Engine Advanced Configuration + constexpr uint8_t nMouseButtons = 5; + constexpr uint8_t nDefaultAlpha = 0xFF; + constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); + constexpr uint8_t nTabSizeInSpaces = 4; + enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; + + // O------------------------------------------------------------------------------O + // | olc::Pixel - Represents a 32-Bit RGBA colour | + // O------------------------------------------------------------------------------O + struct Pixel + { + union + { + uint32_t n = nDefaultPixel; + struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; }; + }; + + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); + Pixel(uint32_t p); + Pixel& operator = (const Pixel& v) = default; + bool operator ==(const Pixel& p) const; + bool operator !=(const Pixel& p) const; + Pixel operator * (const float i) const; + Pixel operator / (const float i) const; + Pixel& operator *=(const float i); + Pixel& operator /=(const float i); + Pixel operator + (const Pixel& p) const; + Pixel operator - (const Pixel& p) const; + Pixel& operator +=(const Pixel& p); + Pixel& operator -=(const Pixel& p); + Pixel inv() const; + }; + + Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t); + + + // O------------------------------------------------------------------------------O + // | USEFUL CONSTANTS | + // O------------------------------------------------------------------------------O + static const Pixel + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + WHITE(255, 255, 255), BLACK(0, 0, 0), BLANK(0, 0, 0, 0); + + // Thanks to scripticuk and others for updating the key maps + // NOTE: The GLUT platform will need updating, open to contributions ;) + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + EQUALS, COMMA, MINUS, + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + CAPS_LOCK, ENUM_END + }; + + namespace Mouse + { + static constexpr int32_t LEFT = 0; + static constexpr int32_t RIGHT = 1; + static constexpr int32_t MIDDLE = 2; + }; + + // O------------------------------------------------------------------------------O + // | olc::HWButton - Represents the state of a hardware button (mouse/key/joy) | + // O------------------------------------------------------------------------------O + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + + + + // O------------------------------------------------------------------------------O + // | olc::vX2d - A generic 2D vector type | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_VEC2D) + template + struct v2d_generic + { + T x = 0; + T y = 0; + v2d_generic() : x(0), y(0) {} + v2d_generic(T _x, T _y) : x(_x), y(_y) {} + v2d_generic(const v2d_generic& v) : x(v.x), y(v.y) {} + v2d_generic& operator=(const v2d_generic& v) = default; + T mag() const { return T(std::sqrt(x * x + y * y)); } + T mag2() const { return x * x + y * y; } + v2d_generic norm() const { T r = 1 / mag(); return v2d_generic(x * r, y * r); } + v2d_generic perp() const { return v2d_generic(-y, x); } + v2d_generic floor() const { return v2d_generic(std::floor(x), std::floor(y)); } + v2d_generic ceil() const { return v2d_generic(std::ceil(x), std::ceil(y)); } + v2d_generic max(const v2d_generic& v) const { return v2d_generic(std::max(x, v.x), std::max(y, v.y)); } + 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) }; } + 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); } + v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y); } + v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + v2d_generic operator * (const v2d_generic& rhs) const { return v2d_generic(this->x * rhs.x, this->y * rhs.y); } + v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + v2d_generic operator / (const v2d_generic& rhs) const { return v2d_generic(this->x / rhs.x, this->y / rhs.y); } + v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } + v2d_generic& operator *= (const v2d_generic& rhs) { this->x *= rhs.x; this->y *= rhs.y; return *this; } + v2d_generic& operator /= (const v2d_generic& rhs) { this->x /= rhs.x; this->y /= rhs.y; return *this; } + v2d_generic operator + () const { return { +x, +y }; } + v2d_generic operator - () const { return { -x, -y }; } + bool operator == (const v2d_generic& rhs) const { return (this->x == rhs.x && this->y == rhs.y); } + bool operator != (const v2d_generic& rhs) const { return (this->x != rhs.x || this->y != rhs.y); } + const std::string str() const { return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + ")"; } + friend std::ostream& operator << (std::ostream& os, const v2d_generic& rhs) { os << rhs.str(); return os; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + // Note: joshinils has some good suggestions here, but they are complicated to implement at this moment, + // however they will appear in a future version of PGE + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); } + + // To stop dandistine crying... + template inline bool operator < (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y < rhs.y || (lhs.y == rhs.y && lhs.x < rhs.x); } + template inline bool operator > (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y > rhs.y || (lhs.y == rhs.y && lhs.x > rhs.x); } + + typedef v2d_generic vi2d; + typedef v2d_generic vu2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; +#endif + + + + + + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack - A virtual scrambled filesystem to pack your assets into | + // O------------------------------------------------------------------------------O + struct ResourceBuffer : public std::streambuf + { + ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size); + std::vector vMemory; + }; + + class ResourcePack : public std::streambuf + { + public: + ResourcePack(); + ~ResourcePack(); + bool AddFile(const std::string& sFile); + bool LoadPack(const std::string& sFile, const std::string& sKey); + bool SavePack(const std::string& sFile, const std::string& sKey); + ResourceBuffer GetFileBuffer(const std::string& sFile); + bool Loaded(); + private: + struct sResourceFile { uint32_t nSize; uint32_t nOffset; }; + std::map mapFiles; + std::ifstream baseFile; + std::vector scramble(const std::vector& data, const std::string& key); + std::string makeposix(const std::string& path); + }; + + + class ImageLoader + { + public: + ImageLoader() = default; + virtual ~ImageLoader() = default; + virtual olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) = 0; + virtual olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) = 0; + }; + + + // O------------------------------------------------------------------------------O + // | olc::Sprite - An image represented by a 2D array of olc::Pixel | + // O------------------------------------------------------------------------------O + class Sprite + { + public: + Sprite(); + Sprite(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + Sprite(int32_t w, int32_t h); + Sprite(const olc::Sprite&) = delete; + ~Sprite(); + + public: + olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC, CLAMP }; + enum Flip { NONE = 0, HORIZ = 1, VERT = 2 }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y) const; + bool SetPixel(int32_t x, int32_t y, Pixel p); + Pixel GetPixel(const olc::vi2d& a) const; + bool SetPixel(const olc::vi2d& a, Pixel p); + Pixel Sample(float x, float y) const; + Pixel SampleBL(float u, float v) const; + Pixel* GetData(); + olc::Sprite* Duplicate(); + olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); + std::vector pColData; + Mode modeSample = Mode::NORMAL; + + static std::unique_ptr loader; + }; + + // O------------------------------------------------------------------------------O + // | olc::Decal - A GPU resident storage of an olc::Sprite | + // O------------------------------------------------------------------------------O + class Decal + { + public: + Decal(olc::Sprite* spr, bool filter = false, bool clamp = true); + Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr); + virtual ~Decal(); + void Update(); + void UpdateSprite(); + + public: // But dont touch + int32_t id = -1; + olc::Sprite* sprite = nullptr; + olc::vf2d vUVScale = { 1.0f, 1.0f }; + }; + + enum class DecalMode + { + NORMAL, + ADDITIVE, + MULTIPLICATIVE, + STENCIL, + ILLUMINATE, + WIREFRAME, + MODEL3D, + }; + + enum class DecalStructure + { + LINE, + FAN, + STRIP, + LIST + }; + + // O------------------------------------------------------------------------------O + // | olc::Renderable - Convenience class to keep a sprite and decal together | + // O------------------------------------------------------------------------------O + class Renderable + { + public: + Renderable() = default; + Renderable(Renderable&& r) : pSprite(std::move(r.pSprite)), pDecal(std::move(r.pDecal)) {} + Renderable(const Renderable&) = delete; + olc::rcode Load(const std::string& sFile, ResourcePack* pack = nullptr, bool filter = false, bool clamp = true); + void Create(uint32_t width, uint32_t height, bool filter = false, bool clamp = true); + olc::Decal* Decal() const; + olc::Sprite* Sprite() const; + + private: + std::unique_ptr pSprite = nullptr; + std::unique_ptr pDecal = nullptr; + }; + + + // O------------------------------------------------------------------------------O + // | Auxilliary components internal to engine | + // O------------------------------------------------------------------------------O + + struct DecalInstance + { + olc::Decal* decal = nullptr; + std::vector pos; + std::vector uv; + std::vector w; + std::vector tint; + olc::DecalMode mode = olc::DecalMode::NORMAL; + olc::DecalStructure structure = olc::DecalStructure::FAN; + uint32_t points = 0; + }; + + struct LayerDesc + { + olc::vf2d vOffset = { 0, 0 }; + olc::vf2d vScale = { 1, 1 }; + bool bShow = false; + bool bUpdate = false; + olc::Renderable pDrawTarget; + uint32_t nResID = 0; + std::vector vecDecalInstance; + olc::Pixel tint = olc::WHITE; + std::function funcHook = nullptr; + }; + + class Renderer + { + public: + virtual ~Renderer() = default; + virtual void PrepareDevice() = 0; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) = 0; + virtual olc::rcode DestroyDevice() = 0; + virtual void DisplayFrame() = 0; + virtual void PrepareDrawing() = 0; + virtual void SetDecalMode(const olc::DecalMode& mode) = 0; + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0; + virtual void DrawDecal(const olc::DecalInstance& decal) = 0; + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) = 0; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual uint32_t DeleteTexture(const uint32_t id) = 0; + virtual void ApplyTexture(uint32_t id) = 0; + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) = 0; + virtual void ClearBuffer(olc::Pixel p, bool bDepth) = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class Platform + { + public: + virtual ~Platform() = default; + virtual olc::rcode ApplicationStartUp() = 0; + virtual olc::rcode ApplicationCleanUp() = 0; + virtual olc::rcode ThreadStartUp() = 0; + virtual olc::rcode ThreadCleanUp() = 0; + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) = 0; + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) = 0; + virtual olc::rcode SetWindowTitle(const std::string& s) = 0; + virtual olc::rcode StartSystemEventLoop() = 0; + virtual olc::rcode HandleSystemEvent() = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class PGEX; + + // The Static Twins (plus one) + static std::unique_ptr renderer; + static std::unique_ptr platform; + static std::map mapKeys; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine - The main BASE class for your application | + // O------------------------------------------------------------------------------O + class PixelGameEngine + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + public: + olc::rcode Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, + bool full_screen = false, bool vsync = false, bool cohesion = false); + olc::rcode Start(); + + public: // User Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be one clean coder + virtual bool OnUserDestroy(); + + // Called when a text entry is confirmed with "enter" key + virtual void OnTextEntryComplete(const std::string& sText); + // Called when a console command is executed + virtual bool OnConsoleCommand(const std::string& sCommand); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused() const; + // Get the state of a specific keyboard button + HWButton GetKey(Key k) const; + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b) const; + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX() const; + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY() const; + // Get Mouse Wheel Delta + int32_t GetMouseWheel() const; + // Get the mouse in window space + const olc::vi2d& GetWindowMouse() const; + // Gets the mouse as a vector to keep Tarriest happy + const olc::vi2d& GetMousePos() const; + + static const std::map& GetKeyMap() { return mapKeys; } + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth() const; + // Returns the height of the screen in "pixels" + int32_t ScreenHeight() const; + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth() const; + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight() const; + // Returns the currently active draw target + olc::Sprite* GetDrawTarget() const; + // Resize the primary screen sprite + void SetScreenSize(int w, int h); + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite* target); + // Gets the current Frames Per Second + uint32_t GetFPS() const; + // Gets last update of elapsed time + float GetElapsedTime() const; + // Gets Actual Window size + const olc::vi2d& GetWindowSize() const; + // Gets pixel scale + const olc::vi2d& GetPixelSize() const; + // Gets actual pixel scale + const olc::vi2d& GetScreenPixelSize() const; + + public: // CONFIGURATION ROUTINES + // Layer targeting functions + void SetDrawTarget(uint8_t layer, bool bDirty = true); + void EnableLayer(uint8_t layer, bool b); + void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); + void SetLayerOffset(uint8_t layer, float x, float y); + void SetLayerScale(uint8_t layer, const olc::vf2d& scale); + void SetLayerScale(uint8_t layer, float x, float y); + void SetLayerTint(uint8_t layer, const olc::Pixel& tint); + void SetLayerCustomRenderFunction(uint8_t layer, std::function f); + + std::vector& GetLayers(); + uint32_t CreateLayer(); + + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor from between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + + + + public: // DRAWING ROUTINES + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + bool Draw(const olc::vi2d& pos, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // 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); + // 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); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws a single line of text - traditional monospaced + void DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSize(const std::string& s); + // Draws a single line of text - non-monospaced + void DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSizeProp(const std::string& s); + + // Decal Quad functions + void SetDecalMode(const olc::DecalMode& mode); + void SetDecalStructure(const olc::DecalStructure& structure); + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours + void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); + // Draws a decal with 4 arbitrary points, warping the texture to look "correct" + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + // As above, but you can specify a region of a decal source sprite + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws a decal rotated to specified angle, wit point of rotation offset + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a multiline string as a decal, with tiniting and scaling + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Draws a single shaded filled rectangle as a decal + void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + // Draws a corner shaded rectangle as a decal + void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); + // 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 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); + + // 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 }); + void DrawRotatedStringPropDecal(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 }); + // Clears entire draw target to Pixel + void Clear(Pixel p); + // Clears the rendering back buffer + void ClearBuffer(Pixel p, bool bDepth = true); + // Returns the font image + olc::Sprite* GetFontSprite(); + + // Clip a line segment to visible area + bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2); + + // Dont allow PGE to mark layers as dirty, so pixel graphics don't update + void EnablePixelTransfer(const bool bEnable = true); + + // Command Console Routines + void ConsoleShow(const olc::Key &keyExit, bool bSuspendTime = true); + bool IsConsoleShowing() const; + void ConsoleClear(); + std::stringstream& ConsoleOut(); + void ConsoleCaptureStdOut(const bool bCapture); + + // Text Entry Routines + void TextEntryEnable(const bool bEnable, const std::string& sText = ""); + std::string TextEntryGetString() const; + int32_t TextEntryGetCursor() const; + bool IsTextEntryEnabled() const; + + + + private: + void UpdateTextEntry(); + void UpdateConsole(); + + public: + + // Experimental Lightweight 3D Routines ================ +#ifdef OLC_ENABLE_EXPERIMENTAL + // Set Manual View Matrix + void LW3D_View(const std::array& m); + // Set Manual World Matrix + void LW3D_World(const std::array& m); + // Set Manual Projection Matrix + void LW3D_Projection(const std::array& m); + + // Draws a vector of vertices, interprted as individual triangles + void LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col); + + void LW3D_ModelTranslate(const float x, const float y, const float z); + + // Camera convenience functions + void LW3D_SetCameraAtTarget(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fTargetX, const float fTargetY, const float fTargetZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + void LW3D_SetCameraAlongDirection(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fDirX, const float fDirY, const float fDirZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + + // 3D Rendering Flags + void LW3D_EnableDepthTest(const bool bEnableDepth); + void LW3D_EnableBackfaceCulling(const bool bEnableCull); +#endif + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + olc::Sprite* pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + olc::vi2d vScreenSize = { 256, 240 }; + olc::vf2d vInvScreenSize = { 1.0f / 256.0f, 1.0f / 240.0f }; + olc::vi2d vPixelSize = { 4, 4 }; + olc::vi2d vScreenPixelSize = { 4, 4 }; + olc::vi2d vMousePos = { 0, 0 }; + int32_t nMouseWheelDelta = 0; + olc::vi2d vMousePosCache = { 0, 0 }; + olc::vi2d vMouseWindowPos = { 0, 0 }; + int32_t nMouseWheelDeltaCache = 0; + olc::vi2d vWindowSize = { 0, 0 }; + olc::vi2d vViewPos = { 0, 0 }; + olc::vi2d vViewSize = { 0,0 }; + bool bFullScreen = false; + olc::vf2d vPixel = { 1.0f, 1.0f }; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + float fLastElapsed = 0.0f; + int nFrameCount = 0; + bool bSuspendTextureTransfer = false; + Renderable fontRenderable; + std::vector vLayers; + uint8_t nTargetLayer = 0; + uint32_t nLastFPS = 0; + bool bPixelCohesion = false; + DecalMode nDecalMode = DecalMode::NORMAL; + DecalStructure nDecalStructure = DecalStructure::FAN; + std::function funcPixelMode; + std::chrono::time_point m_tp1, m_tp2; + std::vector vFontSpacing; + + // Command Console Specific + bool bConsoleShow = false; + bool bConsoleSuspendTime = false; + olc::Key keyConsoleExit = olc::Key::F1; + std::stringstream ssConsoleOutput; + std::streambuf* sbufOldCout = nullptr; + olc::vi2d vConsoleSize; + olc::vi2d vConsoleCursor = { 0,0 }; + olc::vf2d vConsoleCharacterScale = { 1.0f, 2.0f }; + std::vector sConsoleLines; + std::list sCommandHistory; + std::list::iterator sCommandHistoryIt; + + // Text Entry Specific + bool bTextEntryEnable = false; + std::string sTextEntryString = ""; + int32_t nTextEntryCursor = 0; + std::vector> vKeyboardMap; + + + + // State of keyboard + bool pKeyNewState[256] = { 0 }; + bool pKeyOldState[256] = { 0 }; + HWButton pKeyboardState[256] = { 0 }; + + // State of mouse + bool pMouseNewState[nMouseButtons] = { 0 }; + bool pMouseOldState[nMouseButtons] = { 0 }; + HWButton pMouseState[nMouseButtons] = { 0 }; + + // The main engine thread + void EngineThread(); + + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + public: + // "Break In" Functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + void olc_ConstructFontSheet(); + void olc_CoreUpdate(); + void olc_PrepareEngine(); + void olc_UpdateMouseState(int32_t button, bool state); + void olc_UpdateKeyState(int32_t key, bool state); + void olc_UpdateMouseFocus(bool state); + void olc_UpdateKeyFocus(bool state); + void olc_Terminate(); + void olc_Reanimate(); + bool olc_IsRunning(); + + // At the very end of this file, chooses which + // components to compile + virtual void olc_ConfigureSystem(); + + // NOTE: Items Here are to be deprecated, I have left them in for now + // in case you are using them, but they will be removed. + // olc::vf2d vSubPixelOffset = { 0.0f, 0.0f }; + + public: // PGEX Stuff + friend class PGEX; + void pgex_Register(olc::PGEX* pgex); + + private: + std::vector vExtensions; + }; + + + + // O------------------------------------------------------------------------------O + // | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension | + // O------------------------------------------------------------------------------O + class PGEX + { + friend class olc::PixelGameEngine; + public: + PGEX(bool bHook = false); + + protected: + virtual void OnBeforeUserCreate(); + virtual void OnAfterUserCreate(); + virtual bool OnBeforeUserUpdate(float &fElapsedTime); + virtual void OnAfterUserUpdate(float fElapsedTime); + + protected: + static PixelGameEngine* pge; + }; +} + +#pragma endregion + +#endif // OLC_PGE_DEF + + +// O------------------------------------------------------------------------------O +// | START OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | +// | Note: The core implementation is platform independent | +// O------------------------------------------------------------------------------O +#pragma region pge_implementation +namespace olc +{ + // O------------------------------------------------------------------------------O + // | olc::Pixel IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Pixel::Pixel() + { r = 0; g = 0; b = 0; a = nDefaultAlpha; } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { n = red | (green << 8) | (blue << 16) | (alpha << 24); } // Thanks jarekpelczar + + Pixel::Pixel(uint32_t p) + { n = p; } + + bool Pixel::operator==(const Pixel& p) const + { return n == p.n; } + + bool Pixel::operator!=(const Pixel& p) const + { return n != p.n; } + + Pixel Pixel::operator * (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) * i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) * i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) * i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel Pixel::operator / (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) / i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) / i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) / i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel& Pixel::operator *=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * i))); + return *this; + } + + Pixel& Pixel::operator /=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) / i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) / i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) / i))); + return *this; + } + + Pixel Pixel::operator + (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel Pixel::operator - (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel& Pixel::operator += (const Pixel& p) + { + this->r = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return *this; + } + + Pixel& Pixel::operator -= (const Pixel& p) // Thanks Au Lit + { + this->r = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return *this; + } + + Pixel Pixel::inv() const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, 255 - int(r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, 255 - int(g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, 255 - int(b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel PixelF(float red, float green, float blue, float alpha) + { return Pixel(uint8_t(red * 255.0f), uint8_t(green * 255.0f), uint8_t(blue * 255.0f), uint8_t(alpha * 255.0f)); } + + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t) + { return (p2 * t) + p1 * (1.0f - t); } + + // O------------------------------------------------------------------------------O + // | olc::Sprite IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Sprite::Sprite() + { width = 0; height = 0; } + + Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack* pack) + { LoadFromFile(sImageFile, pack); } + + Sprite::Sprite(int32_t w, int32_t h) + { + width = w; height = h; + pColData.resize(width * height); + pColData.resize(width * height, nDefaultPixel); + } + + Sprite::~Sprite() + { pColData.clear(); } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { modeSample = mode; } + + Pixel Sprite::GetPixel(const olc::vi2d& a) const + { return GetPixel(a.x, a.y); } + + bool Sprite::SetPixel(const olc::vi2d& a, Pixel p) + { return SetPixel(a.x, a.y, p); } + + Pixel Sprite::GetPixel(int32_t x, int32_t y) const + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y * width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + if (modeSample == olc::Sprite::Mode::PERIODIC) + return pColData[abs(y % height) * width + abs(x % width)]; + else + return pColData[std::max(0, std::min(y, height-1)) * width + std::max(0, std::min(x, width-1))]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y * width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) const + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) const + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() + { return pColData.data(); } + + + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack) + { + UNUSED(pack); + return loader->LoadImageResource(this, sImageFile, pack); + } + + olc::Sprite* Sprite::Duplicate() + { + olc::Sprite* spr = new olc::Sprite(width, height); + std::memcpy(spr->GetData(), GetData(), width * height * sizeof(olc::Pixel)); + spr->modeSample = modeSample; + return spr; + } + + olc::Sprite* Sprite::Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize) + { + olc::Sprite* spr = new olc::Sprite(vSize.x, vSize.y); + for (int y = 0; y < vSize.y; y++) + for (int x = 0; x < vSize.x; x++) + spr->SetPixel(x, y, GetPixel(vPos.x + x, vPos.y + y)); + return spr; + } + + // O------------------------------------------------------------------------------O + // | olc::Decal IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Decal::Decal(olc::Sprite* spr, bool filter, bool clamp) + { + id = -1; + if (spr == nullptr) return; + sprite = spr; + id = renderer->CreateTexture(sprite->width, sprite->height, filter, clamp); + Update(); + } + + Decal::Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr) + { + if (spr == nullptr) return; + id = nExistingTextureResource; + } + + void Decal::Update() + { + if (sprite == nullptr) return; + vUVScale = { 1.0f / float(sprite->width), 1.0f / float(sprite->height) }; + renderer->ApplyTexture(id); + renderer->UpdateTexture(id, sprite); + } + + void Decal::UpdateSprite() + { + if (sprite == nullptr) return; + renderer->ApplyTexture(id); + renderer->ReadTexture(id, sprite); + } + + Decal::~Decal() + { + if (id != -1) + { + renderer->DeleteTexture(id); + id = -1; + } + } + + void Renderable::Create(uint32_t width, uint32_t height, bool filter, bool clamp) + { + pSprite = std::make_unique(width, height); + pDecal = std::make_unique(pSprite.get(), filter, clamp); + } + + olc::rcode Renderable::Load(const std::string& sFile, ResourcePack* pack, bool filter, bool clamp) + { + pSprite = std::make_unique(); + if (pSprite->LoadFromFile(sFile, pack) == olc::rcode::OK) + { + pDecal = std::make_unique(pSprite.get(), filter, clamp); + return olc::rcode::OK; + } + else + { + pSprite.release(); + pSprite = nullptr; + return olc::rcode::NO_FILE; + } + } + + olc::Decal* Renderable::Decal() const + { return pDecal.get(); } + + olc::Sprite* Renderable::Sprite() const + { return pSprite.get(); } + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack IMPLEMENTATION | + // O------------------------------------------------------------------------------O + + + //============================================================= + // Resource Packs - Allows you to store files in one large + // scrambled file - Thanks MaGetzUb for debugging a null char in std::stringstream bug + ResourceBuffer::ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size) + { + vMemory.resize(size); + ifs.seekg(offset); ifs.read(vMemory.data(), vMemory.size()); + setg(vMemory.data(), vMemory.data(), vMemory.data() + size); + } + + ResourcePack::ResourcePack() { } + ResourcePack::~ResourcePack() { baseFile.close(); } + + bool ResourcePack::AddFile(const std::string& sFile) + { + const std::string file = makeposix(sFile); + + if (_gfs::exists(file)) + { + sResourceFile e; + e.nSize = (uint32_t)_gfs::file_size(file); + e.nOffset = 0; // Unknown at this stage + mapFiles[file] = e; + return true; + } + return false; + } + + bool ResourcePack::LoadPack(const std::string& sFile, const std::string& sKey) + { + // Open the resource file + baseFile.open(sFile, std::ifstream::binary); + if (!baseFile.is_open()) return false; + + // 1) Read Scrambled index + uint32_t nIndexSize = 0; + baseFile.read((char*)&nIndexSize, sizeof(uint32_t)); + + std::vector buffer(nIndexSize); + for (uint32_t j = 0; j < nIndexSize; j++) + buffer[j] = baseFile.get(); + + std::vector decoded = scramble(buffer, sKey); + size_t pos = 0; + auto read = [&decoded, &pos](char* dst, size_t size) { + memcpy((void*)dst, (const void*)(decoded.data() + pos), size); + pos += size; + }; + + auto get = [&read]() -> int { char c; read(&c, 1); return c; }; + + // 2) Read Map + uint32_t nMapEntries = 0; + read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = get(); + + sResourceFile e; + read((char*)&e.nSize, sizeof(uint32_t)); + read((char*)&e.nOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // Don't close base file! we will provide a stream + // pointer when the file is requested + return true; + } + + bool ResourcePack::SavePack(const std::string& sFile, const std::string& sKey) + { + // Create/Overwrite the resource file + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return false; + + // Iterate through map + uint32_t nIndexSize = 0; // Unknown for now + ofs.write((char*)&nIndexSize, sizeof(uint32_t)); + uint32_t nMapSize = uint32_t(mapFiles.size()); + ofs.write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(uint32_t)); + ofs.write(e.first.c_str(), nPathSize); + + // Write the file entry properties + ofs.write((char*)&e.second.nSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + + // 2) Write the individual Data + std::streampos offset = ofs.tellp(); + nIndexSize = (uint32_t)offset; + for (auto& e : mapFiles) + { + // Store beginning of file offset within resource pack file + e.second.nOffset = (uint32_t)offset; + + // Load the file to be added + std::vector vBuffer(e.second.nSize); + std::ifstream i(e.first, std::ifstream::binary); + i.read((char*)vBuffer.data(), e.second.nSize); + i.close(); + + // Write the loaded file into resource pack file + ofs.write((char*)vBuffer.data(), e.second.nSize); + offset += e.second.nSize; + } + + // 3) Scramble Index + std::vector stream; + auto write = [&stream](const char* data, size_t size) { + size_t sizeNow = stream.size(); + stream.resize(sizeNow + size); + memcpy(stream.data() + sizeNow, data, size); + }; + + // Iterate through map + write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + write((char*)&nPathSize, sizeof(uint32_t)); + write(e.first.c_str(), nPathSize); + + // Write the file entry properties + write((char*)&e.second.nSize, sizeof(uint32_t)); + write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + std::vector sIndexString = scramble(stream, sKey); + uint32_t nIndexStringLen = uint32_t(sIndexString.size()); + // 4) Rewrite Map (it has been updated with offsets now) + // at start of file + ofs.seekp(0, std::ios::beg); + ofs.write((char*)&nIndexStringLen, sizeof(uint32_t)); + ofs.write(sIndexString.data(), nIndexStringLen); + ofs.close(); + return true; + } + + ResourceBuffer ResourcePack::GetFileBuffer(const std::string& sFile) + { return ResourceBuffer(baseFile, mapFiles[sFile].nOffset, mapFiles[sFile].nSize); } + + bool ResourcePack::Loaded() + { return baseFile.is_open(); } + + std::vector ResourcePack::scramble(const std::vector& data, const std::string& key) + { + if (key.empty()) return data; + std::vector o; + size_t c = 0; + for (auto s : data) o.push_back(s ^ key[(c++) % key.size()]); + return o; + }; + + std::string ResourcePack::makeposix(const std::string& path) + { + std::string o; + for (auto s : path) o += std::string(1, s == '\\' ? '/' : s); + return o; + }; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine IMPLEMENTATION | + // O------------------------------------------------------------------------------O + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + + // Bring in relevant Platform & Rendering systems depending + // on compiler parameters + olc_ConfigureSystem(); + } + + PixelGameEngine::~PixelGameEngine() + {} + + + olc::rcode PixelGameEngine::Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, bool full_screen, bool vsync, bool cohesion) + { + bPixelCohesion = cohesion; + vScreenSize = { screen_w, screen_h }; + vInvScreenSize = { 1.0f / float(screen_w), 1.0f / float(screen_h) }; + vPixelSize = { pixel_w, pixel_h }; + vWindowSize = vScreenSize * vPixelSize; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + vPixel = 2.0f / vScreenSize; + + if (vPixelSize.x <= 0 || vPixelSize.y <= 0 || vScreenSize.x <= 0 || vScreenSize.y <= 0) + return olc::FAIL; + return olc::OK; + } + + + void PixelGameEngine::SetScreenSize(int w, int h) + { + vScreenSize = { w, h }; + vInvScreenSize = { 1.0f / float(w), 1.0f / float(h) }; + for (auto& layer : vLayers) + { + layer.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + layer.bUpdate = true; + } + SetDrawTarget(nullptr); + renderer->ClearBuffer(olc::BLACK, true); + renderer->DisplayFrame(); + renderer->ClearBuffer(olc::BLACK, true); + renderer->UpdateViewport(vViewPos, vViewSize); + } + +#if !defined(PGE_USE_CUSTOM_START) + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + + // Some implementations may form an event loop here + platform->StartSystemEventLoop(); + + // Wait for thread to be exited + t.join(); + + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +#endif + + void PixelGameEngine::SetDrawTarget(Sprite* target) + { + if (target) + { + pDrawTarget = target; + } + else + { + nTargetLayer = 0; + pDrawTarget = vLayers[0].pDrawTarget.Sprite(); + } + } + + void PixelGameEngine::SetDrawTarget(uint8_t layer, bool bDirty) + { + if (layer < vLayers.size()) + { + pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); + vLayers[layer].bUpdate = bDirty; + nTargetLayer = layer; + } + } + + void PixelGameEngine::EnableLayer(uint8_t layer, bool b) + { if (layer < vLayers.size()) vLayers[layer].bShow = b; } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, const olc::vf2d& offset) + { SetLayerOffset(layer, offset.x, offset.y); } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vOffset = { x, y }; } + + void PixelGameEngine::SetLayerScale(uint8_t layer, const olc::vf2d& scale) + { SetLayerScale(layer, scale.x, scale.y); } + + void PixelGameEngine::SetLayerScale(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vScale = { x, y }; } + + void PixelGameEngine::SetLayerTint(uint8_t layer, const olc::Pixel& tint) + { if (layer < vLayers.size()) vLayers[layer].tint = tint; } + + void PixelGameEngine::SetLayerCustomRenderFunction(uint8_t layer, std::function f) + { if (layer < vLayers.size()) vLayers[layer].funcHook = f; } + + std::vector& PixelGameEngine::GetLayers() + { return vLayers; } + + uint32_t PixelGameEngine::CreateLayer() + { + LayerDesc ld; + ld.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + vLayers.push_back(std::move(ld)); + return uint32_t(vLayers.size()) - 1; + } + + Sprite* PixelGameEngine::GetDrawTarget() const + { return pDrawTarget; } + + int32_t PixelGameEngine::GetDrawTargetWidth() const + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() const + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + uint32_t PixelGameEngine::GetFPS() const + { return nLastFPS; } + + bool PixelGameEngine::IsFocused() const + { return bHasInputFocus; } + + HWButton PixelGameEngine::GetKey(Key k) const + { return pKeyboardState[k]; } + + HWButton PixelGameEngine::GetMouse(uint32_t b) const + { return pMouseState[b]; } + + int32_t PixelGameEngine::GetMouseX() const + { return vMousePos.x; } + + int32_t PixelGameEngine::GetMouseY() const + { return vMousePos.y; } + + const olc::vi2d& PixelGameEngine::GetMousePos() const + { return vMousePos; } + + int32_t PixelGameEngine::GetMouseWheel() const + { return nMouseWheelDelta; } + + int32_t PixelGameEngine::ScreenWidth() const + { return vScreenSize.x; } + + int32_t PixelGameEngine::ScreenHeight() const + { return vScreenSize.y; } + + float PixelGameEngine::GetElapsedTime() const + { return fLastElapsed; } + + const olc::vi2d& PixelGameEngine::GetWindowSize() const + { return vWindowSize; } + + const olc::vi2d& PixelGameEngine::GetPixelSize() const + { return vPixelSize; } + + const olc::vi2d& PixelGameEngine::GetScreenPixelSize() const + { return vScreenPixelSize; } + + const olc::vi2d& PixelGameEngine::GetWindowMouse() const + { return vMouseWindowPos; } + + bool PixelGameEngine::Draw(const olc::vi2d& pos, Pixel p) + { return Draw(pos.x, pos.y, p); } + + // This is it, the critical function that plots a pixel + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if (p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b/*, (uint8_t)(p.a * fBlendFactor)*/)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + + void PixelGameEngine::DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p, uint32_t pattern) + { DrawLine(pos1.x, pos1.y, pos2.x, pos2.y, p, pattern); } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) { pattern = (pattern << 1) | (pattern >> 31); return pattern & 1; }; + + olc::vi2d p1(x1, y1), p2(x2, y2); + //if (!ClipLineToScreen(p1, p2)) + // return; + x1 = p1.x; y1 = p1.y; + x2 = p2.x; y2 = p2.y; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x < xe; i++) + { + x = x + 1; + if (px < 0) + px = px + 2 * dy1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y < ye; i++) + { + y = y + 1; + if (py <= 0) + py = py + 2 * dx1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p, uint8_t mask) + { DrawCircle(pos.x, pos.y, radius, p, mask); } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + while (y0 >= x0) // only formulate 1/8 of circle + { + // Draw even octants + if (mask & 0x01) Draw(x + x0, y - y0, p);// Q6 - upper right right + if (mask & 0x04) Draw(x + y0, y + x0, p);// Q4 - lower lower right + if (mask & 0x10) Draw(x - x0, y + y0, p);// Q2 - lower left left + if (mask & 0x40) Draw(x - y0, y - x0, p);// Q0 - upper upper left + if (x0 != 0 && x0 != y0) + { + if (mask & 0x02) Draw(x + y0, y - x0, p);// Q7 - upper upper right + if (mask & 0x08) Draw(x + x0, y + y0, p);// Q5 - lower right right + if (mask & 0x20) Draw(x - y0, y + x0, p);// Q3 - lower lower left + if (mask & 0x80) Draw(x - x0, y - y0, p);// Q1 - upper left left + } + + if (d < 0) + d += 4 * x0++ + 6; + else + d += 4 * (x0++ - y0--) + 10; + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p) + { FillCircle(pos.x, pos.y, radius, p); } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + auto drawline = [&](int sx, int ex, int y) + { + for (int x = sx; x <= ex; x++) + Draw(x, y, p); + }; + + while (y0 >= x0) + { + drawline(x - y0, x + y0, y - x0); + if (x0 > 0) drawline(x - y0, x + y0, y + x0); + + if (d < 0) + d += 4 * x0++ + 6; + else + { + if (x0 != y0) + { + drawline(x - x0, x + x0, y - y0); + drawline(x - x0, x + x0, y + y0); + } + d += 4 * (x0++ - y0--) + 10; + } + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { DrawRect(pos.x, pos.y, size.x, size.y, p); } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x + w, y, p); + DrawLine(x + w, y, x + w, y + h, p); + DrawLine(x + w, y + h, x, y + h, p); + DrawLine(x, y + h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) m[i] = p; + } + + void PixelGameEngine::ClearBuffer(Pixel p, bool bDepth) + { renderer->ClearBuffer(p, bDepth); } + + olc::Sprite* PixelGameEngine::GetFontSprite() + { return fontRenderable.Sprite(); } + + bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2) + { + // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm + static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; + auto Segment = [&vScreenSize = vScreenSize](const olc::vi2d& v) + { + int i = SEG_I; + if (v.x < 0) i |= SEG_L; else if (v.x > vScreenSize.x) i |= SEG_R; + if (v.y < 0) i |= SEG_B; else if (v.y > vScreenSize.y) i |= SEG_T; + return i; + }; + + int s1 = Segment(in_p1), s2 = Segment(in_p2); + + while (true) + { + if (!(s1 | s2)) return true; + else if (s1 & s2) return false; + else + { + int s3 = s2 > s1 ? s2 : s1; + olc::vi2d n; + if (s3 & SEG_T) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (vScreenSize.y - in_p1.y) / (in_p2.y - in_p1.y); n.y = vScreenSize.y; } + else if (s3 & SEG_B) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (0 - in_p1.y) / (in_p2.y - in_p1.y); n.y = 0; } + else if (s3 & SEG_R) { n.x = vScreenSize.x; n.y = in_p1.y + (in_p2.y - in_p1.y) * (vScreenSize.x - in_p1.x) / (in_p2.x - in_p1.x); } + else if (s3 & SEG_L) { n.x = 0; n.y = in_p1.y + (in_p2.y - in_p1.y) * (0 - in_p1.x) / (in_p2.x - in_p1.x); } + if (s3 == s1) { in_p1 = n; s1 = Segment(in_p1); } + else { in_p2 = n; s2 = Segment(in_p2); } + } + } + return true; + } + + void PixelGameEngine::EnablePixelTransfer(const bool bEnable) + { + bSuspendTextureTransfer = !bEnable; + } + + + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { FillRect(pos.x, pos.y, size.x, size.y, p); } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)GetDrawTargetWidth()) x = (int32_t)GetDrawTargetWidth(); + if (y < 0) y = 0; + if (y >= (int32_t)GetDrawTargetHeight()) y = (int32_t)GetDrawTargetHeight(); + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)GetDrawTargetWidth()) x2 = (int32_t)GetDrawTargetWidth(); + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)GetDrawTargetHeight()) y2 = (int32_t)GetDrawTargetHeight(); + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { DrawTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + void PixelGameEngine::FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { FillTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1 > y2) { std::swap(y1, y2); std::swap(x1, x2); } + if (y1 > y3) { std::swap(y1, y3); std::swap(x1, x3); } + if (y2 > y3) { std::swap(y2, y3); std::swap(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); + if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); + if (dx2 < 0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { std::swap(dx1, dy1); changed1 = true; } + if (dy2 > dx2) { std::swap(dy2, dx2); changed2 = true; } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + i++; + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y == y2) break; + } + next: + // Second half + dx1 = (int)(x3 - x2); if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y3 - y2); + t1x = x2; + + if (dy1 > dx1) { // swap values + std::swap(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i < dx1) i++; + } + next3: + // process second line until y value is about to change + while (t2x != x3) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y > y3) return; + } + } + + void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip) + { DrawSprite(pos.x, pos.y, sprite, scale, flip); } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = sprite->width - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = sprite->height - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx, fy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx, fy)); + } + } + } + + void PixelGameEngine::DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale, uint8_t flip) + { DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip); } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = w - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = h - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx + ox, fy + oy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx + ox, fy + oy)); + } + } + } + + void PixelGameEngine::SetDecalMode(const olc::DecalMode& mode) + { nDecalMode = mode; } + + void PixelGameEngine::SetDecalStructure(const olc::DecalStructure& structure) + { nDecalStructure = structure; } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + -((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + + olc::vf2d vScreenSpaceDim = + { + ((pos.x + source_size.x * scale.x) * vInvScreenSize.x) * 2.0f - 1.0f, + -(((pos.y + source_size.y * scale.y) * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + olc::vf2d vWindow = olc::vf2d(vViewSize); + olc::vf2d vQuantisedPos = ((vScreenSpacePos * vWindow) + olc::vf2d(0.5f, 0.5f)).floor() / vWindow; + olc::vf2d vQuantisedDim = ((vScreenSpaceDim * vWindow) + olc::vf2d(0.5f, -0.5f)).ceil() / vWindow; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vQuantisedPos.x, vQuantisedPos.y }, { vQuantisedPos.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedPos.y } }; + olc::vf2d uvtl = (source_pos + olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + olc::vf2d uvbr = (source_pos + source_size - olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * size.x * vInvScreenSize.x), + vScreenSpacePos.y - (2.0f * size.y * vInvScreenSize.y) + }; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + olc::vf2d uvtl = (source_pos) * decal->vUVScale; + olc::vf2d uvbr = uvtl + ((source_size) * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * (float(decal->sprite->width) * vInvScreenSize.x)) * scale.x, + vScreenSpacePos.y - (2.0f * (float(decal->sprite->height) * vInvScreenSize.y)) * scale.y + }; + + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(elements); + di.uv.resize(elements); + di.w.resize(elements); + di.tint.resize(elements); + di.points = elements; + for (uint32_t i = 0; i < elements; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = col[i]; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector &tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint[i]; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + +#ifdef OLC_ENABLE_EXPERIMENTAL + // Lightweight 3D + void PixelGameEngine::LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { pos[i][0], pos[i][1] }; + di.w[i] = pos[i][2]; + di.uv[i] = tex[i]; + di.tint[i] = col[i]; + } + di.mode = DecalMode::MODEL3D; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } +#endif + + void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) + { + DecalInstance di; + di.decal = nullptr; + di.points = uint32_t(2); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + di.pos[0] = { (pos1.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos1.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[0] = { 0.0f, 0.0f }; + di.tint[0] = p; + di.w[0] = 1.0f; + di.pos[1] = { (pos2.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos2.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[1] = { 0.0f, 0.0f }; + di.tint[1] = p; + di.w[1] = 1.0f; + di.mode = olc::DecalMode::WIREFRAME; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + olc::vf2d vNewSize = (size - olc::vf2d(0.375f, 0.375f)).ceil(); + std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) + { + std::array points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {colTL, colBL, colBR, colTR} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.tint = { tint, tint, tint, tint }; + di.points = 4; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, float(decal->sprite->height)) - center) * scale; + di.pos[2] = (olc::vf2d(float(decal->sprite->width), float(decal->sprite->height)) - center) * scale; + di.pos[3] = (olc::vf2d(float(decal->sprite->width), 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + di.w[i] = 1; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, source_size.y) - center) * scale; + di.pos[2] = (olc::vf2d(source_size.x, source_size.y) - center) * scale; + di.pos[3] = (olc::vf2d(source_size.x, 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + } + + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) + { + // Thanks Nathan Reed, a brilliant article explaining whats going on here + // http://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/ + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { DrawWarpedDecal(decal, pos.data(), tint); } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { DrawWarpedDecal(decal, &pos[0], tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); } + + void PixelGameEngine::DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), {float(ox) * 8.0f, float(oy) * 8.0f}, {8.0f, 8.0f}, scale, col); + spos.x += 8.0f * scale.x; + } + } + } + + void PixelGameEngine::DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x += float(vFontSpacing[c - 32].y) * scale.x; + } + } + } + // Thanks Oso-Grande/Sopadeoso For these awesom and stupidly clever Text Rotation routines... duh XD + void PixelGameEngine::DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x -= 8.0f; + } + } + } + + void PixelGameEngine::DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x -= float(vFontSpacing[c - 32].y); + } + } + } + + olc::vi2d PixelGameEngine::GetTextSize(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y++; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces; } + else pos.x++; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + return size * 8; + } + + void PixelGameEngine::DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawString(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + // Thanks @tucna, spotted bug with col.ALPHA :P + if (m != Pixel::CUSTOM) // Thanks @Megarev, required for "shaders" + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + olc::vi2d PixelGameEngine::GetTextSizeProp(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y += 1; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces * 8; } + else pos.x += vFontSpacing[c - 32].y; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + + size.y *= 8; + return size; + } + + void PixelGameEngine::DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawStringProp(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + + if (m != Pixel::CUSTOM) + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + for (int32_t is = 0; is < int(scale); is++) + for (int32_t js = 0; js < int(scale); js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += vFontSpacing[c - 32].y * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { nPixelMode = m; } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { return nPixelMode; } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + std::stringstream& PixelGameEngine::ConsoleOut() + { return ssConsoleOutput; } + + bool PixelGameEngine::IsConsoleShowing() const + { return bConsoleShow; } + + void PixelGameEngine::ConsoleShow(const olc::Key& keyExit, bool bSuspendTime) + { + if (bConsoleShow) + return; + + bConsoleShow = true; + bConsoleSuspendTime = bSuspendTime; + TextEntryEnable(true); + keyConsoleExit = keyExit; + pKeyboardState[keyConsoleExit].bHeld = false; + pKeyboardState[keyConsoleExit].bPressed = false; + pKeyboardState[keyConsoleExit].bReleased = true; + } + + void PixelGameEngine::ConsoleClear() + { sConsoleLines.clear(); } + + void PixelGameEngine::ConsoleCaptureStdOut(const bool bCapture) + { + if(bCapture) + sbufOldCout = std::cout.rdbuf(ssConsoleOutput.rdbuf()); + else + std::cout.rdbuf(sbufOldCout); + } + + void PixelGameEngine::UpdateConsole() + { + if (GetKey(keyConsoleExit).bPressed) + { + TextEntryEnable(false); + bConsoleSuspendTime = false; + bConsoleShow = false; + return; + } + + // Keep Console sizes based in real screen dimensions + vConsoleCharacterScale = olc::vf2d(1.0f, 2.0f) / (olc::vf2d(vViewSize) * vInvScreenSize); + vConsoleSize = (vViewSize / olc::vi2d(8, 16)) - olc::vi2d(2, 4); + + // If console has changed size, simply reset it + if (vConsoleSize.y != sConsoleLines.size()) + { + vConsoleCursor = { 0,0 }; + sConsoleLines.clear(); + sConsoleLines.resize(vConsoleSize.y); + } + + auto TypeCharacter = [&](const char c) + { + if (c >= 32 && c < 127) + { + sConsoleLines[vConsoleCursor.y].append(1, c); + vConsoleCursor.x++; + } + + if( c == '\n' || vConsoleCursor.x >= vConsoleSize.x) + { + vConsoleCursor.y++; vConsoleCursor.x = 0; + } + + if (vConsoleCursor.y >= vConsoleSize.y) + { + vConsoleCursor.y = vConsoleSize.y - 1; + for (size_t i = 1; i < vConsoleSize.y; i++) + sConsoleLines[i - 1] = sConsoleLines[i]; + sConsoleLines[vConsoleCursor.y].clear(); + } + }; + + // Empty out "std::cout", parsing as we go + while (ssConsoleOutput.rdbuf()->sgetc() != -1) + { + char c = ssConsoleOutput.rdbuf()->sbumpc(); + TypeCharacter(c); + } + + // Draw Shadow + GradientFillRectDecal({ 0,0 }, olc::vf2d(vScreenSize), olc::PixelF(0, 0, 0.5f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f)); + + // Draw the console buffer + SetDecalMode(olc::DecalMode::NORMAL); + for (int32_t nLine = 0; nLine < vConsoleSize.y; nLine++) + DrawStringDecal(olc::vf2d( 1, 1 + float(nLine) ) * vConsoleCharacterScale * 8.0f, sConsoleLines[nLine], olc::WHITE, vConsoleCharacterScale); + + // Draw Input State + FillRectDecal(olc::vf2d(1 + float((TextEntryGetCursor() + 1)), 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, olc::vf2d(8, 8) * vConsoleCharacterScale, olc::DARK_CYAN); + DrawStringDecal(olc::vf2d(1, 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, std::string(">") + TextEntryGetString(), olc::YELLOW, vConsoleCharacterScale); + } + + + + void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) + { + if (bEnable) + { + nTextEntryCursor = int32_t(sText.size()); + sTextEntryString = sText; + bTextEntryEnable = true; + } + else + { + bTextEntryEnable = false; + } + } + + std::string PixelGameEngine::TextEntryGetString() const + { return sTextEntryString; } + + int32_t PixelGameEngine::TextEntryGetCursor() const + { return nTextEntryCursor; } + + bool PixelGameEngine::IsTextEntryEnabled() const + { return bTextEntryEnable; } + + + void PixelGameEngine::UpdateTextEntry() + { + // Check for typed characters + for (const auto& key : vKeyboardMap) + if (GetKey(std::get<0>(key)).bPressed) + { + sTextEntryString.insert(nTextEntryCursor, GetKey(olc::Key::SHIFT).bHeld ? std::get<2>(key) : std::get<1>(key)); + nTextEntryCursor++; + } + + // Check for command characters + if (GetKey(olc::Key::LEFT).bPressed) + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + if (GetKey(olc::Key::RIGHT).bPressed) + nTextEntryCursor = std::min(int32_t(sTextEntryString.size()), nTextEntryCursor + 1); + if (GetKey(olc::Key::BACK).bPressed && nTextEntryCursor > 0) + { + sTextEntryString.erase(nTextEntryCursor-1, 1); + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + } + if (GetKey(olc::Key::DEL).bPressed && nTextEntryCursor < sTextEntryString.size()) + sTextEntryString.erase(nTextEntryCursor, 1); + + if (GetKey(olc::Key::UP).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.begin()) + sCommandHistoryIt--; + + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + } + + if (GetKey(olc::Key::DOWN).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.end()) + { + sCommandHistoryIt++; + if (sCommandHistoryIt != sCommandHistory.end()) + { + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + else + { + nTextEntryCursor = 0; + sTextEntryString = ""; + } + } + } + } + + if (GetKey(olc::Key::ENTER).bPressed) + { + if (bConsoleShow) + { + std::cout << ">" + sTextEntryString + "\n"; + if (OnConsoleCommand(sTextEntryString)) + { + sCommandHistory.push_back(sTextEntryString); + sCommandHistoryIt = sCommandHistory.end(); + } + sTextEntryString.clear(); + nTextEntryCursor = 0; + } + else + { + OnTextEntryComplete(sTextEntryString); + TextEntryEnable(false); + } + } + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + + bool PixelGameEngine::OnUserCreate() + { return false; } + + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { UNUSED(fElapsedTime); return false; } + + bool PixelGameEngine::OnUserDestroy() + { return true; } + + void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } + bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } + + + // Externalised API + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = vScreenSize.x * vPixelSize.x; + int32_t wh = vScreenSize.y * vPixelSize.y; + float wasp = (float)ww / (float)wh; + + if (bPixelCohesion) + { + vScreenPixelSize = (vWindowSize / vScreenSize); + vViewSize = (vWindowSize / vScreenSize) * vScreenSize; + } + else + { + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / wasp); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * wasp); + } + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + vWindowSize = { x, y }; + olc_UpdateViewport(); + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { nMouseWheelDeltaCache += delta; } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + bHasMouseFocus = true; + vMouseWindowPos = { x, y }; + // Full Screen mode may have a weird viewport we must clamp to + x -= vViewPos.x; + y -= vViewPos.y; + vMousePosCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vMousePosCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vMousePosCache.x >= (int32_t)vScreenSize.x) vMousePosCache.x = vScreenSize.x - 1; + if (vMousePosCache.y >= (int32_t)vScreenSize.y) vMousePosCache.y = vScreenSize.y - 1; + if (vMousePosCache.x < 0) vMousePosCache.x = 0; + if (vMousePosCache.y < 0) vMousePosCache.y = 0; + } + + void PixelGameEngine::olc_UpdateMouseState(int32_t button, bool state) + { pMouseNewState[button] = state; } + + void PixelGameEngine::olc_UpdateKeyState(int32_t key, bool state) + { pKeyNewState[key] = state; } + + void PixelGameEngine::olc_UpdateMouseFocus(bool state) + { bHasMouseFocus = state; } + + void PixelGameEngine::olc_UpdateKeyFocus(bool state) + { bHasInputFocus = state; } + + void PixelGameEngine::olc_Reanimate() + { bAtomActive = true; } + + bool PixelGameEngine::olc_IsRunning() + { return bAtomActive; } + + void PixelGameEngine::olc_Terminate() + { bAtomActive = false; } + + void PixelGameEngine::EngineThread() + { + // Allow platform to do stuff here if needed, since its now in the + // context of this thread + if (platform->ThreadStartUp() == olc::FAIL) return; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) { olc_CoreUpdate(); } + + // Allow the user to free resources if they have overrided the destroy function + if (!OnUserDestroy()) + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + + platform->ThreadCleanUp(); + } + + void PixelGameEngine::olc_PrepareEngine() + { + // Start OpenGL, the context is owned by the game thread + if (platform->CreateGraphics(bFullScreen, bEnableVSYNC, vViewPos, vViewSize) == olc::FAIL) return; + + // Construct default font sheet + olc_ConstructFontSheet(); + + // Create Primary Layer "0" + CreateLayer(); + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDrawTarget(nullptr); + + m_tp1 = std::chrono::system_clock::now(); + m_tp2 = std::chrono::system_clock::now(); + } + + + void PixelGameEngine::olc_CoreUpdate() + { + // Handle Timing + m_tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = m_tp2 - m_tp1; + m_tp1 = m_tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + fLastElapsed = fElapsedTime; + + if (bConsoleSuspendTime) + fElapsedTime = 0.0f; + + // Some platforms will need to check for events + platform->HandleSystemEvent(); + + // Compare hardware input states from previous frame + auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) + { + for (uint32_t i = 0; i < nKeyCount; i++) + { + pKeys[i].bPressed = false; + pKeys[i].bReleased = false; + if (pStateNew[i] != pStateOld[i]) + { + if (pStateNew[i]) + { + pKeys[i].bPressed = !pKeys[i].bHeld; + pKeys[i].bHeld = true; + } + else + { + pKeys[i].bReleased = true; + pKeys[i].bHeld = false; + } + } + pStateOld[i] = pStateNew[i]; + } + }; + + ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); + ScanHardware(pMouseState, pMouseOldState, pMouseNewState, nMouseButtons); + + // Cache mouse coordinates so they remain consistent during frame + vMousePos = vMousePosCache; + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + + if (bTextEntryEnable) + { + UpdateTextEntry(); + } + + // Handle Frame Update + 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); + + if (bConsoleShow) + { + SetDrawTarget((uint8_t)0); + UpdateConsole(); + } + + // Display Frame + renderer->UpdateViewport(vViewPos, vViewSize); + renderer->ClearBuffer(olc::BLACK, true); + + // Layer 0 must always exist + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDecalMode(DecalMode::NORMAL); + renderer->PrepareDrawing(); + + for (auto layer = vLayers.rbegin(); layer != vLayers.rend(); ++layer) + { + if (layer->bShow) + { + if (layer->funcHook == nullptr) + { + renderer->ApplyTexture(layer->pDrawTarget.Decal()->id); + if (!bSuspendTextureTransfer && layer->bUpdate) + { + layer->pDrawTarget.Decal()->Update(); + layer->bUpdate = false; + } + + renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); + + // Display Decals in order for this layer + for (auto& decal : layer->vecDecalInstance) + renderer->DrawDecal(decal); + layer->vecDecalInstance.clear(); + } + else + { + // Mwa ha ha.... Have Fun!!! + layer->funcHook(); + } + } + } + + + + // Present Graphics to screen + renderer->DisplayFrame(); + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + nLastFPS = nFrameCount; + fFrameTimer -= 1.0f; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); + platform->SetWindowTitle(sTitle); + nFrameCount = 0; + } + } + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + + fontRenderable.Decal()->Update(); + + constexpr std::array vSpacing = { { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00, } }; + + for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); + + // UK Standard Layout +#ifdef OLC_KEYBOARD_UK + vKeyboardMap = + { + {olc::Key::A, "a", "A"}, {olc::Key::B, "b", "B"}, {olc::Key::C, "c", "C"}, {olc::Key::D, "d", "D"}, {olc::Key::E, "e", "E"}, + {olc::Key::F, "f", "F"}, {olc::Key::G, "g", "G"}, {olc::Key::H, "h", "H"}, {olc::Key::I, "i", "I"}, {olc::Key::J, "j", "J"}, + {olc::Key::K, "k", "K"}, {olc::Key::L, "l", "L"}, {olc::Key::M, "m", "M"}, {olc::Key::N, "n", "N"}, {olc::Key::O, "o", "O"}, + {olc::Key::P, "p", "P"}, {olc::Key::Q, "q", "Q"}, {olc::Key::R, "r", "R"}, {olc::Key::S, "s", "S"}, {olc::Key::T, "t", "T"}, + {olc::Key::U, "u", "U"}, {olc::Key::V, "v", "V"}, {olc::Key::W, "w", "W"}, {olc::Key::X, "x", "X"}, {olc::Key::Y, "y", "Y"}, + {olc::Key::Z, "z", "Z"}, + + {olc::Key::K0, "0", ")"}, {olc::Key::K1, "1", "!"}, {olc::Key::K2, "2", "\""}, {olc::Key::K3, "3", "#"}, {olc::Key::K4, "4", "$"}, + {olc::Key::K5, "5", "%"}, {olc::Key::K6, "6", "^"}, {olc::Key::K7, "7", "&"}, {olc::Key::K8, "8", "*"}, {olc::Key::K9, "9", "("}, + + {olc::Key::NP0, "0", "0"}, {olc::Key::NP1, "1", "1"}, {olc::Key::NP2, "2", "2"}, {olc::Key::NP3, "3", "3"}, {olc::Key::NP4, "4", "4"}, + {olc::Key::NP5, "5", "5"}, {olc::Key::NP6, "6", "6"}, {olc::Key::NP7, "7", "7"}, {olc::Key::NP8, "8", "8"}, {olc::Key::NP9, "9", "9"}, + {olc::Key::NP_MUL, "*", "*"}, {olc::Key::NP_DIV, "/", "/"}, {olc::Key::NP_ADD, "+", "+"}, {olc::Key::NP_SUB, "-", "-"}, {olc::Key::NP_DECIMAL, ".", "."}, + + {olc::Key::PERIOD, ".", ">"}, {olc::Key::EQUALS, "=", "+"}, {olc::Key::COMMA, ",", "<"}, {olc::Key::MINUS, "-", "_"}, {olc::Key::SPACE, " ", " "}, + + {olc::Key::OEM_1, ";", ":"}, {olc::Key::OEM_2, "/", "?"}, {olc::Key::OEM_3, "\'", "@"}, {olc::Key::OEM_4, "[", "{"}, + {olc::Key::OEM_5, "\\", "|"}, {olc::Key::OEM_6, "]", "}"}, {olc::Key::OEM_7, "#", "~"}, + + // {olc::Key::TAB, "\t", "\t"} + }; +#endif + } + + void PixelGameEngine::pgex_Register(olc::PGEX* pgex) + { + if (std::find(vExtensions.begin(), vExtensions.end(), pgex) == vExtensions.end()) + vExtensions.push_back(pgex); + } + + + PGEX::PGEX(bool bHook) { if(bHook) pge->pgex_Register(this); } + void PGEX::OnBeforeUserCreate() {} + void PGEX::OnAfterUserCreate() {} + bool PGEX::OnBeforeUserUpdate(float& fElapsedTime) { return false; } + void PGEX::OnAfterUserUpdate(float fElapsedTime) {} + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; + olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr; + olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr; + std::unique_ptr olc::Sprite::loader = nullptr; +}; +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Renderers - the draw-y bits | +// O------------------------------------------------------------------------------O + +#if !defined(OLC_PGE_HEADLESS) + +#pragma region renderer_ogl10 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL10) + +#if defined(OLC_PLATFORM_WINAPI) + #include + #include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t* wglSwapInterval = nullptr; + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + #include +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + } + typedef int(glSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + static glSwapInterval_t* glSwapIntervalEXT; + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; +#endif + +#if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include +#endif + +namespace olc +{ + class Renderer_OGL10 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; +#endif + + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo + olc::DecalStructure nDecalStructure = olc::DecalStructure(-1); +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bVSYNC) wglSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + if (glSwapIntervalEXT == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (glSwapIntervalEXT != nullptr && !bVSYNC) + glSwapIntervalEXT(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + } + + void PrepareDrawing() override + { + + //ClearBuffer(olc::GREEN, true); + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + nDecalStructure = DecalStructure::FAN; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void SetDecalMode(const olc::DecalMode& mode) + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: + case olc::DecalMode::MODEL3D: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::ADDITIVE: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case olc::DecalMode::MULTIPLICATIVE: + glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::STENCIL: + glBlendFunc(GL_ZERO, GL_SRC_ALPHA); + break; + case olc::DecalMode::ILLUMINATE: + glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); + break; + case olc::DecalMode::WIREFRAME: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + glBegin(GL_QUADS); + glColor4ub(tint.r, tint.g, tint.b, tint.a); + glTexCoord2f(0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glEnd(); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, 0); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + if (nDecalMode == DecalMode::MODEL3D) + { +#ifdef OLC_ENABLE_EXPERIMENTAL + glMatrixMode(GL_PROJECTION); glPushMatrix(); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-1.0f, 1.0f, -1.0f, 1.0f, 1, 1000); + + #pragma comment (lib, "winmm.lib") + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(0, -40, -200); + glRotatef(float(clock()) * 0.1f, 1, 0, 0); + glRotatef(float(clock()) * 0.1f * 2, 0, 1, 0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_TRIANGLES); + + + // Render as 3D Spatial Entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord2f(decal.uv[n].x, decal.uv[n].y); + glVertex3f(decal.pos[n].x, decal.pos[n].y, decal.w[n]); + } + + glEnd(); + + glMatrixMode(GL_PROJECTION); glPopMatrix(); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); + glDisable(GL_DEPTH_TEST); +#endif + } + else + { + if (nDecalMode == DecalMode::WIREFRAME) + glBegin(GL_LINE_LOOP); + else + { + if(decal.structure == olc::DecalStructure::FAN) + glBegin(GL_TRIANGLE_FAN); + else if(decal.structure == olc::DecalStructure::STRIP) + glBegin(GL_TRIANGLE_STRIP); + else if(decal.structure == olc::DecalStructure::LIST) + glBegin(GL_TRIANGLES); + } + + // Render as 2D Spatial entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord4f(decal.uv[n].x, decal.uv[n].y, 0.0f, decal.w[n]); + glVertex2f(decal.pos[n].x, decal.pos[n].y); + } + + glEnd(); + } + + + //glDisable(GL_DEPTH_TEST); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region renderer_ogl33 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL33) + +#if defined(OLC_PLATFORM_WINAPI) + #include + #include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + typedef void __stdcall locSwapInterval_t(GLsizei n); + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; + #define CALLSTYLE __stdcall + #define OGL_LOAD(t, n) (t*)wglGetProcAddress(#n) +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + #include +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + } + 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 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 + +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); + + constexpr size_t OLC_MAX_VERTS = 128; + + class Renderer_OGL33 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLDisplay olc_Display; + EGLConfig olc_Config; + EGLContext olc_Context; + EGLSurface olc_Surface; +#endif + +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; + #endif +#endif + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + private: + locCreateShader_t* locCreateShader = nullptr; + locShaderSource_t* locShaderSource = nullptr; + locCompileShader_t* locCompileShader = nullptr; + locDeleteShader_t* locDeleteShader = nullptr; + locCreateProgram_t* locCreateProgram = nullptr; + locDeleteProgram_t* locDeleteProgram = nullptr; + locLinkProgram_t* locLinkProgram = nullptr; + locAttachShader_t* locAttachShader = nullptr; + locBindBuffer_t* locBindBuffer = nullptr; + locBufferData_t* locBufferData = nullptr; + locGenBuffers_t* locGenBuffers = nullptr; + locVertexAttribPointer_t* locVertexAttribPointer = nullptr; + locEnableVertexAttribArray_t* locEnableVertexAttribArray = nullptr; + locUseProgram_t* locUseProgram = nullptr; + locBindVertexArray_t* locBindVertexArray = nullptr; + locGenVertexArrays_t* locGenVertexArrays = nullptr; + locSwapInterval_t* locSwapInterval = nullptr; + locGetShaderInfoLog_t* locGetShaderInfoLog = nullptr; + + uint32_t m_nFS = 0; + uint32_t m_nVS = 0; + uint32_t m_nQuadShader = 0; + uint32_t m_vbQuad = 0; + uint32_t m_vaQuad = 0; + + struct locVertex + { + float pos[3]; + olc::vf2d tex; + olc::Pixel col; + }; + + locVertex pVertexMem[OLC_MAX_VERTS]; + + olc::Renderable rendBlankQuad; + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { + // Create OpenGL Context +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Set Vertical Sync + locSwapInterval = OGL_LOAD(locSwapInterval_t, "wglSwapIntervalEXT"); + if (locSwapInterval && !bVSYNC) locSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + locSwapInterval = OGL_LOAD(locSwapInterval_t, "glXSwapIntervalEXT"); + + if (locSwapInterval == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (locSwapInterval != nullptr && !bVSYNC) + locSwapInterval(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLint const attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; + EGLint const context_config[] = { EGL_CONTEXT_CLIENT_VERSION , 2, EGL_NONE }; + EGLint num_config; + + olc_Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(olc_Display, nullptr, nullptr); + eglChooseConfig(olc_Display, attribute_list, &olc_Config, 1, &num_config); + + /* create an EGL rendering context */ + olc_Context = eglCreateContext(olc_Display, olc_Config, EGL_NO_CONTEXT, context_config); + olc_Surface = eglCreateWindowSurface(olc_Display, olc_Config, NULL, nullptr); + eglMakeCurrent(olc_Display, olc_Surface, olc_Surface, olc_Context); + //eglSwapInterval is currently a NOP, plement anyways in case it becomes supported + locSwapInterval = &eglSwapInterval; + locSwapInterval(olc_Display, bVSYNC ? 1 : 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + #endif +#endif + // Load External OpenGL Functions + locCreateShader = OGL_LOAD(locCreateShader_t, glCreateShader); + locCompileShader = OGL_LOAD(locCompileShader_t, glCompileShader); + locShaderSource = OGL_LOAD(locShaderSource_t, glShaderSource); + locDeleteShader = OGL_LOAD(locDeleteShader_t, glDeleteShader); + locCreateProgram = OGL_LOAD(locCreateProgram_t, glCreateProgram); + locDeleteProgram = OGL_LOAD(locDeleteProgram_t, glDeleteProgram); + locLinkProgram = OGL_LOAD(locLinkProgram_t, glLinkProgram); + locAttachShader = OGL_LOAD(locAttachShader_t, glAttachShader); + locBindBuffer = OGL_LOAD(locBindBuffer_t, glBindBuffer); + locBufferData = OGL_LOAD(locBufferData_t, glBufferData); + locGenBuffers = OGL_LOAD(locGenBuffers_t, glGenBuffers); + locVertexAttribPointer = OGL_LOAD(locVertexAttribPointer_t, glVertexAttribPointer); + locEnableVertexAttribArray = OGL_LOAD(locEnableVertexAttribArray_t, glEnableVertexAttribArray); + locUseProgram = OGL_LOAD(locUseProgram_t, glUseProgram); + locGetShaderInfoLog = OGL_LOAD(locGetShaderInfoLog_t, glGetShaderInfoLog); +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + locBindVertexArray = OGL_LOAD(locBindVertexArray_t, glBindVertexArray); + locGenVertexArrays = OGL_LOAD(locGenVertexArrays_t, glGenVertexArrays); +#else + locBindVertexArray = glBindVertexArrayOES; + locGenVertexArrays = glGenVertexArraysOES; +#endif + + // Load & Compile Quad Shader - assumes no errors + m_nFS = locCreateShader(0x8B30); + const GLchar* strFS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "out vec4 pixel;\n""in vec2 oTex;\n" + "in vec4 oCol;\n""uniform sampler2D sprTex;\n""void main(){pixel = texture(sprTex, oTex) * oCol;}"; + locShaderSource(m_nFS, 1, &strFS, NULL); + locCompileShader(m_nFS); + + m_nVS = locCreateShader(0x8B31); + const GLchar* strVS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "layout(location = 0) in vec3 aPos;\n""layout(location = 1) in vec2 aTex;\n" + "layout(location = 2) in vec4 aCol;\n""out vec2 oTex;\n""out vec4 oCol;\n" + "void main(){ float p = 1.0 / aPos.z; gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0); oTex = p * aTex; oCol = aCol;}"; + locShaderSource(m_nVS, 1, &strVS, NULL); + locCompileShader(m_nVS); + + m_nQuadShader = locCreateProgram(); + locAttachShader(m_nQuadShader, m_nFS); + locAttachShader(m_nQuadShader, m_nVS); + locLinkProgram(m_nQuadShader); + + // Create Quad + locGenBuffers(1, &m_vbQuad); + locGenVertexArrays(1, &m_vaQuad); + locBindVertexArray(m_vaQuad); + locBindBuffer(0x8892, m_vbQuad); + + locVertex verts[OLC_MAX_VERTS]; + locBufferData(0x8892, sizeof(locVertex) * OLC_MAX_VERTS, verts, 0x88E0); + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); + locBindBuffer(0x8892, 0); + locBindVertexArray(0); + + // Create blank texture for spriteless decals + rendBlankQuad.Create(1, 1); + rendBlankQuad.Sprite()->GetData()[0] = olc::WHITE; + rendBlankQuad.Decal()->Update(); + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglMakeCurrent(olc_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(olc_Display, olc_Context); + eglDestroySurface(olc_Display, olc_Surface); + eglTerminate(olc_Display); + olc_Display = EGL_NO_DISPLAY; + olc_Surface = EGL_NO_SURFACE; + olc_Context = EGL_NO_CONTEXT; +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglSwapBuffers(olc_Display, olc_Surface); +#endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + locUseProgram(m_nQuadShader); + locBindVertexArray(m_vaQuad); + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); +#endif + } + + void SetDecalMode(const olc::DecalMode& mode) override + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; + case olc::DecalMode::MULTIPLICATIVE: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::STENCIL: glBlendFunc(GL_ZERO, GL_SRC_ALPHA); break; + case olc::DecalMode::ILLUMINATE: glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); break; + case olc::DecalMode::WIREFRAME: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + locBindBuffer(0x8892, m_vbQuad); + locVertex verts[4] = { + {{-1.0f, -1.0f, 1.0}, {0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{+1.0f, -1.0f, 1.0}, {1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{-1.0f, +1.0f, 1.0}, {0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + {{+1.0f, +1.0f, 1.0}, {1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + }; + + locBufferData(0x8892, sizeof(locVertex) * 4, verts, 0x88E0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, rendBlankQuad.Decal()->id); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + locBindBuffer(0x8892, m_vbQuad); + + for (uint32_t i = 0; i < decal.points; i++) + pVertexMem[i] = { { decal.pos[i].x, decal.pos[i].y, decal.w[i] }, { decal.uv[i].x, decal.uv[i].y }, decal.tint[i] }; + + locBufferData(0x8892, sizeof(locVertex) * decal.points, pVertexMem, 0x88E0); + + if (nDecalMode == DecalMode::WIREFRAME) + glDrawArrays(GL_LINE_LOOP, 0, decal.points); + else + glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Image loaders | +// O------------------------------------------------------------------------------O + +#pragma region image_gdi +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: GDI+, Windows Only, always exists, a little slow | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_GDI) + +#define min(a, b) ((a < b) ? a : b) +#define max(a, b) ((a > b) ? a : b) +#include +#include +#if defined(__MINGW32__) // Thanks Gusgo & Dandistine, but c'mon mingw!! wtf?! + #include +#else + #include +#endif +#include +#undef min +#undef max + +#if !defined(__MINGW32__) + #pragma comment(lib, "gdiplus.lib") + #pragma comment(lib, "Shlwapi.lib") +#endif + +namespace olc +{ + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + GdiplusStartup(&token, &startupInput, NULL); + } + + ULONG_PTR token; + + ~GDIPlusStartup() + { + // Well, MarcusTU thought this was important :D + Gdiplus::GdiplusShutdown(token); + } + } gdistartup; + + class ImageLoader_GDIPlus : public olc::ImageLoader + { + private: + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + ImageLoader_GDIPlus() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + // clear out existing sprite + spr->pColData.clear(); + + // Open file + UNUSED(pack); + Gdiplus::Bitmap* bmp = nullptr; + if (pack != nullptr) + { + // Load sprite from input stream + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)rb.vMemory.data(), UINT(rb.vMemory.size()))); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + + // Load sprite from file + bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str()); + } + + if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::rcode::FAIL; + spr->width = bmp->GetWidth(); + spr->height = bmp->GetHeight(); + + spr->pColData.resize(spr->width * spr->height); + + for (int y = 0; y < spr->height; y++) + for (int x = 0; x < spr->width; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + spr->SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: GDI+ | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_libpng +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: libpng, default on linux, requires -lpng (libpng-dev) | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_LIBPNG) +#include +namespace olc +{ + void pngReadStream(png_structp pngPtr, png_bytep data, png_size_t length) + { + png_voidp a = png_get_io_ptr(pngPtr); + ((std::istream*)a)->read((char*)data, length); + } + + class ImageLoader_LibPNG : public olc::ImageLoader + { + public: + ImageLoader_LibPNG() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + + // clear out existing sprite + spr->pColData.clear(); + + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_structp png; + png_infop info; + + auto loadPNG = [&]() + { + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + png_bytep* row_pointers; + spr->width = png_get_image_width(png, info); + spr->height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * spr->height); + for (int y = 0; y < spr->height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + // Create sprite array + spr->pColData.resize(spr->width * spr->height); + // Iterate through image rows, converting into sprite format + for (int y = 0; y < spr->height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < spr->width; x++) + { + png_bytep px = &(row[x * 4]); + spr->SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + for (int y = 0; y < spr->height; y++) // Thanks maksym33 + free(row_pointers[y]); + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + }; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + if (pack == nullptr) + { + FILE* f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::rcode::NO_FILE; + png_init_io(png, f); + loadPNG(); + fclose(f); + } + else + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); + png_set_read_fn(png, (png_voidp)&is, pngReadStream); + loadPNG(); + } + + return olc::rcode::OK; + + fail_load: + spr->width = 0; + spr->height = 0; + spr->pColData.clear(); + return olc::rcode::FAIL; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: | +// 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 | +// O------------------------------------------------------------------------------O + +#pragma region platform_windows +// O------------------------------------------------------------------------------O +// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_WINAPI) + +#if defined(_WIN32) && !defined(__MINGW32__) + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input +#endif + +namespace olc +{ + class Platform_Windows : public olc::Platform + { + private: + HWND olc_hWnd = nullptr; + std::wstring wsAppName; + + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() override { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_hWnd }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE"); + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + olc::vi2d vTopLeft = vWindowPos; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; + vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + + // Keep client size as requested + RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + + // 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; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + //mapKeys[VK_RETURN] = Key::ENTER;// mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // Thanks scripticuk + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(s).c_str()); +#else + SetWindowText(olc_hWnd, s.c_str()); +#endif + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override { return olc::rcode::FAIL; } + + // Windows Event Handler - this is statically connected to the windows event system + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_MOUSEMOVE: + { + // Thanks @ForAbby (Discord) + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; + case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; + case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; + case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_KEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_LBUTTONDOWN:ptrPGE->olc_UpdateMouseState(0, true); return 0; + case WM_LBUTTONUP: ptrPGE->olc_UpdateMouseState(0, false); return 0; + case WM_RBUTTONDOWN:ptrPGE->olc_UpdateMouseState(1, true); return 0; + 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_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_linux +// O------------------------------------------------------------------------------O +// | START PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_X11) +namespace olc +{ + class Platform_Linux : public olc::Platform + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::Window olc_Window; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + public: + virtual olc::rcode ApplicationStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + XDestroyWindow(olc_Display, olc_Window); + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_Display, &olc_Window, olc_VisualInfo }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + using namespace X11; + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, vWindowPos.x, vWindowPos.y, + vWindowSize.x, vWindowSize.y, + 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, + CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + vWindowSize.x = gwa.width; + vWindowSize.y = gwa.height; + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + X11::XStoreName(olc_Display, olc_Window, s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + using namespace X11; + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + ptrPGE->olc_UpdateWindowSize(xce.width, xce.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + XKeyEvent* e = (XKeyEvent*)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + XKeyEvent* e = (XKeyEvent*)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, true); break; + case 2: ptrPGE->olc_UpdateMouseState(2, true); break; + case 3: ptrPGE->olc_UpdateMouseState(1, true); break; + case 4: ptrPGE->olc_UpdateMouseWheel(120); break; + case 5: ptrPGE->olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, false); break; + case 2: ptrPGE->olc_UpdateMouseState(2, false); break; + case 3: ptrPGE->olc_UpdateMouseState(1, false); break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + ptrPGE->olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + ptrPGE->olc_UpdateKeyFocus(true); + } + else if (xev.type == FocusOut) + { + ptrPGE->olc_UpdateKeyFocus(false); + } + else if (xev.type == ClientMessage) + { + ptrPGE->olc_Terminate(); + } + } + return olc::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_glut +// O------------------------------------------------------------------------------O +// | START PLATFORM: GLUT (used to make it simple for Apple) | +// O------------------------------------------------------------------------------O +// +// VERY IMPORTANT!!! The Apple port was originally created by @Mumflr (discord) +// and the repo for the development of this project can be found here: +// https://github.com/MumflrFumperdink/olcPGEMac which contains maccy goodness +// and support on how to setup your build environment. +// +// "MASSIVE MASSIVE THANKS TO MUMFLR" - Javidx9 +#if defined(OLC_PLATFORM_GLUT) +namespace olc { + + class Platform_GLUT : public olc::Platform + { + public: + static std::atomic* bActiveRef; + + virtual olc::rcode ApplicationStartUp() override { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + static void ExitMainLoop() { + if (!ptrPGE->OnUserDestroy()) { + *bActiveRef = true; + return; + } + platform->ThreadCleanUp(); + platform->ApplicationCleanUp(); + exit(0); + } + +#if defined(__APPLE__) + static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { + static const SEL deltaYSel = sel_registerName("deltaY"); + +#if defined(__aarch64__) // Thanks ruarq! + double deltaY = ((double (*)(id, SEL))objc_msgSend)(theEvent, deltaYSel); +#else + double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); +#endif + + for (int i = 0; i < abs(deltaY); i++) { + if (deltaY > 0) { + ptrPGE->olc_UpdateMouseWheel(-1); + } + else if (deltaY < 0) { + ptrPGE->olc_UpdateMouseWheel(1); + } + } + } +#endif + static void ThreadFunct() { +#if defined(__APPLE__) + static bool hasEnabledCocoa = false; + if (!hasEnabledCocoa) { + // Objective-C Wizardry + Class NSApplicationClass = objc_getClass("NSApplication"); + + // NSApp = [NSApplication sharedApplication] + SEL sharedApplicationSel = sel_registerName("sharedApplication"); + id NSApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, sharedApplicationSel); + // window = [NSApp mainWindow] + SEL mainWindowSel = sel_registerName("mainWindow"); + id window = ((id(*)(id, SEL))objc_msgSend)(NSApp, mainWindowSel); + + // [window setStyleMask: NSWindowStyleMaskClosable | ~NSWindowStyleMaskResizable] + SEL setStyleMaskSel = sel_registerName("setStyleMask:"); + ((void (*)(id, SEL, NSUInteger))objc_msgSend)(window, setStyleMaskSel, 7); + + hasEnabledCocoa = true; + } +#endif + if (!*bActiveRef) { + ExitMainLoop(); + return; + } + glutPostRedisplay(); + } + + static void DrawFunct() { + ptrPGE->olc_CoreUpdate(); + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { +#if defined(__APPLE__) + Class GLUTViewClass = objc_getClass("GLUTView"); + + SEL scrollWheelSel = sel_registerName("scrollWheel:"); + bool resultAddMethod = class_addMethod(GLUTViewClass, scrollWheelSel, (IMP)scrollWheelUpdate, "v@:@"); + assert(resultAddMethod); +#endif + + renderer->PrepareDevice(); + + if (bFullScreen) + { + vWindowSize.x = glutGet(GLUT_SCREEN_WIDTH); + vWindowSize.y = glutGet(GLUT_SCREEN_HEIGHT); + glutFullScreen(); + } + else + { + if (vWindowSize.x > glutGet(GLUT_SCREEN_WIDTH) || vWindowSize.y > glutGet(GLUT_SCREEN_HEIGHT)) + { + perror("ERROR: The specified window dimensions do not fit on your screen\n"); + return olc::FAIL; + } + glutReshapeWindow(vWindowSize.x, vWindowSize.y - 1); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys['A'] = Key::A; mapKeys['B'] = Key::B; mapKeys['C'] = Key::C; mapKeys['D'] = Key::D; mapKeys['E'] = Key::E; + mapKeys['F'] = Key::F; mapKeys['G'] = Key::G; mapKeys['H'] = Key::H; mapKeys['I'] = Key::I; mapKeys['J'] = Key::J; + mapKeys['K'] = Key::K; mapKeys['L'] = Key::L; mapKeys['M'] = Key::M; mapKeys['N'] = Key::N; mapKeys['O'] = Key::O; + mapKeys['P'] = Key::P; mapKeys['Q'] = Key::Q; mapKeys['R'] = Key::R; mapKeys['S'] = Key::S; mapKeys['T'] = Key::T; + mapKeys['U'] = Key::U; mapKeys['V'] = Key::V; mapKeys['W'] = Key::W; mapKeys['X'] = Key::X; mapKeys['Y'] = Key::Y; + mapKeys['Z'] = Key::Z; + + mapKeys[GLUT_KEY_F1] = Key::F1; mapKeys[GLUT_KEY_F2] = Key::F2; mapKeys[GLUT_KEY_F3] = Key::F3; mapKeys[GLUT_KEY_F4] = Key::F4; + mapKeys[GLUT_KEY_F5] = Key::F5; mapKeys[GLUT_KEY_F6] = Key::F6; mapKeys[GLUT_KEY_F7] = Key::F7; mapKeys[GLUT_KEY_F8] = Key::F8; + mapKeys[GLUT_KEY_F9] = Key::F9; mapKeys[GLUT_KEY_F10] = Key::F10; mapKeys[GLUT_KEY_F11] = Key::F11; mapKeys[GLUT_KEY_F12] = Key::F12; + + mapKeys[GLUT_KEY_DOWN] = Key::DOWN; mapKeys[GLUT_KEY_LEFT] = Key::LEFT; mapKeys[GLUT_KEY_RIGHT] = Key::RIGHT; mapKeys[GLUT_KEY_UP] = Key::UP; + mapKeys[13] = Key::ENTER; + + mapKeys[127] = Key::BACK; mapKeys[27] = Key::ESCAPE; + mapKeys[9] = Key::TAB; mapKeys[GLUT_KEY_HOME] = Key::HOME; + mapKeys[GLUT_KEY_END] = Key::END; mapKeys[GLUT_KEY_PAGE_UP] = Key::PGUP; mapKeys[GLUT_KEY_PAGE_DOWN] = Key::PGDN; mapKeys[GLUT_KEY_INSERT] = Key::INS; + mapKeys[32] = Key::SPACE; mapKeys[46] = Key::PERIOD; + + mapKeys[48] = Key::K0; mapKeys[49] = Key::K1; mapKeys[50] = Key::K2; mapKeys[51] = Key::K3; mapKeys[52] = Key::K4; + mapKeys[53] = Key::K5; mapKeys[54] = Key::K6; mapKeys[55] = Key::K7; mapKeys[56] = Key::K8; mapKeys[57] = Key::K9; + + // NOTE: MISSING KEYS :O + + glutKeyboardFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, true); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, true); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutKeyboardUpFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, false); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, false); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + //No ALT in PGE + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + //Special keys + glutSpecialFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutSpecialUpFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + glutMouseFunc([](int button, int state, int x, int y) -> void { + switch (button) { + case GLUT_LEFT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(0, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(0, true); + break; + case GLUT_MIDDLE_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(2, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(2, true); + break; + case GLUT_RIGHT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(1, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(1, true); + break; + } + }); + + auto mouseMoveCall = [](int x, int y) -> void { + ptrPGE->olc_UpdateMouse(x, y); + }; + + glutMotionFunc(mouseMoveCall); + glutPassiveMotionFunc(mouseMoveCall); + + glutEntryFunc([](int state) -> void { + if (state == GLUT_ENTERED) ptrPGE->olc_UpdateKeyFocus(true); + else if (state == GLUT_LEFT) ptrPGE->olc_UpdateKeyFocus(false); + }); + + glutDisplayFunc(DrawFunct); + glutIdleFunc(ThreadFunct); + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + glutSetWindowTitle(s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override { + glutMainLoop(); + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + return olc::OK; + } + }; + + std::atomic* Platform_GLUT::bActiveRef{ nullptr }; + + //Custom Start + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + olc_PrepareEngine(); + if (!OnUserCreate()) return olc::FAIL; + Platform_GLUT::bActiveRef = &bAtomActive; + glutWMCloseFunc(Platform_GLUT::ExitMainLoop); + bAtomActive = true; + platform->StartSystemEventLoop(); + + //This code will not even be run but why not + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: GLUT | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#pragma region platform_emscripten +// O------------------------------------------------------------------------------O +// | START PLATFORM: Emscripten - Totally Game Changing... | +// O------------------------------------------------------------------------------O + +// +// Firstly a big mega thank you to members of the OLC Community for sorting this +// out. Making a browser compatible version has been a priority for quite some +// time, but I lacked the expertise to do it. This awesome feature is possible +// because a group of former strangers got together and formed friendships over +// their shared passion for code. If anything demonstrates how powerful helping +// each other can be, it's this. - Javidx9 + +// Emscripten Platform: MaGetzUb, Moros1138, Slavka, Dandistine, Gorbit99, Bispoo +// also: Ishidex, Gusgo99, SlicEnDicE, Alexio + + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + +#include +#include + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline int olc_OnPageUnload() + { olc::platform->ApplicationCleanUp(); return 0; } +} + +namespace olc +{ + class Platform_Emscripten : public olc::Platform + { + public: + + virtual olc::rcode ApplicationStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ApplicationCleanUp() override + { ThreadCleanUp(); return olc::rcode::OK; } + + virtual olc::rcode ThreadStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { renderer->DestroyDevice(); return olc::OK; } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + emscripten_set_canvas_element_size("#canvas", vWindowSize.x, vWindowSize.y); + + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; + mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_8; + + // Keyboard Callbacks + emscripten_set_keydown_callback("#canvas", 0, 1, keyboard_callback); + emscripten_set_keyup_callback("#canvas", 0, 1, keyboard_callback); + + // Mouse Callbacks + emscripten_set_wheel_callback("#canvas", 0, 1, wheel_callback); + emscripten_set_mousedown_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mouseup_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mousemove_callback("#canvas", 0, 1, mouse_callback); + + // Touch Callbacks + emscripten_set_touchstart_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchmove_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchend_callback("#canvas", 0, 1, touch_callback); + + // Canvas Focus Callbacks + emscripten_set_blur_callback("#canvas", 0, 1, focus_callback); + emscripten_set_focus_callback("#canvas", 0, 1, focus_callback); + +#pragma warning disable format + EM_ASM( window.onunload = Module._olc_OnPageUnload; ); + + // IMPORTANT! - Sorry About This... + // + // In order to handle certain browser based events, such as resizing and + // going to full screen, we have to effectively inject code into the container + // running the PGE. Yes, I vomited about 11 times too when the others were + // convincing me this is the future. Well, this isnt the future, and if it + // were to be, I want no part of what must be a miserable distopian free + // for all of anarchic code injection to get rudimentary events like "Resize()". + // + // Wake up people! Of course theres a spoon. There has to be to keep feeding + // 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;"; + + // 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) + { + viewHeight = e.detail.height; + viewWidth = e.detail.height * Module._olc_WindowAspectRatio; + } + + 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(); + }); + + // 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); + }; + + + // Disable Refresh Gesture on mobile + document.body.style.cssText += " overscroll-behavior-y: contain;"; + + if (Module.canvas.parentNode.className == 'emscripten_border') + { + // 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(); + + // observe and react to resizing of the container element + var resizeObserver = new ResizeObserver(function(entries) {Module._olc_ResizeCanvas();}).observe(Module.canvas.parentNode); + + // 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++) + { + // 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 + }); + + // add resize listener on window + window.addEventListener("resize", function(e) { Module._olc_ResizeCanvas(); }); + + }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers +#pragma warning restore format + return olc::rcode::OK; + } + + // Interface PGE's UpdateWindowSize, for use in Javascript + void UpdateWindowSize(int width, int height) + { + ptrPGE->olc_UpdateWindowSize(width, height); + } + + //TY Gorbit + static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + ptrPGE->olc_UpdateKeyFocus(false); + ptrPGE->olc_UpdateMouseFocus(false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + ptrPGE->olc_UpdateKeyFocus(true); + ptrPGE->olc_UpdateMouseFocus(true); + } + + return 0; + } + + //TY Moros + static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], true); + + // THANK GOD!! for this compute function. And thanks Dandistine for pointing it out! + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], false); + + //Consume keyboard events so that keys like F1 and F5 don't do weird things + return EM_TRUE; + } + + //TY Moros + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + ptrPGE->olc_UpdateMouseWheel(-1 * e->deltaY); + + return EM_TRUE; + } + + //TY Bispoo + static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) + { + // Move + if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + } + + // Start + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + ptrPGE->olc_UpdateMouseState(0, true); + } + + // End + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + ptrPGE->olc_UpdateMouseState(0, false); + } + + return EM_TRUE; + } + + //TY Moros + static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) + { + //Mouse Movement + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) + ptrPGE->olc_UpdateMouse(e->targetX, e->targetY); + + + //Mouse button press + if (e->button == 0) // left click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(0, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(0, false); + } + + if (e->button == 2) // right click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(1, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(1, false); + + } + + if (e->button == 1) // middle click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(2, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(2, false); + + //at the moment only middle mouse needs to consume events. + return EM_TRUE; + } + + return EM_FALSE; + } + + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { emscripten_set_window_title(s.c_str()); return olc::OK; } + + virtual olc::rcode StartSystemEventLoop() override + { return olc::OK; } + + virtual olc::rcode HandleSystemEvent() override + { return olc::OK; } + + static void MainLoop() + { + olc::Platform::ptrPGE->olc_CoreUpdate(); + if (!ptrPGE->olc_IsRunning()) + { + if (ptrPGE->OnUserDestroy()) + { + emscripten_cancel_main_loop(); + platform->ApplicationCleanUp(); + } + else + { + ptrPGE->olc_Reanimate(); + } + } + } + }; + + //Emscripten needs a special Start function + //Much of this is usually done in EngineThread, but that isn't used here + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Some implementations may form an event loop here + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Consider the "thread" started + bAtomActive = true; + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + platform->StartSystemEventLoop(); + + //This causes a heap memory corruption in Emscripten for some reason + //Platform_Emscripten::bActiveRef = &bAtomActive; + emscripten_set_main_loop(&Platform_Emscripten::MainLoop, 0, 1); + + // Wait for thread to be exited + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + return olc::OK; + } +} + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline void olc_PGE_UpdateWindowSize(int width, int height) + { + emscripten_set_canvas_element_size("#canvas", width, height); + // Thanks slavka + ((olc::Platform_Emscripten*)olc::platform.get())->UpdateWindowSize(width, height); + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: Emscripten | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#endif // Headless + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Auto-Configuration | +// O------------------------------------------------------------------------------O +#pragma region pge_config +namespace olc +{ + void PixelGameEngine::olc_ConfigureSystem() + { + +#if !defined(OLC_PGE_HEADLESS) + +#if defined(OLC_IMAGE_GDI) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_LIBPNG) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_STB) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_CUSTOM_EX) + olc::Sprite::loader = std::make_unique(); +#endif + + + + +#if defined(OLC_PLATFORM_WINAPI) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_X11) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_GLUT) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_CUSTOM_EX) + platform = std::make_unique(); +#endif + + + +#if defined(OLC_GFX_OPENGL10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL33) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGLES2) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX11) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_CUSTOM_EX) + renderer = std::make_unique(); +#endif + + // Associate components with PGE instance + platform->ptrPGE = this; + renderer->ptrPGE = this; +#else + olc::Sprite::loader = nullptr; + platform = nullptr; + renderer = nullptr; +#endif + } +} + +#pragma endregion + +#endif // End OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | END OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O + diff --git a/sig b/sig index 4284122..c248256 100755 --- a/sig +++ b/sig @@ -3,7 +3,7 @@ export AUTO_UPDATE=true source utils/define.sh define PROJECT_NAME "CProjectTemplate" -define CUSTOM_PARAMS "-lncurses" -define LANGUAGE "C" +define CUSTOM_PARAMS "-lpng -lGL -lX11" +define LANGUAGE "C++" source utils/main.sh \ No newline at end of file diff --git a/utils/main.sh b/utils/main.sh index d299d6b..0a4c92a 100644 --- a/utils/main.sh +++ b/utils/main.sh @@ -25,4 +25,4 @@ if [ -z "$1" ] exit fi -./$LANGUAGE/scripts/$1.sh "${*:2}" \ No newline at end of file +./$LANGUAGE/scripts/$1.sh "${@:2}" \ No newline at end of file diff --git a/utils/md5 b/utils/md5 index f7c5ca7..e7f0df3 100644 --- a/utils/md5 +++ b/utils/md5 @@ -1,4 +1,4 @@ define.sh:3ecab0dffe2adfb950f3eb7c7061b750 - -main.sh:d3d1bd0b56d8114eb7479964227f8576 - -search.sh:81d08f5ff48e8a44b5f68387d426da05 - +main.sh:4e6e9f0650ec790ce2c4364db94f0caa - +search.sh:30e1842e9a13452ea883bb6516d28e1c - .updateDirectories:fa5e95db12be22ae8aed7ecbc560e38c -