#pragma region license_and_help /* olcPixelGameEngine.h +-------------------------------------------------------------+ | OneLoneCoder Pixel Game Engine v2.25 | | "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 - 2024 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. Portions of this software are copyright © 2024 The FreeType Project (www.freetype.org). Please see LICENSE_FT.txt for more information. All rights reserved. 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 em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 $(Get-ChildItem *.cpp) -o pge.html --preload-file assets 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 Dragoneye..........Lucas Arts Adventure Game Pack Anonymous Pirate...Return To Monkey Island Special thanks to my Patreons too - I wont name you on here, but I've certainly enjoyed my tea and flapjacks :D - In Memory of SaladinAkara 25.06.2023 - Author ~~~~~~ David Barr, aka javidx9, (c) OneLoneCoder 2018, 2019, 2020, 2021, 2022,© 2024, 2024 */ #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 +OnConsoleCommand() - Override is called when command is entered into built in console 2.20: +DrawRectDecal() - Keeps OneSketchyGuy quiet +GetScreenSize() +olc::Sprite::Size() - returns size of sprite in vector format 2.21: Emscripten Overhaul - Thanks Moros! +DrawPolygonDecal() tint overload, can now tint a polygon accounting for vertex colours +Multiplicative Pixel overload +v2d_generic clamp() +v2d_generic lerp() +GetDroppedFiles() - returns files dropped onto engine window for that frame (MSW only) +GetDroppedFilesPoint() - returns location of dropped files (MSW only) +Exposed OpenGL33 Loader interface so the typedefs can be shared with PGEX & user +Fix OGL33 DecalStructure types - wow, how did that one get missed?? lol +FillTexturedTriangle() - Software rasterizes a textured, coloured, triangle +FillTexturedPolygon() - Hijacks DecalStructure for configuration +olc::vf2d arguments for Sprite::Sample() functions 2.22: = Fix typo on dragged file buffers for unicode builds 2.23: Fixed Emscripten host sizing errors - Thanks Moros Fixed v2d_generic.clamp() function 2.24: Fix FillTexturedTriangle() to remove const-ref 2.25: +DrawPolygonDecal(pos, tex, w, col) !! 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 #define OLC_GFX_OPENGL33 #ifndef OLC_PGE_DEF #define OLC_PGE_DEF #pragma region std_includes #define Arc _OLDARC // O------------------------------------------------------------------------------O // | STANDARD INCLUDES | // O------------------------------------------------------------------------------O #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Error.h" #include "olcUTIL_Geometry2D.h" #include "Pixel.h" #include "TEST_DEFINES.h" #pragma endregion #define PGE_VER 225 // 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 #if defined(OLC_PGE_HEADLESS) #define OLC_PLATFORM_HEADLESS #define OLC_GFX_HEADLESS #if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) #define OLC_IMAGE_HEADLESS #endif #endif // Platform #if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) && !defined(OLC_PLATFORM_HEADLESS) #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 #include #include #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) && !defined(OLC_GFX_HEADLESS) #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) && !defined(OLC_IMAGE_HEADLESS) #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 #if defined(OLC_PGE_HEADLESS) #if defined max #undef max #endif #if defined min #undef min #endif #endif #pragma endregion #undef Arc // O------------------------------------------------------------------------------O // | olcPixelGameEngine INTERFACE DECLARATION | // O------------------------------------------------------------------------------O #pragma region pge_declaration namespace olc { class PixelGameEngine; class Sprite; class Font; // Pixel Game Engine Advanced Configuration constexpr inline uint8_t nMouseButtons = 5; constexpr inline uint8_t nTabSizeInSpaces = 4; constexpr inline size_t OLC_MAX_VERTS = 128; enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; // 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, //UNREACHABLE ITEMS BELOW: SHOULDER, ARROWS, }; 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 }; #define OLC_IGNORE_VEC2D // 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) }; } v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1).min(v2); } v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1.0 - t)) + (v1 * T(t)); } T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } 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(); 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); void Resize(int32_t w, int32_t h); 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 Sample(const olc::vf2d& uv) const; Pixel SampleBL(float u, float v) const; Pixel SampleBL(const olc::vf2d& uv) const; Pixel* GetData(); olc::Sprite* Duplicate(); olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); olc::vi2d Size() const; 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, BLENDWHITE, SCREEN, MODEL3D, }; #ifndef OLC_DECALSTRUCT #define OLC_DECALSTRUCT enum class DecalStructure { LINE, FAN, STRIP, LIST }; #endif // O------------------------------------------------------------------------------O // | olc::Renderable - Convenience class to keep a sprite and decal together | // O------------------------------------------------------------------------------O class Renderable { public: Renderable() = default; Renderable(Renderable&& r)noexcept : 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 z; std::vector tint; olc::DecalMode mode = olc::DecalMode::NORMAL; olc::DecalStructure structure = olc::DecalStructure::FAN; uint32_t points = 0; bool depth = false; }; 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 SetVSync(const bool vSyncEnabled) = 0; virtual void PrepareRender(int width,int height) = 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 SetFullscreen(const bool bFullscreen, const vi2d windowPos = {})=0; virtual void SetWindowPos(vi2d pos) = 0; virtual void SetWindowSize(vi2d size) = 0; virtual olc::rcode StartSystemEventLoop() = 0; virtual olc::rcode HandleSystemEvent() = 0; virtual olc::rcode SendRequest(std::string_view url,std::string_view data){return olc::rcode::OK;}; 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; namespace EngineTests{ class EngineTest; } // O------------------------------------------------------------------------------O // | olc::PixelGameEngine - The main BASE class for your application | // O------------------------------------------------------------------------------O class PixelGameEngine { friend class ViewPort; struct StringDecalData{ char c; vf2d sourcePos; vf2d sourceSize; Pixel col; StringDecalData(char c,vf2d sourcePos,vf2d sourceSize,Pixel col) :c(c),sourcePos(sourcePos),sourceSize(sourceSize),col(col){}; }; public: PixelGameEngine(); virtual ~PixelGameEngine(); public: olc::rcode Construct(int32_t window_x, int32_t window_y, int32_t window_w, int32_t window_h, 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(); virtual void GetAnyKeyPress(Key key); virtual void GetAnyKeyRelease(Key key); virtual void GetAnyMousePress(int32_t mouseButton); virtual void GetAnyMouseHeld(int32_t mouseButton); virtual void GetAnyMouseRelease(int32_t mouseButton); // 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); virtual void OnRequestCompleted(const std::string_view receivedData)const; virtual void OnWindowLoseFocus()const; virtual olc::rcode SendRequest(std::string_view url,std::string_view data); public: // Hardware Interfaces void ClearGarbage(); void ClearTimedOutGarbage(); // 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); double GetRunTime() const; void SetRuntime(const double runTime); // Gets the current Frames Per Second uint32_t GetFPS() const; // Gets last update of elapsed time float GetElapsedTime() const; void SetElapsedTime(float elapsedTime); // Returns whether the mouse cursor exists inside the window or outside of it. const bool IsMouseInsideWindow() const; // Gets Actual Window pos const olc::vi2d& GetWindowPos() const; // Gets the upper-left corner of the window to include window decorations/borders. const olc::vi2d& GetActualWindowPos() 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; // Gets "screen" size const olc::vi2d& GetScreenSize() const; const bool IsFullscreen()const; const bool TestingModeEnabled()const; // Gets any files dropped this frame const std::vector& GetDroppedFiles() const; const olc::vi2d& GetDroppedFilesPoint() const; void SetWindowPos(vi2d pos); void SetWindowSize(vi2d size); void SetVSync(const bool vSyncEnabled); 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); void SetFullscreen(const bool bFullscreen, const vi2d windowPos = {}); 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); // Fill a textured and coloured triangle void FillTexturedTriangle(std::vector vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex); void FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure = olc::DecalStructure::LIST); // Draws an entire sprite at location (x,y) void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::functioncolorFunc=[](const Pixel&in){return in;}); void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::functioncolorFunc=[](const Pixel&in){return in;}); // 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,Pixel colorOverride=WHITE); 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,Pixel colorOverride=WHITE); // Draws a single line of text - traditional monospaced void DrawStringProp(const olc::vi2d& pos, std::string_view sText, Pixel col = olc::WHITE, uint32_t scale = 1,const float width=std::numeric_limits::max(),const bool colorOverride=false); void DrawShadowString(const olc::vi2d& pos, std::string_view sText, Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits::max(),const float shadowSizeFactor=1); void DrawStringProp(int32_t x, int32_t y, std::string_view sText, Pixel col = olc::WHITE, uint32_t scale = 1,const float width=std::numeric_limits::max(),const bool colorOverride=false); void DrawShadowStringProp(const olc::vi2d& pos, std::string_view sText, Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits::max(),const float shadowSizeFactor=1); olc::vi2d GetTextSize(std::string_view s); olc::vi2d GetTextSizeProp(std::string_view s); olc::vf2d GetWrappedTextSize(std::string_view s,const float width=std::numeric_limits::max(),const vf2d scale={1,1}); olc::vf2d GetWrappedTextSizeProp(std::string_view s,const float width=std::numeric_limits::max(),const vf2d scale={1,1}); void DrawString(const olc::vi2d& pos, std::string_view sText, Pixel col = olc::WHITE, uint32_t scale = 1,const float width=std::numeric_limits::max(),const bool colorOverride=false); void DrawString(int32_t x, int32_t y, std::string_view sText, Pixel col = olc::WHITE, uint32_t scale = 1,const float width=std::numeric_limits::max(),const bool colorOverride=false); // 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, const float* w, 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); void DrawPartialSquishedRotatedDecal(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::vf2d& squishScale = { 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, std::string_view sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits::max()); void DrawOGStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); void DrawStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); void DrawStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }, const float width=std::numeric_limits::max()); void DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col=WHITE, const Pixel shadowCol=BLACK, const olc::vf2d& scale={1.f,1.f}); void DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const olc::vf2d&shadowScale,const float width=std::numeric_limits::max()); void DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col=WHITE, const Pixel shadowCol=BLACK, const olc::vf2d& scale={1.f,1.f}); void DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const olc::vf2d&shadowScale,const float width=std::numeric_limits::max()); void DrawShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float shadowSizeFactor=1); void DrawDropShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f }); // Draws a single shaded filled rectangle as a decal void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); // Draws a corner shaded rectangle as a decal void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); // 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& uv, int startInd, const vf2d&offset, const float scale, const olc::Pixel tint = olc::WHITE); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint = olc::WHITE); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& tint); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); // Draws a line in Decal Space void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); void DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); 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 }); void DrawRotatedShadowStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float shadowSizeFactor=1); void DrawRotatedShadowStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float shadowSizeFactor=1); // 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(); // Returns the font image olc::Decal* GetFontDecal(); void SetFontSprite(std::string_view filename,std::string_view shadowFilename,ResourcePack*pack,bool generateGamePack); // 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 = ""); const std::string&TextEntryGetString() const; void TextEntrySetString(const std::string&str); int32_t TextEntryGetCursor() const; bool IsTextEntryEnabled() const; void TextEntrySetCharLimit(const uint8_t charLimit); const uint8_t GetMosaicEffect()const; void SetMosaicEffect(uint8_t effectLevel); void ClearDecalInstances(); HWButton*const GetKeyboardState(uint8_t key); public: static std::map charToColor; static std::string Grey; static std::string Dark_Grey; static std::string Very_Dark_Grey; static std::string Red; static std::string Dark_Red; static std::string Very_Dark_Red; static std::string Yellow; static std::string Dark_Yellow; static std::string Very_Dark_Yellow; static std::string Green; static std::string Dark_Green; static std::string Very_Dark_Green; static std::string Cyan; static std::string Dark_Cyan; static std::string Very_Dark_Cyan; static std::string Blue; static std::string Dark_Blue; static std::string Very_Dark_Blue; static std::string Magenta; static std::string Dark_Magenta; static std::string Very_Dark_Magenta; static std::string White; static std::string Black; static std::string Reset; private: void UpdateTextEntry(); void UpdateConsole(); public: static std::string responseData; // 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_DrawWarpedDecal(olc::Decal* decal, const std::vector>& pos, const olc::Pixel& tint); 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; friend class ViewPort; std::string stripLeadingCol(std::string_view originalText); std::u32string stripLeadingCol(std::u32string_view originalText); std::string stripCol(std::string_view originalText); std::u32string stripCol(std::u32string_view originalText); //If WHITE is provided as the blend color (as is the default), then we need to check the source string for a leading HTML color code. If we find one, return that as the final color we want to render the string as. Otherwise, return the original color. const Pixel GetFinalRenderColor(const Pixel initialCol,std::string_view sourceStr); const Pixel GetFinalRenderColor(const Pixel initialCol,std::u32string_view sourceStr); 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 vWindowPos = { 0, 0 }; olc::vi2d vActualWindowPos = { 0, 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,fontRenderableShadow; std::vector vLayers; uint8_t nTargetLayer = 0; uint32_t nLastFPS = 0; bool bPixelCohesion = false; DecalMode nDecalMode = DecalMode::NORMAL; double dRunTime = 0.0; DecalStructure nDecalStructure = DecalStructure::FAN; std::function funcPixelMode; std::chrono::time_point m_tp1, m_tp2; std::vector vFontSpacing; std::vector vDroppedFiles; std::vector vDroppedFilesCache; olc::vi2d vDroppedFilesPoint; olc::vi2d vDroppedFilesPointCache; uint8_t mosaic=1; uint8_t nTextEntryCharLimit=std::numeric_limits::max(); struct DecalData{ Decal*decal=nullptr; float expireTime=0.0f; std::string originalStr; }; std::unordered_mapgarbageCollector; bool requestClear=false; // 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 }; bool testingMode{false}; // 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_UpdateWindowPos(int32_t x, int32_t y); void olc_UpdateActualWindowPos(int32_t x, int32_t y); 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_DropFiles(int32_t x, int32_t y, const std::vector& vFiles); void olc_Reanimate(); bool olc_IsRunning(); void olc_SetTestingMode(const bool testingMode); // 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 #pragma region opengl33_iface // In order to facilitate more advanced graphics features, some PGEX // will rely on shaders. Instead of having each PGEX responsible for // managing this, for convenience, this interface exists. #if defined(OLC_GFX_OPENGL33) #if defined(OLC_PLATFORM_WINAPI) #include #define CALLSTYLE __stdcall #endif #if defined(__linux__) || defined(__FreeBSD__) #include #endif #if defined(OLC_PLATFORM_X11) namespace X11 { #include } #define CALLSTYLE #endif #if defined(__APPLE__) #define GL_SILENCE_DEPRECATION #include #include #include #define CALLSTYLE #endif #if defined(OLC_PLATFORM_EMSCRIPTEN) #include #include #define GL_GLEXT_PROTOTYPES #include #include #define CALLSTYLE #define GL_CLAMP GL_CLAMP_TO_EDGE #endif namespace olc { typedef char GLchar; typedef ptrdiff_t GLsizeiptr; #ifndef OLC_GFX_HEADLESS typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); typedef GLuint CALLSTYLE locCreateProgram_t(void); typedef void CALLSTYLE locDeleteShader_t(GLuint shader); typedef void CALLSTYLE locCompileShader_t(GLuint shader); typedef void CALLSTYLE locLinkProgram_t(GLuint program); typedef void CALLSTYLE locDeleteProgram_t(GLuint program); typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); typedef void CALLSTYLE locUseProgram_t(GLuint program); typedef void CALLSTYLE locBindVertexArray_t(GLuint array); typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); typedef void CALLSTYLE locUniform2f_t(GLint location, GLfloat v0, GLfloat v1); typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); typedef void CALLSTYLE locActiveTexture_t(GLenum texture); typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); #endif #if defined(OLC_PLATFORM_WINAPI) typedef void __stdcall locSwapInterval_t(GLsizei n); #endif #if defined(OLC_PLATFORM_X11) typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); #endif #if defined(OLC_PLATFORM_EMSCRIPTEN) typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); #else #ifndef OLC_GFX_HEADLESS typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); #endif #endif } // olc namespace #endif // OpenGL33 Definitions #pragma endregion #endif // OLC_PGE_DEF // O------------------------------------------------------------------------------O // | START OF OLC_PGE_APPLICATION | // O------------------------------------------------------------------------------O #ifdef OLC_PGE_APPLICATION #undef OLC_PGE_APPLICATION #include "olcPGEX_TTF.h" // O------------------------------------------------------------------------------O // | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | // | Note: The core implementation is platform independent | // O------------------------------------------------------------------------------O #pragma region pge_implementation namespace olc { std::map PixelGameEngine::charToColor; std::string PixelGameEngine::Grey; std::string PixelGameEngine::Dark_Grey; std::string PixelGameEngine::Very_Dark_Grey; std::string PixelGameEngine::Red; std::string PixelGameEngine::Dark_Red; std::string PixelGameEngine::Very_Dark_Red; std::string PixelGameEngine::Yellow; std::string PixelGameEngine::Dark_Yellow; std::string PixelGameEngine::Very_Dark_Yellow; std::string PixelGameEngine::Green; std::string PixelGameEngine::Dark_Green; std::string PixelGameEngine::Very_Dark_Green; std::string PixelGameEngine::Cyan; std::string PixelGameEngine::Dark_Cyan; std::string PixelGameEngine::Very_Dark_Cyan; std::string PixelGameEngine::Blue; std::string PixelGameEngine::Dark_Blue; std::string PixelGameEngine::Very_Dark_Blue; std::string PixelGameEngine::Magenta; std::string PixelGameEngine::Dark_Magenta; std::string PixelGameEngine::Very_Dark_Magenta; std::string PixelGameEngine::White; std::string PixelGameEngine::Black; std::string PixelGameEngine::Reset; //Will render the original color provided when used. // 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, Pixel{nDefaultPixel}); } Sprite::~Sprite() { pColData.clear(); } void Sprite::Resize(int32_t w, int32_t h) { width = w; height = h; pColData.resize(width * height); pColData.resize(width * height, Pixel{nDefaultPixel}); } 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::Sample(const olc::vf2d& uv) const { return Sample(uv.x, uv.y); } Pixel Sprite::SampleBL(float u, float v) const { u = u * width - 0.5f; 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::SampleBL(const olc::vf2d& uv) const { return SampleBL(uv.x, uv.y); } 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; } olc::vi2d olc::Sprite::Size() const { return { width, height }; } // O------------------------------------------------------------------------------O // | olc::Decal IMPLEMENTATION | // O------------------------------------------------------------------------------O Decal::Decal(olc::Sprite* spr, bool filter, bool clamp) { id = -1; if (renderer->ptrPGE->TestingModeEnabled()||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 (renderer->ptrPGE->TestingModeEnabled()||spr == nullptr) return; id = nExistingTextureResource; } void Decal::Update() { if (renderer->ptrPGE->TestingModeEnabled()||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 (renderer->ptrPGE->TestingModeEnabled()||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(){} 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; }; std::string PixelGameEngine::responseData; // 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 window_x, int32_t window_y, int32_t window_w, int32_t window_h, 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) }; vWindowPos = {window_x,window_y}; vPixelSize = { pixel_w, pixel_h }; vWindowSize = {window_w,window_h}; 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(vWindowPos, 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; if(!TestingModeEnabled())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; } double PixelGameEngine::GetRunTime() const { return dRunTime; } void PixelGameEngine::SetRuntime(const double runTime){ dRunTime=runTime; } uint32_t PixelGameEngine::GetFPS() const { return nLastFPS; } void PixelGameEngine::ClearGarbage(){ requestClear=true; } void PixelGameEngine::ClearTimedOutGarbage(){ std::erase_if(garbageCollector,[&](auto&key){ if(key.second.expireTime0&&originalText[0]=='#')return std::string(originalText.substr(7)); return std::string(originalText); } void PixelGameEngine::ClearDecalInstances(){ for(LayerDesc&layer:GetLayers()){ layer.vecDecalInstance.clear(); } } std::u32string PixelGameEngine::stripLeadingCol(std::u32string_view originalText){ if(originalText.length()>0&&originalText[0]=='#')return std::u32string(originalText.substr(7)); return std::u32string(originalText); } std::string PixelGameEngine::stripCol(std::string_view originalText){ std::string newStr{""}; for(int i=0;i0&&initialCol==WHITE&&sourceStr[0]=='#')return Pixel(std::stoi(std::string(sourceStr.substr(1,6)),nullptr,16)); return initialCol; } const Pixel PixelGameEngine::GetFinalRenderColor(const Pixel initialCol,std::u32string_view sourceStr){ if(sourceStr.length()>0&&initialCol==WHITE&&sourceStr[0]=='#')return Pixel(std::stoi(std::string(sourceStr.substr(1,6).begin(),sourceStr.substr(1,6).end()),nullptr,16)); return initialCol; } bool PixelGameEngine::IsFocused() const { return bHasInputFocus; } HWButton PixelGameEngine::GetKey(Key k) const { return IsFocused()?pKeyboardState[k]:HWButton{}; } HWButton PixelGameEngine::GetMouse(uint32_t b) const { return IsFocused()?pMouseState[b]:HWButton{}; } 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 IsFocused()?nMouseWheelDelta:0; } int32_t PixelGameEngine::ScreenWidth() const { return vScreenSize.x; } int32_t PixelGameEngine::ScreenHeight() const { return vScreenSize.y; } float PixelGameEngine::GetElapsedTime() const { return fLastElapsed; } void PixelGameEngine::SetElapsedTime(float elapsedTime) { fLastElapsed=elapsedTime; } const bool PixelGameEngine::IsMouseInsideWindow() const { return GetMouseX()>=0&&GetMouseY()>=0&&GetMouseX()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(); } olc::Decal* PixelGameEngine::GetFontDecal() { return fontRenderable.Decal(); } void PixelGameEngine::SetFontSprite(std::string_view filename,std::string_view shadowFilename,ResourcePack*pack,bool generateGamePack) { if(pack->Loaded()){ fontRenderable.Load(std::string(filename),pack); fontRenderableShadow.Load(std::string(shadowFilename),pack); }else{ fontRenderable.Load(std::string(filename),nullptr); fontRenderableShadow.Load(std::string(shadowFilename),nullptr); if(generateGamePack){ pack->AddFile(std::string(filename)); pack->AddFile(std::string(shadowFilename)); } } //Generate shadow version. } 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::FillTexturedTriangle(std::vector vPoints, std::vector vTex, std::vector vColour, olc::Sprite* sprTex) { olc::vi2d p1 = vPoints[0]; olc::vi2d p2 = vPoints[1]; olc::vi2d p3 = vPoints[2]; if (p2.y < p1.y){std::swap(p1.y, p2.y); std::swap(p1.x, p2.x); std::swap(vTex[0].x, vTex[1].x); std::swap(vTex[0].y, vTex[1].y); std::swap(vColour[0], vColour[1]);} if (p3.y < p1.y){std::swap(p1.y, p3.y); std::swap(p1.x, p3.x); std::swap(vTex[0].x, vTex[2].x); std::swap(vTex[0].y, vTex[2].y); std::swap(vColour[0], vColour[2]);} if (p3.y < p2.y){std::swap(p2.y, p3.y); std::swap(p2.x, p3.x); std::swap(vTex[1].x, vTex[2].x); std::swap(vTex[1].y, vTex[2].y); std::swap(vColour[1], vColour[2]);} olc::vi2d dPos1 = p2 - p1; olc::vf2d dTex1 = vTex[1] - vTex[0]; int dcr1 = vColour[1].r - vColour[0].r; int dcg1 = vColour[1].g - vColour[0].g; int dcb1 = vColour[1].b - vColour[0].b; int dca1 = vColour[1].a - vColour[0].a; olc::vi2d dPos2 = p3 - p1; olc::vf2d dTex2 = vTex[2] - vTex[0]; int dcr2 = vColour[2].r - vColour[0].r; int dcg2 = vColour[2].g - vColour[0].g; int dcb2 = vColour[2].b - vColour[0].b; int dca2 = vColour[2].a - vColour[0].a; float dax_step = 0, dbx_step = 0, dcr1_step = 0, dcr2_step = 0, dcg1_step = 0, dcg2_step = 0, dcb1_step = 0, dcb2_step = 0, dca1_step = 0, dca2_step = 0; olc::vf2d vTex1Step, vTex2Step; if (dPos1.y) { dax_step = dPos1.x / (float)abs(dPos1.y); vTex1Step = dTex1 / (float)abs(dPos1.y); dcr1_step = dcr1 / (float)abs(dPos1.y); dcg1_step = dcg1 / (float)abs(dPos1.y); dcb1_step = dcb1 / (float)abs(dPos1.y); dca1_step = dca1 / (float)abs(dPos1.y); } if (dPos2.y) { dbx_step = dPos2.x / (float)abs(dPos2.y); vTex2Step = dTex2 / (float)abs(dPos2.y); dcr2_step = dcr2 / (float)abs(dPos2.y); dcg2_step = dcg2 / (float)abs(dPos2.y); dcb2_step = dcb2 / (float)abs(dPos2.y); dca2_step = dca2 / (float)abs(dPos2.y); } olc::vi2d vStart; olc::vi2d vEnd; int vStartIdx; for (int pass = 0; pass < 2; pass++) { if (pass == 0) { vStart = p1; vEnd = p2; vStartIdx = 0; } else { dPos1 = p3 - p2; dTex1 = vTex[2] - vTex[1]; dcr1 = vColour[2].r - vColour[1].r; dcg1 = vColour[2].g - vColour[1].g; dcb1 = vColour[2].b - vColour[1].b; dca1 = vColour[2].a - vColour[1].a; dcr1_step = 0; dcg1_step = 0; dcb1_step = 0; dca1_step = 0; if (dPos2.y) dbx_step = dPos2.x / (float)abs(dPos2.y); if (dPos1.y) { dax_step = dPos1.x / (float)abs(dPos1.y); vTex1Step = dTex1 / (float)abs(dPos1.y); dcr1_step = dcr1 / (float)abs(dPos1.y); dcg1_step = dcg1 / (float)abs(dPos1.y); dcb1_step = dcb1 / (float)abs(dPos1.y); dca1_step = dca1 / (float)abs(dPos1.y); } vStart = p2; vEnd = p3; vStartIdx = 1; } if (dPos1.y) { for (int i = vStart.y; i <= vEnd.y; i++) { int ax = int(vStart.x + (float)(i - vStart.y) * dax_step); int bx = int(p1.x + (float)(i - p1.y) * dbx_step); olc::vf2d tex_s(vTex[vStartIdx].x + (float)(i - vStart.y) * vTex1Step.x, vTex[vStartIdx].y + (float)(i - vStart.y) * vTex1Step.y); olc::vf2d tex_e(vTex[0].x + (float)(i - p1.y) * vTex2Step.x, vTex[0].y + (float)(i - p1.y) * vTex2Step.y); olc::Pixel col_s(vColour[vStartIdx].r + uint8_t((float)(i - vStart.y) * dcr1_step), vColour[vStartIdx].g + uint8_t((float)(i - vStart.y) * dcg1_step), vColour[vStartIdx].b + uint8_t((float)(i - vStart.y) * dcb1_step), vColour[vStartIdx].a + uint8_t((float)(i - vStart.y) * dca1_step)); olc::Pixel col_e(vColour[0].r + uint8_t((float)(i - p1.y) * dcr2_step), vColour[0].g + uint8_t((float)(i - p1.y) * dcg2_step), vColour[0].b + uint8_t((float)(i - p1.y) * dcb2_step), vColour[0].a + uint8_t((float)(i - p1.y) * dca2_step)); if (ax > bx) { std::swap(ax, bx); std::swap(tex_s, tex_e); std::swap(col_s, col_e); } float tstep = 1.0f / ((float)(bx - ax)); float t = 0.0f; for (int j = ax; j < bx; j++) { olc::Pixel pixel = PixelLerp(col_s, col_e, t); if (sprTex != nullptr) pixel *= sprTex->Sample(tex_s.lerp(tex_e, t)); Draw(j, i, pixel); t += tstep; } } } } } void PixelGameEngine::FillTexturedPolygon(const std::vector& vPoints, const std::vector& vTex, const std::vector& vColour, olc::Sprite* sprTex, olc::DecalStructure structure) { if (structure == olc::DecalStructure::LINE) { return; // Meaningless, so do nothing } if (vPoints.size() < 3 || vTex.size() < 3 || vColour.size() < 3) return; if (structure == olc::DecalStructure::LIST) { for (int tri = 0; tri < vPoints.size() / 3; tri++) { std::vector vP = { vPoints[tri * 3 + 0], vPoints[tri * 3 + 1], vPoints[tri * 3 + 2] }; std::vector vT = { vTex[tri * 3 + 0], vTex[tri * 3 + 1], vTex[tri * 3 + 2] }; std::vector vC = { vColour[tri * 3 + 0], vColour[tri * 3 + 1], vColour[tri * 3 + 2] }; FillTexturedTriangle(vP, vT, vC, sprTex); } return; } if (structure == olc::DecalStructure::STRIP) { for (int tri = 2; tri < vPoints.size(); tri++) { std::vector vP = { vPoints[tri - 2], vPoints[tri-1], vPoints[tri] }; std::vector vT = { vTex[tri - 2], vTex[tri - 1], vTex[tri] }; std::vector vC = { vColour[tri - 2], vColour[tri - 1], vColour[tri] }; FillTexturedTriangle(vP, vT, vC, sprTex); } return; } if (structure == olc::DecalStructure::FAN) { for (int tri = 2; tri < vPoints.size(); tri++) { std::vector vP = { vPoints[0], vPoints[tri - 1], vPoints[tri] }; std::vector vT = { vTex[0], vTex[tri - 1], vTex[tri] }; std::vector vC = { vColour[0], vColour[tri - 1], vColour[tri] }; FillTexturedTriangle(vP, vT, vC, sprTex); } return; } } void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip, std::functioncolorFunc) { DrawSprite(pos.x, pos.y, sprite, scale, flip, colorFunc); } void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip, std::functioncolorFunc) { 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, colorFunc(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, colorFunc(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,Pixel colorOverride) { DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip,colorOverride); } 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,Pixel colorOverride) { 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, (colorOverride!=WHITE&&sprite->GetPixel(fx + ox, fy + oy).a!=0)?colorOverride: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, (colorOverride!=WHITE&&sprite->GetPixel(fx + ox, fy + oy).a!=0)?colorOverride: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.01f, 0.01f)) * decal->vUVScale; olc::vf2d uvbr = (source_pos + source_size - olc::vf2d(0.02f, 0.02f)) * 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 + olc::vf2d(0.01f, 0.01f)) * decal->vUVScale; olc::vf2d uvbr = uvtl + ((source_size - olc::vf2d(0.02f, 0.02f)) * 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, const float* w, 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] = w[i]; } 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) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); 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 int startInd, const vf2d&offset, const float scale, const olc::Pixel tint) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); DecalInstance di; di.decal = decal; di.points = uint32_t(pos.size()-startInd); di.pos.resize(di.points); di.uv.resize(di.points); di.w.resize(di.points); di.tint.resize(di.points); if(startInd!=0){ di.pos[0] = { ((pos[0].x*scale+offset.x) * vInvScreenSize.x) * 2.0f - 1.0f, (((pos[0].y*scale+offset.y) * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; di.uv[0] = uv[0]; di.tint[0] = tint; di.w[0] = 1.0f; } for (uint32_t i = 1; i < di.points; i++) { di.pos[i] = { ((pos[startInd+i].x*scale+offset.x) * vInvScreenSize.x) * 2.0f - 1.0f, (((pos[startInd+i].y*scale+offset.y) * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; di.uv[i] = uv[startInd+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) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); 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& uv, const std::vector& colours, const olc::Pixel tint) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); std::vector newColours(colours.size(), olc::WHITE); std::transform(colours.begin(), colours.end(), newColours.begin(), [&tint](const olc::Pixel pin) { return pin * tint; }); DrawPolygonDecal(decal, pos, uv, newColours); } void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); 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] = depth[i]; } 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 std::vector& colours, const olc::Pixel tint) { if(pos.size()>OLC_MAX_VERTS)throw std::runtime_error("Vertex count exceeds Maximum allowed vertices!"); 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] = colours[i] * tint; di.w[i] = depth[i]; } 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.z.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.z[i] = pos[i][2]; di.uv[i] = tex[i]; di.tint[i] = col[i]; } di.mode = nDecalMode; di.structure = DecalStructure::LIST; di.depth = true; vLayers[nTargetLayer].vecDecalInstance.push_back(di); } void PixelGameEngine::LW3D_DrawWarpedDecal(olc::Decal* decal, const std::vector>& 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.z = { 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][0] - pos[0][0]) * (pos[3][1] - pos[1][1]) - (pos[3][0] - pos[1][0]) * (pos[2][1] - pos[0][1])); if (rd != 0) { rd = 1.0f / rd; float rn = ((pos[3][0] - pos[1][0]) * (pos[0][1] - pos[1][1]) - (pos[3][1] - pos[1][1]) * (pos[0][0] - pos[1][0])) * rd; float sn = ((pos[2][0] - pos[0][0]) * (pos[0][1] - pos[1][1]) - (pos[2][1] - pos[0][1]) * (pos[0][0] - pos[1][0])) * rd; if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) { center.x = pos[0][0] + rn * (pos[2][0] - pos[0][0]); center.y = pos[0][1] + rn * (pos[2][1] - pos[0][1]); } float d[4]; for (int i = 0; i < 4; i++) d[i] = std::sqrt((pos[i][0] - center.x) * (pos[i][0] - center.x) + (pos[i][1] - center.y) * (pos[i][1] - center.y)); 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.z[i] = pos[i][2]; di.pos[i] = { (pos[i][0] * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i][1] * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; } di.mode = nDecalMode; di.structure = nDecalStructure; di.depth = true; vLayers[nTargetLayer].vecDecalInstance.push_back(di); } } #endif void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) { auto m = nDecalMode; nDecalMode = olc::DecalMode::WIREFRAME; DrawPolygonDecal(nullptr, { pos1, pos2 }, { {0, 0}, {0,0} }, p); nDecalMode = m; /*DecalInstance di; di.decal = nullptr; di.points = uint32_t(2); di.pos.resize(di.points); 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; di.structure = nDecalStructure; vLayers[nTargetLayer].vecDecalInstance.push_back(di);*/ } void PixelGameEngine::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { auto m = nDecalMode; SetDecalMode(olc::DecalMode::WIREFRAME); olc::vf2d vNewSize = size;// (size - olc::vf2d(0.375f, 0.375f)).ceil(); std::array 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} }; std::array ws = {1,1,1,1}; DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), ws.data(), 4); SetDecalMode(m); } void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { olc::vf2d vNewSize = size;// (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} }; std::array ws = {1,1,1,1}; DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), ws.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} }; std::array ws = {1,1,1,1}; DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), ws.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::DrawPartialSquishedRotatedDecal(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::vf2d& squishScale, 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; } di.pos[0] *= squishScale; di.pos[1] *= squishScale; di.pos[2] *= squishScale; di.pos[3] *= squishScale; 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::DrawOGStringDecal(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::DrawStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const olc::vf2d& scale,const float width) { Pixel textCol=col; static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale,letter.col); drawingMarker.x += 8.0f * scale.x; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale, letter.col); drawingMarker.x += 8.0f * scale.x; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale.y; } else if (c == ' ') { drawingMarker.x += 8.0f * scale.x; planningMarker.x += 8.0f * scale.x; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; } else if (c>=-128&&c<-105) { textCol={PixelGameEngine::charToColor[c].r,PixelGameEngine::charToColor[c].g,PixelGameEngine::charToColor[c].b,col.a}; } else if (c==PixelGameEngine::Reset[0]) { textCol=col; } else if (c=='#') { skip=6; textCol=BLACK; for(int i=1;i<7;i++){ if(i<3){ textCol.r*=16; textCol.r+=hexToNumber(sText[index+i]); }else if(i<5){ textCol.g*=16; textCol.g+=hexToNumber(sText[index+i]); }else{ textCol.b*=16; textCol.b+=hexToNumber(sText[index+i]); } } if(textCol==WHITE)textCol=col; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += 8.0f * scale.x; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale,letter.col); drawingMarker.x += 8.0f * scale.x; } letters.clear(); }else{ drawingMarker.x-=float(vFontSpacing[' '-32].y)*scale.x; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += 8.0f * scale.x; } } letters.emplace_back(c,vf2d{ float(ox) * 8.0f, float(oy) * 8.0f }, vf2d{ 8.0f, 8.0f },textCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale,letter.col); drawingMarker.x += 8.0f * scale.x; } } void PixelGameEngine::DrawStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const olc::vf2d& scale, const float width) { Pixel textCol=col; static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale, letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale, letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale.y; } else if (c == ' ') { drawingMarker.x += float(vFontSpacing[' ' - 32].y) * scale.x; planningMarker.x += float(vFontSpacing[' ' - 32].y) * scale.x; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; } else if (c>=-128&&c<-105) { textCol={PixelGameEngine::charToColor[c].r,PixelGameEngine::charToColor[c].g,PixelGameEngine::charToColor[c].b,col.a}; } else if (c==PixelGameEngine::Reset[0]) { textCol=col; } else if (c=='#') { skip=6; textCol=BLACK; for(int i=1;i<7;i++){ if(i<3){ textCol.r*=16; textCol.r+=hexToNumber(sText[index+i]); }else if(i<5){ textCol.g*=16; textCol.g+=hexToNumber(sText[index+i]); }else{ textCol.b*=16; textCol.b+=hexToNumber(sText[index+i]); } } if(textCol==WHITE)textCol=col; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += float(vFontSpacing[c - 32].y) * scale.x; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale,letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); }else{ drawingMarker.x-=float(vFontSpacing[' '-32].y)*scale.x; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } } letters.emplace_back(c,vf2d{ float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, vf2d{ float(vFontSpacing[c - 32].y), 8.0f },textCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialDecal(pos + drawingMarker, fontRenderable.Decal(), letter.sourcePos, letter.sourceSize, scale, letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } } void PixelGameEngine::DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale){ DrawShadowStringDecal(pos,sText,col,shadowCol,scale,scale); } void PixelGameEngine::DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const olc::vf2d& shadowScale,const float width){ static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += 8.0f * scale.x; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += 8.0f * scale.x; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale.y; } else if (c == ' ') { drawingMarker.x += 8.0f * scale.x; planningMarker.x += 8.0f * scale.x; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; } else if (c>=-128&&c<-105){} else if (c==PixelGameEngine::Reset[0]){} else if (c=='#') { skip=6; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += 8.0f * scale.x; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += 8.0f * scale.x; } letters.clear(); }else{ drawingMarker.x-=float(vFontSpacing[' '-32].y)*scale.x; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += 8.0f * scale.x; } } letters.emplace_back(c,vf2d{ float(ox) * 10.0f, float(oy) * 10.0f }, vf2d{ 10.0f, 10.0f },shadowCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += 8.0f * scale.x; } DrawStringDecal(pos,sText,col,scale,width); } void PixelGameEngine::DrawStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const olc::vf2d& scale){ if(sText.length()==0)return; std::u32string originalKey{stripCol(sText)}; std::u32string renderStr{stripLeadingCol(sText)}; std::u32string Ukey=U"DSD_"+font.GetFontName()+U"_"+originalKey; std::string key=std::string(Ukey.begin(),Ukey.end()); const bool RerenderRequired=garbageCollector.count(key)&&garbageCollector[key].originalStr!=std::string(renderStr.begin(),renderStr.end()); if(!garbageCollector.count(key)||RerenderRequired){ //If the text key already exists, don't have to recreate the decal, just update the expire time. delete garbageCollector[key].decal; garbageCollector[key].decal=font.RenderStringToDecal(renderStr,WHITE); garbageCollector[key].originalStr=std::string(renderStr.begin(),renderStr.end()); } garbageCollector[key].expireTime=GetRunTime()+120.0f; DrawDecal(pos,garbageCollector[key].decal,scale/4,GetFinalRenderColor(col,sText)); } void PixelGameEngine::DrawShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float shadowSizeFactor){ if(sText.length()==0)return; std::u32string originalKey{stripCol(sText)}; std::u32string renderStr{stripLeadingCol(sText)}; std::u32string Ukey=U"DSSD_"+font.GetFontName()+U"_"+originalKey; std::string key=std::string(Ukey.begin(),Ukey.end()); const bool RerenderRequired=garbageCollector.count(key)&&garbageCollector[key].originalStr!=std::string(renderStr.begin(),renderStr.end()); if(!garbageCollector.count(key)||RerenderRequired){ //If the text key already exists, don't have to recreate the decal, just update the expire time. delete garbageCollector[key].decal; garbageCollector[key].decal=font.RenderStringToDecal(renderStr,WHITE); garbageCollector[key].originalStr=std::string(renderStr.begin(),renderStr.end()); } garbageCollector[key].expireTime=GetRunTime()+120.0f; for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){ for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){ if(x!=0||y!=0){ DrawDecal(pos+vf2d{x,y},garbageCollector[key].decal,scale/4,shadowCol); } } } DrawDecal(pos,garbageCollector[key].decal,scale/4,GetFinalRenderColor(col,sText)); } void PixelGameEngine::DrawDropShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale){ if(sText.length()==0)return; std::u32string originalKey{stripCol(sText)}; std::u32string renderStr{stripLeadingCol(sText)}; std::u32string Ukey=U"DDSSD_"+font.GetFontName()+U"_"+originalKey; std::string key=std::string(Ukey.begin(),Ukey.end()); const bool RerenderRequired=garbageCollector.count(key)&&garbageCollector[key].originalStr!=std::string(renderStr.begin(),renderStr.end()); if(!garbageCollector.count(key)||RerenderRequired){ //If the text key already exists, don't have to recreate the decal, just update the expire time. delete garbageCollector[key].decal; garbageCollector[key].decal=font.RenderStringToDecal(renderStr,WHITE); garbageCollector[key].originalStr=std::string(renderStr.begin(),renderStr.end()); } garbageCollector[key].expireTime=GetRunTime()+120.0f; DrawDecal(pos+vf2d{0,0.5f},garbageCollector[key].decal,scale/4,shadowCol); DrawDecal(pos+vf2d{0.5f,0},garbageCollector[key].decal,scale/4,shadowCol); DrawDecal(pos+vf2d{0.5f,0.5f},garbageCollector[key].decal,scale/4,shadowCol); DrawDecal(pos,garbageCollector[key].decal,scale/4,GetFinalRenderColor(col,sText)); } void PixelGameEngine::DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale){ DrawShadowStringPropDecal(pos,sText,col,shadowCol,scale,scale); } void PixelGameEngine::DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const olc::vf2d& shadowScale,const float width){ static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale.y; } else if (c == ' ') { drawingMarker.x += float(vFontSpacing[' ' - 32].y) * scale.x; planningMarker.x += float(vFontSpacing[' ' - 32].y) * scale.x; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale.x; } else if (c>=-128&&c<-105){} else if (c==PixelGameEngine::Reset[0]){} else if (c=='#') { skip=6; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += float(vFontSpacing[c - 32].y) * scale.x; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } letters.clear(); }else{ drawingMarker.x-=float(vFontSpacing[' '-32].y)*scale.x; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale.y; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } } letters.emplace_back(c,vf2d{ float(ox) * 10.0f + float(vFontSpacing[c - 32].x), float(oy) * 10.0f }, vf2d{ float(vFontSpacing[c - 32].y)+2, 10.0f },shadowCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialRotatedDecal(pos + drawingMarker+vf2d{5.f,5.f}*scale-shadowScale, fontRenderableShadow.Decal(),0.f,{5.f,5.f}, letter.sourcePos, letter.sourceSize,shadowScale,shadowCol); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale.x; } DrawStringPropDecal(pos,sText,col,scale,width); } void PixelGameEngine::DrawShadowString(const olc::vi2d& pos, std::string_view sText, Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float width,const float shadowSizeFactor){ for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){ for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){ if(x!=0||y!=0){ DrawString(int32_t(pos.x+x),int32_t(pos.y+y), sText, shadowCol,int(scale.x),width,true); } } } DrawString(pos.x,pos.y, sText, col,int(scale.x),width); } void PixelGameEngine::DrawShadowStringProp(const olc::vi2d& pos, std::string_view sText, Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float width,const float shadowSizeFactor){ for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){ for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){ if(x!=0||y!=0){ DrawStringProp(int32_t(pos.x+x),int32_t(pos.y+y), sText, shadowCol,int(scale.x),width,true); } } } DrawStringProp(pos.x,pos.y, sText, col,int(scale.x),width); } // 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); } } } void PixelGameEngine::DrawRotatedShadowStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float shadowSizeFactor){ for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){ for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){ if(x!=0||y!=0){ DrawRotatedStringDecal(pos+vf2d{x,y}, sText,fAngle,center,shadowCol,scale); } } } DrawRotatedStringDecal(pos, sText,fAngle,center,col,scale); } void PixelGameEngine::DrawRotatedShadowStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float shadowSizeFactor){ for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){ for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){ if(x!=0||y!=0){ DrawRotatedStringPropDecal(pos+vf2d{x,y}, sText,fAngle,center,shadowCol,scale); } } } DrawRotatedStringPropDecal(pos, sText,fAngle,center,col,scale); } olc::vf2d PixelGameEngine::GetWrappedTextSize(std::string_view s,const float width,const vf2d scale) { float adjustedWidth=width; if(width!=std::numeric_limits::max()){ adjustedWidth/=scale.x; } int lettersWidth=0; float maxWidth=0; bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; for (int skip=0,index=-1;auto c : s) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ drawingMarker.x+=lettersWidth; lettersWidth=0; wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; maxWidth=std::max(maxWidth,drawingMarker.x); }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f; drawingMarker.x+=lettersWidth; maxWidth=std::max(maxWidth,drawingMarker.x); drawingMarker.x = 0; drawingMarker.y += 8.0f; } else if (c == ' ') { drawingMarker.x += 8.0f; planningMarker.x += 8.0f; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces); planningMarker.x += 8.0f * float(nTabSizeInSpaces); } else if (c>=-128&&c<-105||c==PixelGameEngine::Reset[0]) { continue; } else if (c=='#') { skip=6; } else { planningMarker.x += 8.0f; if(planningMarker.x>adjustedWidth){ if(drawingMarker.x==0){ //The text has overflowed a full line because there were no spaces, so we have to dump the line. drawingMarker.x+=lettersWidth; lettersWidth=0; }else{ drawingMarker.x-=vFontSpacing[' '-32].y; //Don't include the space in the sizing when wrapping. } maxWidth=std::max(maxWidth,drawingMarker.x); planningMarker.x = 0; planningMarker.y += 8.0f; drawingMarker=planningMarker; wrappingOccurred=true; planningMarker.x+=lettersWidth; } lettersWidth+=8.0f; } } drawingMarker.x += lettersWidth; maxWidth=std::max(maxWidth,drawingMarker.x); return vf2d{maxWidth,planningMarker.y+8}*scale; } olc::vi2d PixelGameEngine::GetTextSize(std::string_view s) { olc::vi2d size = { 0,1 }; olc::vi2d pos = { 0,1 }; for (int skip=0;auto c : s) { if(skip){ skip--; continue; } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { pos.y++; pos.x = 0; } else if (c == '\t') { pos.x += nTabSizeInSpaces; } else if(c<0)continue; else if (c=='#') { skip=6; continue; } 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, std::string_view sText, Pixel col, uint32_t scale,const float width,const bool colorOverride) { DrawString(pos.x, pos.y, sText, col, scale,width,colorOverride); } void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string_view sText, Pixel col, uint32_t scale,const float width,const bool colorOverride) { Pixel::Mode m = nPixelMode; Pixel textCol=col; // 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); } vf2d pos={float(x),float(y)}; static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), fontRenderable.Sprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += 8.0f * scale; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), fontRenderable.Sprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += 8.0f * scale; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale; } else if (c == ' ') { drawingMarker.x += 8.0f * scale; planningMarker.x += 8.0f * scale; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale; } else if (c>=-128&&c<-105) { textCol={PixelGameEngine::charToColor[c].r,PixelGameEngine::charToColor[c].g,PixelGameEngine::charToColor[c].b,col.a}; } else if (c==PixelGameEngine::Reset[0]) { textCol=col; } else if (c=='#') { skip=6; textCol=BLACK; for(int i=1;i<7;i++){ if(i<3){ textCol.r*=16; textCol.r+=hexToNumber(sText[index+i]); }else if(i<5){ textCol.g*=16; textCol.g+=hexToNumber(sText[index+i]); }else{ textCol.b*=16; textCol.b+=hexToNumber(sText[index+i]); } } if(textCol==WHITE)textCol=col; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += 8.0f * scale; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), fontRenderable.Sprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += 8.0f * scale; } letters.clear(); }else{ drawingMarker.x-=vFontSpacing[' '-32].y*scale; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += 8.0f * scale; } } letters.emplace_back(c,vf2d{ float(ox) * 8.0f, float(oy) * 8.0f }, vf2d{ 8.0f, 8.0f },colorOverride?col:textCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), fontRenderable.Sprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += 8.0f * scale; } SetPixelMode(m); } olc::vi2d PixelGameEngine::GetTextSizeProp(std::string_view s) { if(vFontSpacing.size()==0)return{}; //Hack to allow for testing mode to just return a default value. olc::vi2d size = { 0,1 }; olc::vi2d pos = { 0,1 }; for (int skip=0;auto c : s) { if(skip){ skip--; continue; } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { pos.y += 1; pos.x = 0; } else if (c == '\t') { pos.x += nTabSizeInSpaces * 8; } else if(c<0)continue; else if (c=='#') { skip=6; continue; } 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; } olc::vf2d PixelGameEngine::GetWrappedTextSizeProp(std::string_view s,const float width,const vf2d scale) { float adjustedWidth=width; if(width!=std::numeric_limits::max()){ adjustedWidth/=scale.x; } int lettersWidth=0; float maxWidth=0; bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; for (int skip=0,index=-1;auto c : s) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ drawingMarker.x+=lettersWidth; lettersWidth=0; wrappingOccurred=false; maxWidth=std::max(maxWidth,drawingMarker.x); } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f; drawingMarker.x+=lettersWidth; maxWidth=std::max(maxWidth,drawingMarker.x); drawingMarker.x=0;drawingMarker.y+=8.0f; } else if (c == ' ') { drawingMarker.x += vFontSpacing[c-32].y; planningMarker.x += vFontSpacing[c-32].y; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces); planningMarker.x += 8.0f * float(nTabSizeInSpaces); } else if (c>=-128&&c<-105||c==PixelGameEngine::Reset[0]) { continue; } else if (c=='#') { skip=6; } else { planningMarker.x += vFontSpacing[c-32].y; if(planningMarker.x>adjustedWidth){ if(drawingMarker.x==0){ //The text has overflowed a full line because there are no spaces, so we have to dump the line. drawingMarker.x+=lettersWidth; lettersWidth=0; }else{ drawingMarker.x-=vFontSpacing[' '-32].y; } maxWidth=std::max(maxWidth,drawingMarker.x); planningMarker.x = 0; planningMarker.y += 8.0f; drawingMarker=planningMarker; wrappingOccurred=true; planningMarker.x+=lettersWidth; } lettersWidth+=vFontSpacing[c-32].y; } } drawingMarker.x += lettersWidth; maxWidth=std::max(maxWidth,drawingMarker.x); return vf2d{maxWidth,planningMarker.y+8}*scale; } void PixelGameEngine::DrawStringProp(const olc::vi2d& pos, std::string_view sText, Pixel col, uint32_t scale,const float width,const bool colorOverride) { DrawStringProp(pos.x, pos.y, sText, col, scale,width,colorOverride); } void PixelGameEngine::DrawStringProp(int32_t x, int32_t y, std::string_view sText, Pixel col, uint32_t scale,const float width,const bool colorOverride) { Pixel::Mode m = nPixelMode; Pixel textCol=col; // 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); } static std::vectorletters; letters.clear(); bool wrappingOccurred=false; olc::vf2d planningMarker = { 0.0f, 0.0f }; olc::vf2d drawingMarker = { 0.0f, 0.0f }; vf2d pos={float(x),float(y)}; const auto hexToNumber=[](char c){ if(c<='9')return c-'0'; return (c-'A')+10; }; for (int skip=0,index=-1;auto c : sText) { index++; if(skip){ skip--; continue; } if(c==' '||c=='\t'||c=='\n'){ for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), GetFontSprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale,Sprite::Flip::NONE, letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale; } letters.clear(); wrappingOccurred=false; } if(wrappingOccurred){ if(c!=' '&&c!='\t'&&c!='\n'){ wrappingOccurred=false; }else{ continue; } } if(c=='\r')continue; //Ignore carriage returns. Stupid Linux. if (c == '\n') { planningMarker.x = 0; planningMarker.y += 8.0f * scale; for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), GetFontSprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale,Sprite::Flip::NONE, letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale; } letters.clear(); drawingMarker.x = 0; drawingMarker.y += 8.0f * scale; } else if (c == ' ') { drawingMarker.x += float(vFontSpacing[' ' - 32].y) * scale; planningMarker.x += float(vFontSpacing[' ' - 32].y) * scale; } else if (c == '\t') { drawingMarker.x += 8.0f * float(nTabSizeInSpaces) * scale; planningMarker.x += 8.0f * float(nTabSizeInSpaces) * scale; } else if (c>=-128&&c<-105) { textCol={PixelGameEngine::charToColor[c].r,PixelGameEngine::charToColor[c].g,PixelGameEngine::charToColor[c].b,col.a}; } else if (c==PixelGameEngine::Reset[0]) { textCol=col; } else if (c=='#') { skip=6; textCol=BLACK; for(int i=1;i<7;i++){ if(i<3){ textCol.r*=16; textCol.r+=hexToNumber(sText[index+i]); }else if(i<5){ textCol.g*=16; textCol.g+=hexToNumber(sText[index+i]); }else{ textCol.b*=16; textCol.b+=hexToNumber(sText[index+i]); } } if(textCol==WHITE)textCol=col; } else { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; planningMarker.x += float(vFontSpacing[c - 32].y) * scale; if(planningMarker.x>width){ if(drawingMarker.x==0){ //The text has overflowed a full line, so we have to dump the line. for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), GetFontSprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += float(vFontSpacing[letter.c - 32].y) * scale; } letters.clear(); }else{ drawingMarker.x-=vFontSpacing[' '-32].y*scale; //Don't include the space in the sizing when wrapping. } planningMarker.x = 0; planningMarker.y += 8.0f * scale; drawingMarker=planningMarker; wrappingOccurred=true; for(PixelGameEngine::StringDecalData&letter:letters){ planningMarker.x += float(vFontSpacing[letter.c - 32].y) * scale; } } letters.emplace_back(c,vf2d{ float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, vf2d{ float(vFontSpacing[c - 32].y), 8.0f },colorOverride?col:textCol); } } for(PixelGameEngine::StringDecalData&letter:letters){ DrawPartialSprite(vi2d(pos + drawingMarker), GetFontSprite(), vi2d(letter.sourcePos), vi2d(letter.sourceSize), scale, Sprite::Flip::NONE,letter.col); drawingMarker.x += float(vFontSpacing[letter.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; } void PixelGameEngine::SetFullscreen(const bool bFullscreen, const vi2d windowPos){ this->bFullScreen=bFullscreen; platform->SetFullscreen(bFullscreen,windowPos); } 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); } const std::vector& PixelGameEngine::GetDroppedFiles() const { return vDroppedFiles; } const olc::vi2d& PixelGameEngine::GetDroppedFilesPoint() const { return vDroppedFilesPoint; } void PixelGameEngine::SetWindowPos(vi2d pos){ platform->SetWindowPos(pos); } void PixelGameEngine::SetWindowSize(vi2d size){ platform->SetWindowSize(size); } void PixelGameEngine::SetVSync(const bool vSyncEnabled){ renderer->SetVSync(vSyncEnabled); } void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) { if (bEnable) { nTextEntryCursor = int32_t(sText.size()); sTextEntryString = sText; bTextEntryEnable = true; } else { bTextEntryEnable = false; } } const std::string&PixelGameEngine::TextEntryGetString() const { return sTextEntryString; } void PixelGameEngine::TextEntrySetString(const std::string&str){ sTextEntryString=str; nTextEntryCursor=sTextEntryString.length(); }; int32_t PixelGameEngine::TextEntryGetCursor() const { return nTextEntryCursor; } bool PixelGameEngine::IsTextEntryEnabled() const { return bTextEntryEnable; } void PixelGameEngine::TextEntrySetCharLimit(const uint8_t charLimit){ nTextEntryCharLimit=charLimit; } const uint8_t PixelGameEngine::GetMosaicEffect()const{ return mosaic; } void PixelGameEngine::SetMosaicEffect(uint8_t effectLevel){ mosaic=std::max(uint8_t(1),effectLevel); } HWButton*const PixelGameEngine::GetKeyboardState(uint8_t key){ return &pKeyboardState[key]; } void PixelGameEngine::UpdateTextEntry() { // Check for typed characters for (const auto& key : vKeyboardMap) if (GetKey(std::get<0>(key)).bPressed&&sTextEntryString.size()(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::GetAnyKeyPress(Key key) { } void PixelGameEngine::GetAnyKeyRelease(Key key) { } void PixelGameEngine::GetAnyMousePress(int32_t mouseButton) { } void PixelGameEngine::GetAnyMouseHeld(int32_t mouseButton) { } void PixelGameEngine::GetAnyMouseRelease(int32_t mouseButton) { } void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } void PixelGameEngine::OnRequestCompleted(const std::string_view receivedData)const{} void PixelGameEngine::OnWindowLoseFocus()const{} olc::rcode PixelGameEngine::SendRequest(std::string_view url,std::string_view data){platform->SendRequest(url,data);return olc::rcode::OK;}; // 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_UpdateWindowPos(int32_t x, int32_t y) { vWindowPos = { x, y }; } void PixelGameEngine::olc_UpdateActualWindowPos(int32_t x, int32_t y){ vActualWindowPos = { x, y }; } 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)); } 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) { if(!state){ for (uint32_t i = 0; i < 256; i++) { olc_UpdateKeyState(i,false); } } if(!state)OnWindowLoseFocus(); bHasInputFocus = state; } void PixelGameEngine::olc_DropFiles(int32_t x, int32_t y, const std::vector& vFiles) { x -= vViewPos.x; y -= vViewPos.y; vDroppedFilesPointCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); vDroppedFilesPointCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); if (vDroppedFilesPointCache.x >= (int32_t)vScreenSize.x) vDroppedFilesPointCache.x = vScreenSize.x - 1; if (vDroppedFilesPointCache.y >= (int32_t)vScreenSize.y) vDroppedFilesPointCache.y = vScreenSize.y - 1; if (vDroppedFilesPointCache.x < 0) vDroppedFilesPointCache.x = 0; if (vDroppedFilesPointCache.y < 0) vDroppedFilesPointCache.y = 0; vDroppedFilesCache = vFiles; } void PixelGameEngine::olc_Reanimate() { bAtomActive = true; } bool PixelGameEngine::olc_IsRunning() { return bAtomActive; } void PixelGameEngine::olc_SetTestingMode(const bool testingMode){ this->testingMode=testingMode; } 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; OnUserDestroy(); } 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; charToColor[-128]=GREY; charToColor[-127]=DARK_GREY; charToColor[-126]=VERY_DARK_GREY; charToColor[-125]=RED; charToColor[-124]=DARK_RED; charToColor[-123]=VERY_DARK_RED; charToColor[-122]=YELLOW; charToColor[-121]=DARK_YELLOW; charToColor[-120]=VERY_DARK_YELLOW; charToColor[-119]=GREEN; charToColor[-118]=DARK_GREEN; charToColor[-117]=VERY_DARK_GREEN; charToColor[-116]=CYAN; charToColor[-115]=DARK_CYAN; charToColor[-114]=VERY_DARK_CYAN; charToColor[-113]=BLUE; charToColor[-112]=DARK_BLUE; charToColor[-111]=VERY_DARK_BLUE; charToColor[-110]=MAGENTA; charToColor[-109]=DARK_MAGENTA; charToColor[-108]=VERY_DARK_MAGENTA; charToColor[-107]=WHITE; charToColor[-106]=BLACK; Grey =std::string(1,char(-128)); Dark_Grey =std::string(1,char(-127)); Very_Dark_Grey =std::string(1,char(-126)); Red =std::string(1,char(-125)); Dark_Red =std::string(1,char(-124)); Very_Dark_Red =std::string(1,char(-123)); Yellow =std::string(1,char(-122)); Dark_Yellow =std::string(1,char(-121)); Very_Dark_Yellow =std::string(1,char(-120)); Green =std::string(1,char(-119)); Dark_Green =std::string(1,char(-118)); Very_Dark_Green =std::string(1,char(-117)); Cyan =std::string(1,char(-116)); Dark_Cyan =std::string(1,char(-115)); Very_Dark_Cyan =std::string(1,char(-114)); Blue =std::string(1,char(-113)); Dark_Blue =std::string(1,char(-112)); Very_Dark_Blue =std::string(1,char(-111)); Magenta =std::string(1,char(-110)); Dark_Magenta =std::string(1,char(-109)); Very_Dark_Magenta =std::string(1,char(-108)); White =std::string(1,char(-107)); Black =std::string(1,char(-106)); Reset =std::string(1,char(-105)); // 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 actualElapsedTime = elapsedTime.count(); float fElapsedTime = std::clamp(elapsedTime.count(),0.f,1/30.f); //HACK fix. We can't have a negative time. Although using a more precise system clock should make this never occur. Also make sure if the game is too slow we advance by only 1/30th of a second. if (bConsoleSuspendTime) fElapsedTime = 0.0f; fLastElapsed = fElapsedTime; dRunTime += fElapsedTime; // 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; if(pKeys==pKeyboardState)GetAnyKeyPress(Key(i)); //Only produce an event when it's for the keyboard. else if(pKeys==pMouseState)GetAnyMousePress(i); //Only produce an event when it's for the mouse. } else { pKeys[i].bReleased = true; pKeys[i].bHeld = false; if(pKeys==pKeyboardState)GetAnyKeyRelease(Key(i)); //Only produce an event when it's for the keyboard. else if(pKeys==pMouseState)GetAnyMouseRelease(i); //Only produce an event when it's for the mouse. } } if(pKeys==pMouseState&&pKeys[i].bHeld)GetAnyMouseHeld(i); 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; vDroppedFiles = vDroppedFilesCache; vDroppedFilesPoint = vDroppedFilesPointCache; vDroppedFilesCache.clear(); 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->PrepareRender(ScreenWidth(),ScreenHeight()); renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); // Display Decals in order for this layer for (auto& decal : layer->vecDecalInstance){ if(decal.decal!=nullptr){ renderer->PrepareRender(decal.decal->sprite->width,decal.decal->sprite->height); } renderer->DrawDecal(decal); } layer->vecDecalInstance.clear(); } else { // Mwa ha ha.... Have Fun!!! layer->funcHook(); } } } if(requestClear||garbageCollector.size()>5000){ std::for_each(garbageCollector.begin(),garbageCollector.end(),[&](auto&key){ delete key.second.decal->sprite; delete key.second.decal; }); garbageCollector.clear(); requestClear=false; } // Present Graphics to screen renderer->DisplayFrame(); // Update Title Bar fFrameTimer += actualElapsedTime; 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, 0x09,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 #ifdef OLC_KEYBOARD_US 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 #pragma region platform_headless namespace olc { #if defined(OLC_GFX_HEADLESS) class Renderer_Headless : public olc::Renderer { public: virtual void PrepareDevice() {}; virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) { return olc::rcode::OK; } virtual olc::rcode DestroyDevice() { return olc::rcode::OK; } virtual void DisplayFrame() {} virtual void PrepareDrawing() {} virtual void SetVSync(const bool vSyncEnabled) {} virtual void PrepareRender(int width,int height) {}; virtual void SetDecalMode(const olc::DecalMode& mode) {} virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) {} virtual void DrawDecal(const olc::DecalInstance& decal) {} virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) {return 1;}; virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) {} virtual void ReadTexture(uint32_t id, olc::Sprite* spr) {} virtual uint32_t DeleteTexture(const uint32_t id) {return 1;} virtual void ApplyTexture(uint32_t id) {} virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) {} virtual void ClearBuffer(olc::Pixel p, bool bDepth) {} }; #endif #if defined(OLC_PLATFORM_HEADLESS) class Platform_Headless : public olc::Platform { public: virtual olc::rcode ApplicationStartUp() { return olc::rcode::OK; } virtual olc::rcode ApplicationCleanUp() { return olc::rcode::OK; } virtual olc::rcode ThreadStartUp() { return olc::rcode::OK; } virtual olc::rcode ThreadCleanUp() { return olc::rcode::OK; } virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) { return olc::rcode::OK; } virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) { return olc::rcode::OK; } virtual olc::rcode SetWindowTitle(const std::string& s) { return olc::rcode::OK; } virtual olc::rcode StartSystemEventLoop() { return olc::rcode::OK; } virtual olc::rcode HandleSystemEvent() { return olc::rcode::OK; } }; #endif } #pragma endregion // O------------------------------------------------------------------------------O // | olcPixelGameEngine Renderers - the draw-y bits | // O------------------------------------------------------------------------------O #pragma region image_stb // O------------------------------------------------------------------------------O // | START IMAGE LOADER: stb_image.h, all systems, very fast | // O------------------------------------------------------------------------------O // Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h // MIT License - Copyright(c) 2017 Sean Barrett // Note you need to download the above file into your project folder, and // #define OLC_IMAGE_STB // #define OLC_PGE_APPLICATION // #include "olcPixelGameEngine.h" #if defined(OLC_IMAGE_STB) #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" namespace olc { class ImageLoader_STB : public olc::ImageLoader { public: ImageLoader_STB() : ImageLoader() {} olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override { UNUSED(pack); // clear out existing sprite spr->pColData.clear(); // Open file stbi_uc* bytes = nullptr; int w = 0, h = 0, cmp = 0; if (pack != nullptr) { ResourceBuffer rb = pack->GetFileBuffer(sImageFile); bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); } else { // Check file exists if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); } if (!bytes) return olc::rcode::FAIL; spr->width = w; spr->height = h; spr->pColData.resize(spr->width * spr->height); std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); delete[] bytes; return olc::rcode::OK; } olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override { return olc::rcode::OK; } }; } #endif // O------------------------------------------------------------------------------O // | START IMAGE LOADER: stb_image.h | // O------------------------------------------------------------------------------O #pragma endregion #if !defined(OLC_PGE_HEADLESS) #pragma region renderer_ogl10 // 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 } void PrepareRender(int width,int height)override{}; 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 SetVSync(const bool vSyncEnabled){ #if defined(OLC_PLATFORM_WINAPI) if (locSwapInterval) locSwapInterval(vSyncEnabled?1:0); bSync = vSyncEnabled; #endif #if defined(OLC_PLATFORM_X11) locSwapInterval(olc_Display, *olc_Window, vSyncEnabled?1:0); #endif #if defined(OLC_PLATFORM_EMSCRIPTEN) locSwapInterval(olc_Display, vSyncEnabled?1:0); #endif #if defined(OLC_PLATFORM_GLUT) #if defined(__APPLE__) GLint sync = vSyncEnabled?1:0; CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); #endif #endif } void SetDecalMode(const olc::DecalMode& mode) { 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; case olc::DecalMode::BLENDWHITE: break; case olc::DecalMode::SCREEN: glBlendFunc(GL_ONE, GL_ONE); 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 (decal.depth) { // 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); glTexCoord4f(decal.uv[n].x, decal.uv[n].y, 0.0f, decal.w[n]); glVertex3f(decal.pos[n].x, decal.pos[n].y, decal.z[n]); } } else { // 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(); if (decal.depth) { 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); // typedef GLint CALLSTYLE locGetUniformLocation_t(GLuint program, const GLchar* name); // typedef void CALLSTYLE locUniform1f_t(GLint location, GLfloat v0); // typedef void CALLSTYLE locUniform1i_t(GLint location, GLint v0); // typedef void CALLSTYLE locUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); // typedef void CALLSTYLE locActiveTexture_t(GLenum texture); // typedef void CALLSTYLE locGenFrameBuffers_t(GLsizei n, GLuint* ids); // typedef void CALLSTYLE locBindFrameBuffer_t(GLenum target, GLuint fb); // typedef GLenum CALLSTYLE locCheckFrameBufferStatus_t(GLenum target); // typedef void CALLSTYLE locDeleteFrameBuffers_t(GLsizei n, const GLuint* fbs); // typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); // typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs); // typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); 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; locGetUniformLocation_t* locGetUniformLocation = nullptr; locUniform1i_t* locUniform1i = nullptr; locUniform2f_t* locUniform2f = 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); locGetUniformLocation = OGL_LOAD(locGetUniformLocation_t, glGetUniformLocation); locUniform1i = OGL_LOAD(locUniform1i_t, glUniform1i); locUniform2f = OGL_LOAD(locUniform2f_t, glUniform2f); #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 highp float;" #else "#version 330 core\n" #endif "out vec4 pixel;\n" "in vec2 oTex;\n" "in vec4 oCol;\n" "uniform int mosaic;\n" "uniform vec2 size;\n" "uniform sampler2D sprTex;\n" "void main(){\n" "if(mosaic<=1){pixel = texture(sprTex,oTex) * oCol;return;}\n" "vec2 texelSize = 1.0 / size; \n" "vec2 pos = oTex / texelSize; \n""vec2 outputPos = vec2(floor(pos.x/float(mosaic))*float(mosaic),floor(pos.y/float(mosaic))*float(mosaic));\n" "pixel = texture(sprTex, outputPos*texelSize) * oCol;\n" "}"; 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" "\n" "void main(){\n" "float p = 1.0 / aPos.z;\n" "gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0);\n" "oTex = p * aTex;\n" "oCol = aCol;\n" "}"; 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); GLint mosaicUniformLoc = locGetUniformLocation(m_nQuadShader, "mosaic"); GLint sizeUniformLoc = locGetUniformLocation(m_nQuadShader, "size"); locUniform1i(mosaicUniformLoc,ptrPGE->GetMosaicEffect()); locUniform2f(sizeUniformLoc,ptrPGE->ScreenWidth(),ptrPGE->ScreenHeight()); 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 SetVSync(const bool vSyncEnabled){ #if defined(OLC_PLATFORM_WINAPI) if (locSwapInterval) locSwapInterval(vSyncEnabled?1:0); bSync = vSyncEnabled; #endif #if defined(OLC_PLATFORM_X11) locSwapInterval(olc_Display, *olc_Window, vSyncEnabled?1:0); #endif #if defined(OLC_PLATFORM_EMSCRIPTEN) locSwapInterval(olc_Display, vSyncEnabled?1:0); #endif #if defined(OLC_PLATFORM_GLUT) #if defined(__APPLE__) GLint sync = vSyncEnabled?1:0; CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); #endif #endif } void PrepareRender(int width,int height)override{ GLint sizeUniformLoc = locGetUniformLocation(m_nQuadShader, "size"); locUniform2f(sizeUniformLoc,width,height); } 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 { if (decal.structure == olc::DecalStructure::FAN) glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); else if (decal.structure == olc::DecalStructure::STRIP) glDrawArrays(GL_TRIANGLE_STRIP, 0, decal.points); else if (decal.structure == olc::DecalStructure::LIST) glDrawArrays(GL_TRIANGLES, 0, decal.points); } } uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override { 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 // 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; static RECT resultRect; static bool monitorFound; static BOOL CALLBACK monitorCallback(HMONITOR monitor, HDC deviceContext, LPRECT rectPtr, LPARAM data){ monitorFound=true; tagMONITORINFO monitorInfo; monitorInfo.cbSize=sizeof(MONITORINFO); GetMonitorInfoA(monitor,&monitorInfo); resultRect=monitorInfo.rcWork; return FALSE; //Return false here because we will use the first monitor that EnumDisplayMonitors gives us. By returning false, we are ending the enumeration immediately. }; 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 = static_cast(::LoadImage(GetModuleHandle(nullptr), MAKEINTRESOURCE(102), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR));; 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); monitorFound=false; //After calling EnumDisplayMonitors, monitorCallback will set this variable to true if the clipping rectangle region intersects any monitor. RECT monitorClippingRect{vTopLeft.x,vTopLeft.y,vTopLeft.x+width,vTopLeft.y+height}; EnumDisplayMonitors(NULL,&monitorClippingRect,monitorCallback,NULL); if(monitorFound){ vTopLeft.x=std::clamp(long(vTopLeft.x),resultRect.left,resultRect.right); vTopLeft.y=std::clamp(long(vTopLeft.y),resultRect.top,resultRect.bottom); int monitorWidth=resultRect.right-resultRect.left; int monitorHeight=resultRect.bottom-resultRect.top; bool rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen=vTopLeft.x+width>resultRect.right&&widthresultRect.bottom&&heightGetScreenSize().x*ptrPGE->GetPixelSize().x, ptrPGE->GetScreenSize().y*ptrPGE->GetPixelSize().y }; SetWindowLongPtrA(olc_hWnd,GWL_EXSTYLE,dwExStyle); SetWindowLongPtrA(olc_hWnd,GWL_STYLE,dwStyle); AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; MoveWindow(olc_hWnd,windowPos.x, windowPos.y, width, height,true); } return olc::OK; } virtual void SetWindowPos(vi2d pos)override{ if(!ptrPGE->IsFullscreen()){ RECT rWndRect = { 0, 0, ptrPGE->GetWindowSize().x, ptrPGE->GetWindowSize().y }; int windowWidth = rWndRect.right - rWndRect.left; int windowHeight = rWndRect.bottom - rWndRect.top; monitorFound=false; //After calling EnumDisplayMonitors, monitorCallback will set this variable to true if the clipping rectangle region intersects any monitor. RECT monitorClippingRect{pos.x,pos.y,pos.x+windowWidth,pos.y+windowHeight}; EnumDisplayMonitors(NULL,&monitorClippingRect,monitorCallback,NULL); if(monitorFound){ pos.x=std::clamp(long(pos.x),resultRect.left,resultRect.right); pos.y=std::clamp(long(pos.y),resultRect.top,resultRect.bottom); int monitorWidth=resultRect.right-resultRect.left; int monitorHeight=resultRect.bottom-resultRect.top; bool rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen=pos.x+windowWidth>resultRect.right&&windowWidthresultRect.bottom&&windowHeightIsFullscreen()){ RECT rWndRect = { 0, 0, size.x, size.y }; DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; WINDOWPLACEMENT placement; GetWindowPlacement(olc_hWnd,&placement); AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; MoveWindow(olc_hWnd,placement.rcNormalPosition.left, placement.rcNormalPosition.top, width, height,true); } } 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 { struct tagPOINT p{0,0}; //Update mouse positions and states outside the window. GetCursorPos(&p); ptrPGE->olc_UpdateMouse(p.x-ptrPGE->GetWindowPos().x,p.y-ptrPGE->GetWindowPos().y); ptrPGE->olc_UpdateMouseState(0,GetAsyncKeyState(VK_LBUTTON)>>7); ptrPGE->olc_UpdateMouseState(1,GetAsyncKeyState(VK_RBUTTON)>>7); ptrPGE->olc_UpdateMouseState(2,GetAsyncKeyState(VK_MBUTTON)>>7); ptrPGE->olc_UpdateMouseState(3,GetAsyncKeyState(VK_XBUTTON1)>>7); ptrPGE->olc_UpdateMouseState(4,GetAsyncKeyState(VK_XBUTTON2)>>7); return olc::rcode::OK; } // 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_MOVE: { 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_UpdateWindowPos(ix, iy); //HACK ALERT! GetWindowPos() actually returns (0,0) for the canvas and not the window border, so to compensate we have to move down by the border amount. WINDOWPLACEMENT placement; GetWindowPlacement(hWnd,&placement); ptrPGE->olc_UpdateActualWindowPos(placement.rcNormalPosition.left, placement.rcNormalPosition.top); 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_DROPFILES: { // This is all eww... HDROP drop = (HDROP)wParam; uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); std::vector vFiles; for (uint32_t i = 0; i < nFiles; i++) { TCHAR dfbuffer[256]{}; uint32_t len = DragQueryFile(drop, i, nullptr, 0); DragQueryFile(drop, i, dfbuffer, 256); #ifdef UNICODE #ifdef __MINGW32__ char* buffer = NEW char[len + 1]; wcstombs(buffer, dfbuffer, len); buffer[len] = '\0'; #else int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); char* buffer = NEW char[count]; WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); #endif vFiles.push_back(std::string(buffer)); delete[] buffer; #else vFiles.push_back(std::string(dfbuffer)); #endif } // Even more eww... POINT p; DragQueryPoint(drop, &p); ptrPGE->olc_DropFiles(p.x, p.y, vFiles); DragFinish(drop); return 0; } break; case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } }; RECT Platform_Windows::resultRect; bool Platform_Windows::monitorFound=false; } #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_WindowParent; 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 void SetWindowPos(vi2d pos)override{ using namespace X11; if(!ptrPGE->IsFullscreen()){ Window rootW,parentW; Window*childWindows; unsigned int children; XQueryTree(olc_Display,olc_Window,&rootW,&olc_WindowParent,&childWindows,&children); if(childWindows!=nullptr)XFree(childWindows); XWindowAttributes gwa; XGetWindowAttributes(olc_Display, olc_Window, &gwa); XWindowAttributes cwa; XGetWindowAttributes(olc_Display, olc_WindowParent, &cwa); int diffX=gwa.x-cwa.x; //The border is some defined size, and the window manager may be accounting for that when deciding window positions. If we take the difference of window between the two and subtract, we probably end up with a window at the right spot! int diffY=gwa.y-cwa.y; XMapWindow(olc_Display, olc_Window); XMoveWindow(olc_Display, olc_Window, pos.x-diffX, pos.y-diffY); } } virtual void SetWindowSize(vi2d size)override{ if(!ptrPGE->IsFullscreen()){ XMapWindow(olc_Display, olc_Window); XResizeWindow(olc_Display, olc_Window, size.x, size.y); } } virtual olc::rcode SetWindowTitle(const std::string& s) override { X11::XStoreName(olc_Display, olc_Window, s.c_str()); return olc::OK; } virtual olc::rcode SetFullscreen(const bool bFullscreen, const vi2d windowPos)override{ using namespace X11; 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); if(!bFullscreen){ XMoveWindow(olc_Display, olc_Window, windowPos.x, windowPos.y); } 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) { Window rootW,parentW; Window*childWindows; unsigned int children; XQueryTree(olc_Display,olc_Window,&rootW,&olc_WindowParent,&childWindows,&children); if(childWindows!=nullptr)XFree(childWindows); XWindowAttributes cwa; XGetWindowAttributes(olc_Display, olc_WindowParent, &cwa); XWindowAttributes gwa; XGetWindowAttributes(olc_Display, olc_Window, &gwa); ptrPGE->olc_UpdateWindowPos(gwa.x, gwa.y); ptrPGE->olc_UpdateActualWindowPos(cwa.x, cwa.y); ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); } else if (xev.type == ConfigureNotify) { Window rootW,parentW; Window*childWindows; unsigned int children; XQueryTree(olc_Display,olc_Window,&rootW,&olc_WindowParent,&childWindows,&children); if(childWindows!=nullptr)XFree(childWindows); XWindowAttributes gwa; XGetWindowAttributes(olc_Display, olc_WindowParent, &gwa); XConfigureEvent xce = xev.xconfigure; ptrPGE->olc_UpdateWindowPos(xce.x, xce.y); ptrPGE->olc_UpdateActualWindowPos(gwa.x, gwa.y); 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(vWindowPos, 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(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, keyboard_callback); emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, keyboard_callback); // Mouse Callbacks emscripten_set_wheel_callback("#canvas", 0, 1, wheel_callback); emscripten_set_mousedown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, mouse_callback); emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, mouse_callback); emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, mouse_callback); // Touch Callbacks emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, touch_callback); emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, touch_callback); emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 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. EM_ASM({ requestResp=""; // olc_ApsectRatio // // Used by olc_ResizeHandler to calculate the viewport from the // dimensions of the canvas container's element. Module.olc_AspectRatio = $0 / $1; // HACK ALERT! // // Here we assume any html shell that uses 3 or more instance of the class "emscripten" // is using one of the default or minimal emscripten page layouts Module.olc_AssumeDefaultShells = (document.querySelectorAll('.emscripten').length >= 3) ? true : false; oncontextmenu=function(e){return false}; //Because we can click outside the window, we want to disable normal right-click context menu for the application. // olc_ResizeHandler // // Used by olc_Init, and is called when a resize observer and fullscreenchange event is triggered. var olc_ResizeHandler = function() { // are we in fullscreen mode? let isFullscreen = (document.fullscreenElement != null); // get the width of the containing element let width = (isFullscreen) ? window.innerWidth : Module.canvas.parentNode.clientWidth; let height = (isFullscreen) ? window.innerHeight : Module.canvas.parentNode.clientHeight; // calculate the expected viewport size let viewWidth = width; let viewHeight = width / Module.olc_AspectRatio; // if we're taller than the containing element, recalculate based on height if(viewHeight > height) { viewWidth = height * Module.olc_AspectRatio; viewHeight = height; } // ensure resulting viewport is in integer space viewWidth = parseInt(viewWidth); viewHeight = parseInt(viewHeight); setTimeout(function() { // if default shells, apply default styles if(Module.olc_AssumeDefaultShells) Module.canvas.parentNode.setAttribute('style', 'width: 100%; height: 70vh; margin-left: auto; margin-right: auto;'); // apply viewport dimensions to teh canvas Module.canvas.setAttribute('width', viewWidth); Module.canvas.setAttribute('height', viewHeight); Module.canvas.setAttribute('style', `width: ${viewWidth}px; height: ${viewHeight}px;`); // update the PGE window size Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); // force focus on our PGE canvas Module.canvas.focus(); }, 200); }; // olc_Init // // set up resize observer and fullscreenchange event handler var olc_Init = function() { if(Module.olc_AspectRatio === undefined) { setTimeout(function() { Module.olc_Init(); }, 50); return; } let resizeObserver = new ResizeObserver(function(entries) { Module.olc_ResizeHandler(); }).observe(Module.canvas.parentNode); let mutationObserver = new MutationObserver(function(mutationsList, observer) { setTimeout(function() { Module.olc_ResizeHandler(); }, 200); }).observe(Module.canvas.parentNode, { attributes: false, childList: true, subtree: false }); window.addEventListener('fullscreenchange', function(e) { setTimeout(function() { Module.olc_ResizeHandler();}, 200); }); }; // set up hooks Module.olc_ResizeHandler = (Module.olc_ResizeHandler != undefined) ? Module.olc_ResizeHandler : olc_ResizeHandler; Module.olc_Init = (Module.olc_Init != undefined) ? Module.olc_Init : olc_Init; // run everything! Module.olc_Init(); }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers #pragma warning restore format return olc::rcode::OK; } virtual olc::rcode SetFullscreen(const bool bFullscreen, const vi2d windowPos)override{ return olc::rcode::OK; } virtual void SetWindowPos(vi2d pos)override{ } virtual void SetWindowSize(vi2d size)override{ } // Interface PGE's UpdateWindowSize, for use in Javascript void UpdateWindowSize(int width, int height) { ptrPGE->olc_UpdateWindowSize(width, height); } virtual olc::rcode SendRequest(std::string_view url,std::string_view data)override{ EM_ASM({ requestResp=""; fetch(UTF8ToString($0),{ headers: { "Content-Type": "application/json", }, method: "POST",body:UTF8ToString($1)}).then((resp)=>{ if(resp.ok){ return resp.text(); }else{ throw new Error(resp.text()); } }).then((data)=>{requestResp=data;}) .catch((err)=>{requestResp="ERR";}); },std::string(url).c_str(),std::string(data).c_str()); return olc::rcode::OK; } //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-ptrPGE->GetWindowPos().x-EM_ASM_INT({return window.scrollX}), e->touches->targetY-ptrPGE->GetWindowPos().y-EM_ASM_INT({return window.scrollY})); } // Start if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) { ptrPGE->olc_UpdateMouse(e->touches->targetX-ptrPGE->GetWindowPos().x-EM_ASM_INT({return window.scrollX}), e->touches->targetY-ptrPGE->GetWindowPos().y-EM_ASM_INT({return window.scrollY})); 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-ptrPGE->GetWindowPos().x-EM_ASM_INT({return window.scrollX}), e->targetY-ptrPGE->GetWindowPos().y-EM_ASM_INT({return window.scrollY})); //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 { ptrPGE->olc_UpdateWindowPos(EM_ASM_INT({return Module.canvas.getBoundingClientRect().left}),EM_ASM_INT({return Module.canvas.getBoundingClientRect().top})); ptrPGE->olc_UpdateActualWindowPos(EM_ASM_INT({return Module.canvas.getBoundingClientRect().left}),EM_ASM_INT({return Module.canvas.getBoundingClientRect().top})); return olc::OK; } virtual olc::rcode HandleSystemEvent() override { ptrPGE->olc_UpdateWindowPos(EM_ASM_INT({return Module.canvas.getBoundingClientRect().left}),EM_ASM_INT({return Module.canvas.getBoundingClientRect().top})); ptrPGE->olc_UpdateActualWindowPos(EM_ASM_INT({return Module.canvas.getBoundingClientRect().left}),EM_ASM_INT({return Module.canvas.getBoundingClientRect().top})); return olc::OK; } static void MainLoop() { olc::Platform::ptrPGE->olc_CoreUpdate(); char*ptr=(char*)EM_ASM_PTR({ if(requestResp&&requestResp!==""){ var newPtr=stringToNewUTF8(requestResp); requestResp=""; return newPtr; } return 0; }); if(ptr!=NULL){ PixelGameEngine::responseData=std::string(ptr); ptrPGE->OnRequestCompleted(PixelGameEngine::responseData); delete ptr; } 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(vWindowPos, 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) olc::Sprite::loader = nullptr; #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_HEADLESS) platform = 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_HEADLESS) renderer = 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 using namespace olc; #define PI 3.14159f using Color=olc::PixelGameEngine;