From 0fe2d7cf14e8b65571a71fc2173c52d622b89c9f Mon Sep 17 00:00:00 2001 From: zombie Date: Sat, 22 Jan 2022 06:00:21 -0500 Subject: [PATCH] i wanted to write c again and i found this folder on my computer i rearranged some shit and started actually making 3xplus1.c i consider 3xplus1.c to be complete (maybe) and there is also hello.c that is complete @quantum teach me c pl0xpl0xpl0xpl0xpl0xpl0xpl0xpl0xpl0xpl0xpl0x --- 3x+1andfriends.c | 19 + 3xplus1.c | 21 + a.out | Bin 17288 -> 17360 bytes hello.c | 17 +- jame/src/olcExampleProgram | Bin 0 -> 458520 bytes jame/src/olcExampleProgram.cpp | 49 + jame/src/olcPixelGameEngine.h | 5603 ++++++++++++++++++++++++++++++++ 7 files changed, 5696 insertions(+), 13 deletions(-) create mode 100644 3x+1andfriends.c create mode 100644 3xplus1.c create mode 100755 jame/src/olcExampleProgram create mode 100644 jame/src/olcExampleProgram.cpp create mode 100644 jame/src/olcPixelGameEngine.h diff --git a/3x+1andfriends.c b/3x+1andfriends.c new file mode 100644 index 0000000..398c61c --- /dev/null +++ b/3x+1andfriends.c @@ -0,0 +1,19 @@ +#include +char *libz_atoi(char *dicks); + +int main() { + char *dicks = "!123"; + long x = 76; + + while(x != 1) { + if(x % 2 == 0) { + x /= 2; + } + else {x = (x * 3) + 1;} + printf("%ld\n", x); + } + printf("%s\n", dicks); + char *dick2 = libz_atoi(dicks); + printf("%d\n", dick2); + return 0; +} diff --git a/3xplus1.c b/3xplus1.c new file mode 100644 index 0000000..2e800ee --- /dev/null +++ b/3xplus1.c @@ -0,0 +1,21 @@ +#include //printf and shit + +int main(){ + //asd + int x; //idk what types are i think this is a 32bit signed int... idk @quantum help... also should this even be here or should it be out of main + x = 69; + while(x != 1){ //while x is not true i think + printf("%d\n",x ); + if(x & 1 == 1){ //if x is odd + x = (3 * x) + 1; + } + else{ + x = x / 2; + } + + } + + + puts("number reaches one"); + return 0; +} diff --git a/a.out b/a.out index 31a74dab18e55daa421001222293e3312315dec5..9ff90460386f2da0aef1566695aaf84b352e21ba 100755 GIT binary patch delta 2140 zcmZWqeQc9O7{7blYx}P2+IJu6>gL+Q#@6ZDZe>}4b$z=zXkmYpz=S{0ZQvGAJ_f9c z#5v}|N3$&1c>Ib%1Mv@oga~P(Ml=qMd?c7ujU)mZ2$BrZI29Dkv_AKKj4&^GpS%10 zes}jgchB8>U_Z_7r{kqk(m|9Ws};#k_}&qdCbgHG_%0x}3u>nwBpMKFsM@Q61on~| zw-$!!UsAb8+E<Y+^{Ap!}j3zMmc+OTMS8CEi< zYg&^B8Q%{=uPlFb5b+hVe@SsZJZf#Lq;;45)V3LTdwL- zfRK%RKW=6r;;4(Q$Nuf{_*mp5HsWLKU0yA^J5UlIWi7nDcXB|?UA4RS;283`R-=Ac z(KqgfV~%oK0_PkRwGo6!D%2StiTutZj;C)7*y5v+3dD0IR}q@pkjv%HuwrmX^+RXd zXWCJ9t8KJ>CVd9ieyf0wIr|}kWa`U*b2%hNu2S?s8tt4(XXzx(j!e>Y`^+snAM@Gi zpWHP2)m;Qe24`sM1DxYYb&jbQakw(kdEMtf>=l2<)^v^wLM;U%baCy4e>%Zo5K^XK@+xvM0u>!|Fq0MFzp2=i9RW_F_ag-4KWVNIuexQCg z4TIO;MGwO;w$pILAEiOK=66dTEv@-E^3n}bAWAVu0&ZFh+XL@OOZCK31tbW*pifL_ ztM$W4!8!i|a*R+V#f)pP^Dr0Z;dfv6O0aQ=8QG?-11MxXDJeDaBsKM+5mulX6xO2M zkDq}XVzz8SD^LTd*6jp_Qkxrp!u2LHD_snfJ216cYoi zz%s(DeXMu|vybm(HpA{888hVfvHTlQ)v#K*^UpOjiJ@Knn^QaM{TtwT!)maF>Ol@| zbnTgQOtrX@KnlnSI32ow-E}`!aX*NQgyFYP0FT?daE*9M!3s$w3@@y9omZqqWLkSZ z^pTg~^g?Iiy2k2{i_ZS3G$c~)(Gj&4F&intt&|_cFM_wx>2f1mh`t63RhH*GF9BGI z?5oInNGYk26>!$9fRl~RdFzmMARC^;`uLoJorDYFqs9gFSuS?HtcmANb%ZZ8bOKpP zC?)!vsIT-1$NBr}Q4ym?TtJ+=J*nLVytPXC7Oev}SC-YjZr{-K`u~MLXS`fN9QCdL zi(yI|pyx`zswcl7&K;c_^F1C0E5aYpJiM00K$}jOwQPa+o50bJzYTd=N20Z@tHIyk zZ_LEQgF@!q;%-VO!Pgv+4r`(5bsDI#2wrF|muj^$(Q5}BZ4N+b!~s*yL9tt7yTKc2 zntfCBwtHXtjfZ0O^)(+FBYZCp(hp4^AMx{n A0{{R3 delta 2009 zcmZWqZ){Ul6ux*Y}u{*SL`3m}vZi1~H6Gu+RlQ zV8$%ERWbt{zo?M7#31-V98HX9NTwwGNi<@N1`RRW2h9|tTg12+u%7$=j5u%d&N;u| zIrrT2?mh3mE5meTm>v#Es-GA`H5C~-5_?w+RhcMs6Wh> zhUj0CM>c))8cIT?{@j=4cUHZ%cjM_@KWuuld*A6Z(#Lr?=6Hf;A@2-=-x;DokeosK zILCB{!5(OH(z{_?sD~5IVMwt0rWoe7Y9#}A_KAcDq{Up1OlM5PW#I(mSui|lg-6x@ zdZRGLR#FFi!GiEP^VH5{O+SBW2DgyC$vhxeg+T6bm7fkwibwq_bo*CmyA?ui9M(rO zXVK6{*%_|Z%oP^thu8*gzdTV8E0ey+0iN-^r7^x_#HU_@PyCDNGPvqrvZ50yGSJei z7c&Ak_0o@pTK!OFIfBZfnK&WSTPu~y1-2MuDLJTL?40Yuu9dv}rN&JzcR|dhu7+bfo;7 z6!5}?;6Ey}`u=BYcxKw*XiK1G_|!v8-oj4)Ua811DmVJ}@Bq#0e?{o1trB*Y&&$hd z`Sfr?;vPcmv$ML5#DK;lGzv@OTj&T3p?wg>;#nGksdz*pR=-x8lZgtFlBF_iR3g*^ zdF3>Ai#ei@OvJ=B)+&3j!-DsmMHeH~XddBV1b6jV(YND{CsKnBC+pi;z(B^^jnb;s zsGQHCU=N`Xx~S(-)UV=G#YHu7xt>QYK8S)k7fWhHJ^t1_vVYBtFLaG$QyM#=G|-Mf*_*%tJ)MuDYJp z6c4h1!rTSsdyL)T+Rmg)Of0fm*Bi{eA0BAl1jn0GaILw=9apw*>+jzc-%8>TZ)t|7 zT6TnAus>kT%*P6R1mY!c!&J-d@6~Xj;FVAkCJB&Q6`BC8b+gFNijn{})L3g{_@WV7 zM`o<=-+(Bt!c=Q8H)VP4+rjqUFbs*<*ObjyW3y3$o2=T5sn3I!3Wo1OZvbnR118Jc znvW*Pr-Hd6dSwzKvTBg?B8R*m`NRUgk!KTpBx+@|Cv?MDDhSi5PJ_J51v^S96ZajW33-peZI{~|21kq?&#_TvxcpB)jA)3AMtV7r~ywvHQ>DX z#)`N5f;SZ(-y@Gz1^zeTCtfrqurz&%8EpYGX(+$GdcRPvZQDxeNyx8Nq!Fi?#?}X6 z99fUG)3)})>{>;XEmnqDCM~KKQlT%Cp1%(4Yas;VnL#Kx{h+qXqWzK>+nyHf1Bk+@ z_VrYR*>**=8^Z&!EasNYs?>5{c0D!HOsKMGpNkAXt7*~Rp#;RVEKBfv5C3U(a6pUF zb#O|{in}?7;~dOtX;DdS+8IN04KZ diff --git a/hello.c b/hello.c index f82307a..c751dca 100644 --- a/hello.c +++ b/hello.c @@ -1,15 +1,6 @@ -#include +#include //printf and shit -int main() { - long x = 76; - - while(x != 1) { - if(x % 2 == 0) { - x /= 2; - } - else {x = (x * 3) + 1;} - printf("%ld\n", x); - } - - return 0; +int main(){ + printf("asd"); + return 0; } diff --git a/jame/src/olcExampleProgram b/jame/src/olcExampleProgram new file mode 100755 index 0000000000000000000000000000000000000000..a9dc74b9a97d75cff49edba1b0d7b57db8b8f972 GIT binary patch literal 458520 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 literal 0 HcmV?d00001 diff --git a/jame/src/olcExampleProgram.cpp b/jame/src/olcExampleProgram.cpp new file mode 100644 index 0000000..c3dc81b --- /dev/null +++ b/jame/src/olcExampleProgram.cpp @@ -0,0 +1,49 @@ +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include +#include + +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) { + + float idkwhy = fElapsedTime; + idkwhy = idkwhy * 0x40000000; + int cuck = (int)idkwhy; + + if (cuck % 2){ + Draw(x, y, olc::Pixel(cuck % 255, 69, 5)); + printf("%d\n", cuck); + } + else{Draw(x, y, olc::Pixel(5, 5, 5));} + } + return true; + } +}; + + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + + return 0; +} diff --git a/jame/src/olcPixelGameEngine.h b/jame/src/olcPixelGameEngine.h new file mode 100644 index 0000000..48a9be5 --- /dev/null +++ b/jame/src/olcPixelGameEngine.h @@ -0,0 +1,5603 @@ +#pragma region license_and_help +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v2.16 | + | "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 - 2021 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + Community: https://community.onelonecoder.com + + + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17 + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64. + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + Add these libraries to "Linker Options": + user32 gdi32 opengl32 gdiplus Shlwapi dwmapi stdc++fs + + Set these compiler options: -std=c++17 + + + + Compiling on Mac - EXPERIMENTAL! PROBABLY HAS BUGS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Yes yes, people use Macs for C++ programming! Who knew? Anyway, enough + arguing, thanks to Mumflr the PGE is now supported on Mac. Now I know nothing + about Mac, so if you need support, I suggest checking out the instructions + here: https://github.com/MumflrFumperdink/olcPGEMac + + clang++ -arch x86_64 -std=c++17 -mmacosx-version-min=10.15 -Wall -framework OpenGL + -framework GLUT -framework Carbon -lpng YourSource.cpp -o YourProgName + + + + Compiling with Emscripten (New & Experimental) + ~~~~~~~~~~~~~~~~~~~~~~~~~ + Emscripten compiler will turn your awesome C++ PixelGameEngine project into WASM! + This means you can run your application in teh browser, great for distributing + and submission in to jams and things! It's a bit new at the moment. + + em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 ./YourSource.cpp -o pge.html + + + + Using stb_image.h + ~~~~~~~~~~~~~~~~~ + The PGE will load png images by default (with help from libpng on non-windows systems). + However, the excellent "stb_image.h" can be used instead, supporting a variety of + image formats, and has no library dependence - something we like at OLC studios ;) + To use stb_image.h, make sure it's in your code base, and simply: + + #define OLC_IMAGE_STB + + Before including the olcPixelGameEngine.h header file. stb_image.h works on many systems + and can be downloaded here: https://github.com/nothings/stb/blob/master/stb_image.h + + + + Multiple cpp file projects? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + As a single header solution, the OLC_PGE_APPLICATION definition is used to + insert the engine implementation at a project location of your choosing. + The simplest way to setup multifile projects is to create a file called + "olcPixelGameEngine.cpp" which includes the following: + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + + That's all it should include. You can also include PGEX includes and + defines in here too. With this in place, you dont need to + #define OLC_PGE_APPLICATION anywhere, and can simply include this + header file as an when you need to. + + + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Raspberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + + + Thanks + ~~~~~~ + I'd like to extend thanks to Ian McKay, Bispoo, Eremiell, slavka, 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 + 230K YouTube followers, 80+ Patreons 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 + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020, 2021 +*/ +#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) + + + + !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! + !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! +*/ +#pragma endregion + +#pragma region hello_world_example +// O------------------------------------------------------------------------------O +// | Example "Hello World" Program (main.cpp) | +// O------------------------------------------------------------------------------O +/* + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Override base class with your custom functionality +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + // Name your application + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand() % 256)); + return true; + } +}; + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} + +*/ +#pragma endregion + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#pragma region std_includes +// O------------------------------------------------------------------------------O +// | STANDARD INCLUDES | +// O------------------------------------------------------------------------------O +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma endregion + +#define PGE_VER 216 + +// 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(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) + // C++14 + #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + #include + namespace _gfs = std::experimental::filesystem::v1; +#else + // C++17 + #include + namespace _gfs = std::filesystem; +#endif + +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define UNUSED(x) (void)(x) + +// O------------------------------------------------------------------------------O +// | PLATFORM SELECTION CODE, Thanks slavka! | +// O------------------------------------------------------------------------------O + +// Platform +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) + #if !defined(OLC_PLATFORM_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_PLATFORM_WINAPI + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #define OLC_PLATFORM_X11 + #endif + #if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #define OLC_PLATFORM_GLUT + #endif + #if defined(__EMSCRIPTEN__) + #define OLC_PLATFORM_EMSCRIPTEN + #endif + #endif +#endif + +// Start Situation +#if defined(OLC_PLATFORM_GLUT) || defined(OLC_PLATFORM_EMSCRIPTEN) + #define PGE_USE_CUSTOM_START +#endif + +// Renderer +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) + #if !defined(OLC_GFX_CUSTOM_EX) + #if defined(OLC_PLATFORM_EMSCRIPTEN) + #define OLC_GFX_OPENGL33 + #else + #define OLC_GFX_OPENGL10 + #endif + #endif +#endif + +// Image loader +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) + #if !defined(OLC_IMAGE_CUSTOM_EX) + #if defined(_WIN32) + #define OLC_IMAGE_GDI + #endif + #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + #define OLC_IMAGE_LIBPNG + #endif + #endif +#endif + + +// O------------------------------------------------------------------------------O +// | PLATFORM-SPECIFIC DEPENDENCIES | +// O------------------------------------------------------------------------------O +#if !defined(OLC_PGE_HEADLESS) +#if defined(OLC_PLATFORM_WINAPI) + #define _WINSOCKAPI_ // Thanks Cornchipss + #if !defined(VC_EXTRALEAN) + #define VC_EXTRALEAN + #endif + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + + // In Code::Blocks + #if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif + + #include + #undef _WINSOCKAPI_ +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + #include + } +#endif + +#if defined(OLC_PLATFORM_GLUT) + #if defined(__linux__) + #include + #include + #endif + #if defined(__APPLE__) + #include + #include + #include + #endif +#endif +#endif +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE DECLARATION | +// O------------------------------------------------------------------------------O +#pragma region pge_declaration +namespace olc +{ + class PixelGameEngine; + class Sprite; + + // Pixel Game Engine Advanced Configuration + constexpr uint8_t nMouseButtons = 5; + constexpr uint8_t nDefaultAlpha = 0xFF; + constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); + enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; + + // O------------------------------------------------------------------------------O + // | olc::Pixel - Represents a 32-Bit RGBA colour | + // O------------------------------------------------------------------------------O + struct Pixel + { + union + { + uint32_t n = nDefaultPixel; + struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; }; + }; + + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); + Pixel(uint32_t p); + Pixel& operator = (const Pixel& v) = default; + bool operator ==(const Pixel& p) const; + bool operator !=(const Pixel& p) const; + Pixel operator * (const float i) const; + Pixel operator / (const float i) const; + Pixel& operator *=(const float i); + Pixel& operator /=(const float i); + Pixel operator + (const Pixel& p) const; + Pixel operator - (const Pixel& p) const; + Pixel& operator +=(const Pixel& p); + Pixel& operator -=(const Pixel& p); + Pixel inv() const; + }; + + Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t); + + + // O------------------------------------------------------------------------------O + // | USEFUL CONSTANTS | + // O------------------------------------------------------------------------------O + static const Pixel + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + WHITE(255, 255, 255), BLACK(0, 0, 0), BLANK(0, 0, 0, 0); + + // Thanks to scripticuk and others for updating the key maps + // NOTE: The GLUT platform will need updating, open to contributions ;) + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + EQUALS, COMMA, MINUS, + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + CAPS_LOCK, ENUM_END + }; + + namespace Mouse + { + static constexpr int32_t LEFT = 0; + static constexpr int32_t RIGHT = 1; + static constexpr int32_t MIDDLE = 2; + }; + + // O------------------------------------------------------------------------------O + // | olc::HWButton - Represents the state of a hardware button (mouse/key/joy) | + // O------------------------------------------------------------------------------O + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + + + + // O------------------------------------------------------------------------------O + // | olc::vX2d - A generic 2D vector type | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_VEC2D) + template + struct v2d_generic + { + T x = 0; + T y = 0; + v2d_generic() : x(0), y(0) {} + v2d_generic(T _x, T _y) : x(_x), y(_y) {} + v2d_generic(const v2d_generic& v) : x(v.x), y(v.y) {} + v2d_generic& operator=(const v2d_generic& v) = default; + T mag() const { return T(std::sqrt(x * x + y * y)); } + T mag2() const { return x * x + y * y; } + v2d_generic norm() const { T r = 1 / mag(); return v2d_generic(x * r, y * r); } + v2d_generic perp() const { return v2d_generic(-y, x); } + v2d_generic floor() const { return v2d_generic(std::floor(x), std::floor(y)); } + v2d_generic ceil() const { return v2d_generic(std::ceil(x), std::ceil(y)); } + v2d_generic max(const v2d_generic& v) const { return v2d_generic(std::max(x, v.x), std::max(y, v.y)); } + v2d_generic min(const v2d_generic& v) const { return v2d_generic(std::min(x, v.x), std::min(y, v.y)); } + v2d_generic cart() { return { std::cos(y) * x, std::sin(y) * x }; } + v2d_generic polar() { return { mag(), std::atan2(y, x) }; } + T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } + T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } + v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } + v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y); } + v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + v2d_generic operator * (const v2d_generic& rhs) const { return v2d_generic(this->x * rhs.x, this->y * rhs.y); } + v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + v2d_generic operator / (const v2d_generic& rhs) const { return v2d_generic(this->x / rhs.x, this->y / rhs.y); } + v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } + v2d_generic& operator *= (const v2d_generic& rhs) { this->x *= rhs.x; this->y *= rhs.y; return *this; } + v2d_generic& operator /= (const v2d_generic& rhs) { this->x /= rhs.x; this->y /= rhs.y; return *this; } + v2d_generic operator + () const { return { +x, +y }; } + v2d_generic operator - () const { return { -x, -y }; } + bool operator == (const v2d_generic& rhs) const { return (this->x == rhs.x && this->y == rhs.y); } + bool operator != (const v2d_generic& rhs) const { return (this->x != rhs.x || this->y != rhs.y); } + const std::string str() const { return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + ")"; } + friend std::ostream& operator << (std::ostream& os, const v2d_generic& rhs) { os << rhs.str(); return os; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + // Note: joshinils has some good suggestions here, but they are complicated to implement at this moment, + // however they will appear in a future version of PGE + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); } + + // To stop dandistine crying... + template inline bool operator < (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y < rhs.y || (lhs.y == rhs.y && lhs.x < rhs.x); } + template inline bool operator > (const v2d_generic& lhs, const v2d_generic& rhs) + { return lhs.y > rhs.y || (lhs.y == rhs.y && lhs.x > rhs.x); } + + typedef v2d_generic vi2d; + typedef v2d_generic vu2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; +#endif + + + + + + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack - A virtual scrambled filesystem to pack your assets into | + // O------------------------------------------------------------------------------O + struct ResourceBuffer : public std::streambuf + { + ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size); + std::vector vMemory; + }; + + class ResourcePack : public std::streambuf + { + public: + ResourcePack(); + ~ResourcePack(); + bool AddFile(const std::string& sFile); + bool LoadPack(const std::string& sFile, const std::string& sKey); + bool SavePack(const std::string& sFile, const std::string& sKey); + ResourceBuffer GetFileBuffer(const std::string& sFile); + bool Loaded(); + private: + struct sResourceFile { uint32_t nSize; uint32_t nOffset; }; + std::map mapFiles; + std::ifstream baseFile; + std::vector scramble(const std::vector& data, const std::string& key); + std::string makeposix(const std::string& path); + }; + + + class ImageLoader + { + public: + ImageLoader() = default; + virtual ~ImageLoader() = default; + virtual olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) = 0; + virtual olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) = 0; + }; + + + // O------------------------------------------------------------------------------O + // | olc::Sprite - An image represented by a 2D array of olc::Pixel | + // O------------------------------------------------------------------------------O + class Sprite + { + public: + Sprite(); + Sprite(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + Sprite(int32_t w, int32_t h); + Sprite(const olc::Sprite&) = delete; + ~Sprite(); + + public: + olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + enum Flip { NONE = 0, HORIZ = 1, VERT = 2 }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y) const; + bool SetPixel(int32_t x, int32_t y, Pixel p); + Pixel GetPixel(const olc::vi2d& a) const; + bool SetPixel(const olc::vi2d& a, Pixel p); + Pixel Sample(float x, float y) const; + Pixel SampleBL(float u, float v) const; + Pixel* GetData(); + olc::Sprite* Duplicate(); + olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); + std::vector pColData; + Mode modeSample = Mode::NORMAL; + + static std::unique_ptr loader; + }; + + // O------------------------------------------------------------------------------O + // | olc::Decal - A GPU resident storage of an olc::Sprite | + // O------------------------------------------------------------------------------O + class Decal + { + public: + Decal(olc::Sprite* spr, bool filter = false, bool clamp = true); + Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr); + virtual ~Decal(); + void Update(); + void UpdateSprite(); + + public: // But dont touch + int32_t id = -1; + olc::Sprite* sprite = nullptr; + olc::vf2d vUVScale = { 1.0f, 1.0f }; + }; + + enum class DecalMode + { + NORMAL, + ADDITIVE, + MULTIPLICATIVE, + STENCIL, + ILLUMINATE, + WIREFRAME, + }; + + // O------------------------------------------------------------------------------O + // | olc::Renderable - Convenience class to keep a sprite and decal together | + // O------------------------------------------------------------------------------O + class Renderable + { + public: + Renderable() = default; + Renderable(Renderable&& r) : pSprite(std::move(r.pSprite)), pDecal(std::move(r.pDecal)) {} + Renderable(const Renderable&) = delete; + olc::rcode Load(const std::string& sFile, ResourcePack* pack = nullptr, bool filter = false, bool clamp = true); + void Create(uint32_t width, uint32_t height, bool filter = false, bool clamp = true); + olc::Decal* Decal() const; + olc::Sprite* Sprite() const; + + private: + std::unique_ptr pSprite = nullptr; + std::unique_ptr pDecal = nullptr; + }; + + + // O------------------------------------------------------------------------------O + // | Auxilliary components internal to engine | + // O------------------------------------------------------------------------------O + + struct DecalInstance + { + olc::Decal* decal = nullptr; + std::vector pos; + std::vector uv; + std::vector w; + std::vector tint; + olc::DecalMode mode = olc::DecalMode::NORMAL; + uint32_t points = 0; + }; + + struct LayerDesc + { + olc::vf2d vOffset = { 0, 0 }; + olc::vf2d vScale = { 1, 1 }; + bool bShow = false; + bool bUpdate = false; + olc::Renderable pDrawTarget; + uint32_t nResID = 0; + std::vector vecDecalInstance; + olc::Pixel tint = olc::WHITE; + std::function funcHook = nullptr; + }; + + class Renderer + { + public: + virtual ~Renderer() = default; + virtual void PrepareDevice() = 0; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) = 0; + virtual olc::rcode DestroyDevice() = 0; + virtual void DisplayFrame() = 0; + virtual void PrepareDrawing() = 0; + virtual void SetDecalMode(const olc::DecalMode& mode) = 0; + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0; + virtual void DrawDecal(const olc::DecalInstance& decal) = 0; + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) = 0; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual uint32_t DeleteTexture(const uint32_t id) = 0; + virtual void ApplyTexture(uint32_t id) = 0; + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) = 0; + virtual void ClearBuffer(olc::Pixel p, bool bDepth) = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class Platform + { + public: + virtual ~Platform() = default; + virtual olc::rcode ApplicationStartUp() = 0; + virtual olc::rcode ApplicationCleanUp() = 0; + virtual olc::rcode ThreadStartUp() = 0; + virtual olc::rcode ThreadCleanUp() = 0; + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) = 0; + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) = 0; + virtual olc::rcode SetWindowTitle(const std::string& s) = 0; + virtual olc::rcode StartSystemEventLoop() = 0; + virtual olc::rcode HandleSystemEvent() = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class PGEX; + + // The Static Twins (plus one) + static std::unique_ptr renderer; + static std::unique_ptr platform; + static std::map mapKeys; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine - The main BASE class for your application | + // O------------------------------------------------------------------------------O + class PixelGameEngine + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + public: + olc::rcode Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, + bool full_screen = false, bool vsync = false, bool cohesion = false); + olc::rcode Start(); + + public: // User Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be one clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused() const; + // Get the state of a specific keyboard button + HWButton GetKey(Key k) const; + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b) const; + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX() const; + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY() const; + // Get Mouse Wheel Delta + int32_t GetMouseWheel() const; + // Get the mouse in window space + const olc::vi2d& GetWindowMouse() const; + // Gets the mouse as a vector to keep Tarriest happy + const olc::vi2d& GetMousePos() const; + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth() const; + // Returns the height of the screen in "pixels" + int32_t ScreenHeight() const; + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth() const; + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight() const; + // Returns the currently active draw target + olc::Sprite* GetDrawTarget() const; + // Resize the primary screen sprite + void SetScreenSize(int w, int h); + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite* target); + // Gets the current Frames Per Second + uint32_t GetFPS() const; + // Gets last update of elapsed time + float GetElapsedTime() const; + // Gets Actual Window size + const olc::vi2d& GetWindowSize() const; + // Gets pixel scale + const olc::vi2d& GetPixelSize() const; + // Gets actual pixel scale + const olc::vi2d& GetScreenPixelSize() const; + + public: // CONFIGURATION ROUTINES + // Layer targeting functions + void SetDrawTarget(uint8_t layer); + void EnableLayer(uint8_t layer, bool b); + void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); + void SetLayerOffset(uint8_t layer, float x, float y); + void SetLayerScale(uint8_t layer, const olc::vf2d& scale); + void SetLayerScale(uint8_t layer, float x, float y); + void SetLayerTint(uint8_t layer, const olc::Pixel& tint); + void SetLayerCustomRenderFunction(uint8_t layer, std::function f); + + std::vector& GetLayers(); + uint32_t CreateLayer(); + + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor from between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + + + + public: // DRAWING ROUTINES + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + bool Draw(const olc::vi2d& pos, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws a single line of text - traditional monospaced + void DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSize(const std::string& s); + // Draws a single line of text - non-monospaced + void DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSizeProp(const std::string& s); + + // Decal Quad functions + void SetDecalMode(const olc::DecalMode& mode); + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours + void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); + // Draws a decal with 4 arbitrary points, warping the texture to look "correct" + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + // As above, but you can specify a region of a decal source sprite + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws a decal rotated to specified angle, wit point of rotation offset + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a multiline string as a decal, with tiniting and scaling + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Draws a single shaded filled rectangle as a decal + void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + // Draws a corner shaded rectangle as a decal + void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); + // Draws an arbitrary convex textured polygon using GPU + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + // Draws a line in Decal Space + void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); + void DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Clears entire draw target to Pixel + void Clear(Pixel p); + // Clears the rendering back buffer + void ClearBuffer(Pixel p, bool bDepth = true); + // Returns the font image + olc::Sprite* GetFontSprite(); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + olc::Sprite* pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + olc::vi2d vScreenSize = { 256, 240 }; + olc::vf2d vInvScreenSize = { 1.0f / 256.0f, 1.0f / 240.0f }; + olc::vi2d vPixelSize = { 4, 4 }; + olc::vi2d vScreenPixelSize = { 4, 4 }; + olc::vi2d vMousePos = { 0, 0 }; + int32_t nMouseWheelDelta = 0; + olc::vi2d vMousePosCache = { 0, 0 }; + olc::vi2d vMouseWindowPos = { 0, 0 }; + int32_t nMouseWheelDeltaCache = 0; + olc::vi2d vWindowSize = { 0, 0 }; + olc::vi2d vViewPos = { 0, 0 }; + olc::vi2d vViewSize = { 0,0 }; + bool bFullScreen = false; + olc::vf2d vPixel = { 1.0f, 1.0f }; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + float fLastElapsed = 0.0f; + int nFrameCount = 0; + Sprite* fontSprite = nullptr; + Decal* fontDecal = nullptr; + std::vector vLayers; + uint8_t nTargetLayer = 0; + uint32_t nLastFPS = 0; + bool bPixelCohesion = false; + DecalMode nDecalMode = DecalMode::NORMAL; + std::function funcPixelMode; + std::chrono::time_point m_tp1, m_tp2; + std::vector vFontSpacing; + + // State of keyboard + bool pKeyNewState[256] = { 0 }; + bool pKeyOldState[256] = { 0 }; + HWButton pKeyboardState[256] = { 0 }; + + // State of mouse + bool pMouseNewState[nMouseButtons] = { 0 }; + bool pMouseOldState[nMouseButtons] = { 0 }; + HWButton pMouseState[nMouseButtons] = { 0 }; + + // The main engine thread + void EngineThread(); + + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + public: + // "Break In" Functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + void olc_ConstructFontSheet(); + void olc_CoreUpdate(); + void olc_PrepareEngine(); + void olc_UpdateMouseState(int32_t button, bool state); + void olc_UpdateKeyState(int32_t key, bool state); + void olc_UpdateMouseFocus(bool state); + void olc_UpdateKeyFocus(bool state); + void olc_Terminate(); + void olc_Reanimate(); + bool olc_IsRunning(); + + // At the very end of this file, chooses which + // components to compile + virtual void olc_ConfigureSystem(); + + // NOTE: Items Here are to be deprecated, I have left them in for now + // in case you are using them, but they will be removed. + // olc::vf2d vSubPixelOffset = { 0.0f, 0.0f }; + + public: // PGEX Stuff + friend class PGEX; + void pgex_Register(olc::PGEX* pgex); + + private: + std::vector vExtensions; + }; + + + + // O------------------------------------------------------------------------------O + // | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension | + // O------------------------------------------------------------------------------O + class PGEX + { + friend class olc::PixelGameEngine; + public: + PGEX(bool bHook = false); + + protected: + virtual void OnBeforeUserCreate(); + virtual void OnAfterUserCreate(); + virtual void OnBeforeUserUpdate(float &fElapsedTime); + virtual void OnAfterUserUpdate(float fElapsedTime); + + protected: + static PixelGameEngine* pge; + }; +} + +#pragma endregion + +#endif // OLC_PGE_DEF + + +// O------------------------------------------------------------------------------O +// | START OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | +// | Note: The core implementation is platform independent | +// O------------------------------------------------------------------------------O +#pragma region pge_implementation +namespace olc +{ + // O------------------------------------------------------------------------------O + // | olc::Pixel IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Pixel::Pixel() + { r = 0; g = 0; b = 0; a = nDefaultAlpha; } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { n = red | (green << 8) | (blue << 16) | (alpha << 24); } // Thanks jarekpelczar + + Pixel::Pixel(uint32_t p) + { n = p; } + + bool Pixel::operator==(const Pixel& p) const + { return n == p.n; } + + bool Pixel::operator!=(const Pixel& p) const + { return n != p.n; } + + Pixel Pixel::operator * (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) * i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) * i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) * i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel Pixel::operator / (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) / i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) / i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) / i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel& Pixel::operator *=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * i))); + return *this; + } + + Pixel& Pixel::operator /=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) / i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) / i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) / i))); + return *this; + } + + Pixel Pixel::operator + (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel Pixel::operator - (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel& Pixel::operator += (const Pixel& p) + { + this->r = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return *this; + } + + Pixel& Pixel::operator -= (const Pixel& p) // Thanks Au Lit + { + this->r = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return *this; + } + + Pixel Pixel::inv() const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, 255 - int(r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, 255 - int(g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, 255 - int(b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel PixelF(float red, float green, float blue, float alpha) + { return Pixel(uint8_t(red * 255.0f), uint8_t(green * 255.0f), uint8_t(blue * 255.0f), uint8_t(alpha * 255.0f)); } + + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t) + { return (p2 * t) + p1 * (1.0f - t); } + + // O------------------------------------------------------------------------------O + // | olc::Sprite IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Sprite::Sprite() + { width = 0; height = 0; } + + Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack* pack) + { LoadFromFile(sImageFile, pack); } + + Sprite::Sprite(int32_t w, int32_t h) + { + width = w; height = h; + pColData.resize(width * height); + pColData.resize(width * height, nDefaultPixel); + } + + Sprite::~Sprite() + { pColData.clear(); } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { modeSample = mode; } + + Pixel Sprite::GetPixel(const olc::vi2d& a) const + { return GetPixel(a.x, a.y); } + + bool Sprite::SetPixel(const olc::vi2d& a, Pixel p) + { return SetPixel(a.x, a.y, p); } + + Pixel Sprite::GetPixel(int32_t x, int32_t y) const + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y * width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y % height) * width + abs(x % width)]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y * width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) const + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) const + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() + { return pColData.data(); } + + + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack) + { + UNUSED(pack); + return loader->LoadImageResource(this, sImageFile, pack); + } + + olc::Sprite* Sprite::Duplicate() + { + olc::Sprite* spr = new olc::Sprite(width, height); + std::memcpy(spr->GetData(), GetData(), width * height * sizeof(olc::Pixel)); + spr->modeSample = modeSample; + return spr; + } + + olc::Sprite* Sprite::Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize) + { + olc::Sprite* spr = new olc::Sprite(vSize.x, vSize.y); + for (int y = 0; y < vSize.y; y++) + for (int x = 0; x < vSize.x; x++) + spr->SetPixel(x, y, GetPixel(vPos.x + x, vPos.y + y)); + return spr; + } + + // O------------------------------------------------------------------------------O + // | olc::Decal IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Decal::Decal(olc::Sprite* spr, bool filter, bool clamp) + { + id = -1; + if (spr == nullptr) return; + sprite = spr; + id = renderer->CreateTexture(sprite->width, sprite->height, filter, clamp); + Update(); + } + + Decal::Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr) + { + if (spr == nullptr) return; + id = nExistingTextureResource; + } + + void Decal::Update() + { + if (sprite == nullptr) return; + vUVScale = { 1.0f / float(sprite->width), 1.0f / float(sprite->height) }; + renderer->ApplyTexture(id); + renderer->UpdateTexture(id, sprite); + } + + void Decal::UpdateSprite() + { + if (sprite == nullptr) return; + renderer->ApplyTexture(id); + renderer->ReadTexture(id, sprite); + } + + Decal::~Decal() + { + if (id != -1) + { + renderer->DeleteTexture(id); + id = -1; + } + } + + void Renderable::Create(uint32_t width, uint32_t height, bool filter, bool clamp) + { + pSprite = std::make_unique(width, height); + pDecal = std::make_unique(pSprite.get(), filter, clamp); + } + + olc::rcode Renderable::Load(const std::string& sFile, ResourcePack* pack, bool filter, bool clamp) + { + pSprite = std::make_unique(); + if (pSprite->LoadFromFile(sFile, pack) == olc::rcode::OK) + { + pDecal = std::make_unique(pSprite.get(), filter, clamp); + return olc::rcode::OK; + } + else + { + pSprite.release(); + pSprite = nullptr; + return olc::rcode::NO_FILE; + } + } + + olc::Decal* Renderable::Decal() const + { return pDecal.get(); } + + olc::Sprite* Renderable::Sprite() const + { return pSprite.get(); } + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack IMPLEMENTATION | + // O------------------------------------------------------------------------------O + + + //============================================================= + // Resource Packs - Allows you to store files in one large + // scrambled file - Thanks MaGetzUb for debugging a null char in std::stringstream bug + ResourceBuffer::ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size) + { + vMemory.resize(size); + ifs.seekg(offset); ifs.read(vMemory.data(), vMemory.size()); + setg(vMemory.data(), vMemory.data(), vMemory.data() + size); + } + + ResourcePack::ResourcePack() { } + ResourcePack::~ResourcePack() { baseFile.close(); } + + bool ResourcePack::AddFile(const std::string& sFile) + { + const std::string file = makeposix(sFile); + + if (_gfs::exists(file)) + { + sResourceFile e; + e.nSize = (uint32_t)_gfs::file_size(file); + e.nOffset = 0; // Unknown at this stage + mapFiles[file] = e; + return true; + } + return false; + } + + bool ResourcePack::LoadPack(const std::string& sFile, const std::string& sKey) + { + // Open the resource file + baseFile.open(sFile, std::ifstream::binary); + if (!baseFile.is_open()) return false; + + // 1) Read Scrambled index + uint32_t nIndexSize = 0; + baseFile.read((char*)&nIndexSize, sizeof(uint32_t)); + + std::vector buffer(nIndexSize); + for (uint32_t j = 0; j < nIndexSize; j++) + buffer[j] = baseFile.get(); + + std::vector decoded = scramble(buffer, sKey); + size_t pos = 0; + auto read = [&decoded, &pos](char* dst, size_t size) { + memcpy((void*)dst, (const void*)(decoded.data() + pos), size); + pos += size; + }; + + auto get = [&read]() -> int { char c; read(&c, 1); return c; }; + + // 2) Read Map + uint32_t nMapEntries = 0; + read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = get(); + + sResourceFile e; + read((char*)&e.nSize, sizeof(uint32_t)); + read((char*)&e.nOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // Don't close base file! we will provide a stream + // pointer when the file is requested + return true; + } + + bool ResourcePack::SavePack(const std::string& sFile, const std::string& sKey) + { + // Create/Overwrite the resource file + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return false; + + // Iterate through map + uint32_t nIndexSize = 0; // Unknown for now + ofs.write((char*)&nIndexSize, sizeof(uint32_t)); + uint32_t nMapSize = uint32_t(mapFiles.size()); + ofs.write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(uint32_t)); + ofs.write(e.first.c_str(), nPathSize); + + // Write the file entry properties + ofs.write((char*)&e.second.nSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + + // 2) Write the individual Data + std::streampos offset = ofs.tellp(); + nIndexSize = (uint32_t)offset; + for (auto& e : mapFiles) + { + // Store beginning of file offset within resource pack file + e.second.nOffset = (uint32_t)offset; + + // Load the file to be added + std::vector vBuffer(e.second.nSize); + std::ifstream i(e.first, std::ifstream::binary); + i.read((char*)vBuffer.data(), e.second.nSize); + i.close(); + + // Write the loaded file into resource pack file + ofs.write((char*)vBuffer.data(), e.second.nSize); + offset += e.second.nSize; + } + + // 3) Scramble Index + std::vector stream; + auto write = [&stream](const char* data, size_t size) { + size_t sizeNow = stream.size(); + stream.resize(sizeNow + size); + memcpy(stream.data() + sizeNow, data, size); + }; + + // Iterate through map + write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + write((char*)&nPathSize, sizeof(uint32_t)); + write(e.first.c_str(), nPathSize); + + // Write the file entry properties + write((char*)&e.second.nSize, sizeof(uint32_t)); + write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + std::vector sIndexString = scramble(stream, sKey); + uint32_t nIndexStringLen = uint32_t(sIndexString.size()); + // 4) Rewrite Map (it has been updated with offsets now) + // at start of file + ofs.seekp(0, std::ios::beg); + ofs.write((char*)&nIndexStringLen, sizeof(uint32_t)); + ofs.write(sIndexString.data(), nIndexStringLen); + ofs.close(); + return true; + } + + ResourceBuffer ResourcePack::GetFileBuffer(const std::string& sFile) + { return ResourceBuffer(baseFile, mapFiles[sFile].nOffset, mapFiles[sFile].nSize); } + + bool ResourcePack::Loaded() + { return baseFile.is_open(); } + + std::vector ResourcePack::scramble(const std::vector& data, const std::string& key) + { + if (key.empty()) return data; + std::vector o; + size_t c = 0; + for (auto s : data) o.push_back(s ^ key[(c++) % key.size()]); + return o; + }; + + std::string ResourcePack::makeposix(const std::string& path) + { + std::string o; + for (auto s : path) o += std::string(1, s == '\\' ? '/' : s); + return o; + }; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine IMPLEMENTATION | + // O------------------------------------------------------------------------------O + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + + // Bring in relevant Platform & Rendering systems depending + // on compiler parameters + olc_ConfigureSystem(); + } + + PixelGameEngine::~PixelGameEngine() + {} + + + olc::rcode PixelGameEngine::Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, bool full_screen, bool vsync, bool cohesion) + { + bPixelCohesion = cohesion; + vScreenSize = { screen_w, screen_h }; + vInvScreenSize = { 1.0f / float(screen_w), 1.0f / float(screen_h) }; + vPixelSize = { pixel_w, pixel_h }; + vWindowSize = vScreenSize * vPixelSize; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + vPixel = 2.0f / vScreenSize; + + if (vPixelSize.x <= 0 || vPixelSize.y <= 0 || vScreenSize.x <= 0 || vScreenSize.y <= 0) + return olc::FAIL; + return olc::OK; + } + + + void PixelGameEngine::SetScreenSize(int w, int h) + { + vScreenSize = { w, h }; + vInvScreenSize = { 1.0f / float(w), 1.0f / float(h) }; + for (auto& layer : vLayers) + { + layer.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + layer.bUpdate = true; + } + SetDrawTarget(nullptr); + renderer->ClearBuffer(olc::BLACK, true); + renderer->DisplayFrame(); + renderer->ClearBuffer(olc::BLACK, true); + renderer->UpdateViewport(vViewPos, vViewSize); + } + +#if !defined(PGE_USE_CUSTOM_START) + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + + // Some implementations may form an event loop here + platform->StartSystemEventLoop(); + + // Wait for thread to be exited + t.join(); + + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +#endif + + void PixelGameEngine::SetDrawTarget(Sprite* target) + { + if (target) + { + pDrawTarget = target; + } + else + { + nTargetLayer = 0; + pDrawTarget = vLayers[0].pDrawTarget.Sprite(); + } + } + + void PixelGameEngine::SetDrawTarget(uint8_t layer) + { + if (layer < vLayers.size()) + { + pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); + vLayers[layer].bUpdate = true; + nTargetLayer = layer; + } + } + + void PixelGameEngine::EnableLayer(uint8_t layer, bool b) + { if (layer < vLayers.size()) vLayers[layer].bShow = b; } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, const olc::vf2d& offset) + { SetLayerOffset(layer, offset.x, offset.y); } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vOffset = { x, y }; } + + void PixelGameEngine::SetLayerScale(uint8_t layer, const olc::vf2d& scale) + { SetLayerScale(layer, scale.x, scale.y); } + + void PixelGameEngine::SetLayerScale(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vScale = { x, y }; } + + void PixelGameEngine::SetLayerTint(uint8_t layer, const olc::Pixel& tint) + { if (layer < vLayers.size()) vLayers[layer].tint = tint; } + + void PixelGameEngine::SetLayerCustomRenderFunction(uint8_t layer, std::function f) + { if (layer < vLayers.size()) vLayers[layer].funcHook = f; } + + std::vector& PixelGameEngine::GetLayers() + { return vLayers; } + + uint32_t PixelGameEngine::CreateLayer() + { + LayerDesc ld; + ld.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + vLayers.push_back(std::move(ld)); + return uint32_t(vLayers.size()) - 1; + } + + Sprite* PixelGameEngine::GetDrawTarget() const + { return pDrawTarget; } + + int32_t PixelGameEngine::GetDrawTargetWidth() const + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() const + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + uint32_t PixelGameEngine::GetFPS() const + { return nLastFPS; } + + bool PixelGameEngine::IsFocused() const + { return bHasInputFocus; } + + HWButton PixelGameEngine::GetKey(Key k) const + { return pKeyboardState[k]; } + + HWButton PixelGameEngine::GetMouse(uint32_t b) const + { return pMouseState[b]; } + + int32_t PixelGameEngine::GetMouseX() const + { return vMousePos.x; } + + int32_t PixelGameEngine::GetMouseY() const + { return vMousePos.y; } + + const olc::vi2d& PixelGameEngine::GetMousePos() const + { return vMousePos; } + + int32_t PixelGameEngine::GetMouseWheel() const + { return nMouseWheelDelta; } + + int32_t PixelGameEngine::ScreenWidth() const + { return vScreenSize.x; } + + int32_t PixelGameEngine::ScreenHeight() const + { return vScreenSize.y; } + + float PixelGameEngine::GetElapsedTime() const + { return fLastElapsed; } + + const olc::vi2d& PixelGameEngine::GetWindowSize() const + { return vWindowSize; } + + const olc::vi2d& PixelGameEngine::GetPixelSize() const + { return vPixelSize; } + + const olc::vi2d& PixelGameEngine::GetScreenPixelSize() const + { return vScreenPixelSize; } + + const olc::vi2d& PixelGameEngine::GetWindowMouse() const + { return vMouseWindowPos; } + + bool PixelGameEngine::Draw(const olc::vi2d& pos, Pixel p) + { return Draw(pos.x, pos.y, p); } + + // This is it, the critical function that plots a pixel + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if (p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b/*, (uint8_t)(p.a * fBlendFactor)*/)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + + void PixelGameEngine::DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p, uint32_t pattern) + { DrawLine(pos1.x, pos1.y, pos2.x, pos2.y, p, pattern); } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) { pattern = (pattern << 1) | (pattern >> 31); return pattern & 1; }; + + // 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 fontSprite; } + + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { FillRect(pos.x, pos.y, size.x, size.y, p); } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)GetDrawTargetWidth()) x = (int32_t)GetDrawTargetWidth(); + if (y < 0) y = 0; + if (y >= (int32_t)GetDrawTargetHeight()) y = (int32_t)GetDrawTargetHeight(); + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)GetDrawTargetWidth()) x2 = (int32_t)GetDrawTargetWidth(); + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)GetDrawTargetHeight()) y2 = (int32_t)GetDrawTargetHeight(); + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { DrawTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + void PixelGameEngine::FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { FillTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1 > y2) { std::swap(y1, y2); std::swap(x1, x2); } + if (y1 > y3) { std::swap(y1, y3); std::swap(x1, x3); } + if (y2 > y3) { std::swap(y2, y3); std::swap(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); + if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); + if (dx2 < 0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { std::swap(dx1, dy1); changed1 = true; } + if (dy2 > dx2) { std::swap(dy2, dx2); changed2 = true; } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + i++; + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y == y2) break; + } + next: + // Second half + dx1 = (int)(x3 - x2); if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y3 - y2); + t1x = x2; + + if (dy1 > dx1) { // swap values + std::swap(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i < dx1) i++; + } + next3: + // process second line until y value is about to change + while (t2x != x3) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y > y3) return; + } + } + + void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip) + { DrawSprite(pos.x, pos.y, sprite, scale, flip); } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = sprite->width - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = sprite->height - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx, fy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx, fy)); + } + } + } + + void PixelGameEngine::DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale, uint8_t flip) + { DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip); } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = w - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = h - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx + ox, fy + oy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx + ox, fy + oy)); + } + } + } + + void PixelGameEngine::SetDecalMode(const olc::DecalMode& mode) + { nDecalMode = mode; } + + void PixelGameEngine::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 = + { + (std::floor(pos.x) * vInvScreenSize.x) * 2.0f - 1.0f, + ((std::floor(pos.y) * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * source_size.x * vInvScreenSize.x) * scale.x, + vScreenSpacePos.y - (2.0f * source_size.y * vInvScreenSize.y) * scale.y + }; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + 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 = + { + (std::floor(pos.x) * vInvScreenSize.x) * 2.0f - 1.0f, + ((std::floor(pos.y) * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * size.x * vInvScreenSize.x), + vScreenSpacePos.y - (2.0f * size.y * vInvScreenSize.y) + }; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + olc::vf2d uvtl = (source_pos) * decal->vUVScale; + olc::vf2d uvbr = uvtl + ((source_size) * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.mode = nDecalMode; + 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 = + { + (std::floor(pos.x) * vInvScreenSize.x) * 2.0f - 1.0f, + ((std::floor(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; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(elements); + di.uv.resize(elements); + di.w.resize(elements); + di.tint.resize(elements); + di.points = elements; + for (uint32_t i = 0; i < elements; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = col[i]; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + } + di.mode = nDecalMode; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) + { + DecalInstance di; + di.decal = nullptr; + di.points = uint32_t(2); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + di.pos[0] = { (pos1.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos1.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[0] = { 0.0f, 0.0f }; + di.tint[0] = p; + di.w[0] = 1.0f; + di.pos[1] = { (pos2.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos2.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[1] = { 0.0f, 0.0f }; + di.tint[1] = p; + di.w[1] = 1.0f; + di.mode = olc::DecalMode::WIREFRAME; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + 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 = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) + { + std::array points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {colTL, colBL, colBR, colTR} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.tint = { tint, tint, tint, tint }; + di.points = 4; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, float(decal->sprite->height)) - center) * scale; + di.pos[2] = (olc::vf2d(float(decal->sprite->width), float(decal->sprite->height)) - center) * scale; + di.pos[3] = (olc::vf2d(float(decal->sprite->width), 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + di.w[i] = 1; + } + di.mode = nDecalMode; + 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; + 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; + 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; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { DrawWarpedDecal(decal, pos.data(), tint); } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { DrawWarpedDecal(decal, &pos[0], tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); } + + void PixelGameEngine::DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x += 8.0f * scale.x; + } + } + } + + void PixelGameEngine::DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x += float(vFontSpacing[c - 32].y) * scale.x; + } + } + } + // Thanks Oso-Grande/Sopadeoso For these awesom and stupidly clever Text Rotation routines... duh XD + void PixelGameEngine::DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + 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 + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x -= float(vFontSpacing[c - 32].y); + } + } + } + + olc::vi2d PixelGameEngine::GetTextSize(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y++; pos.x = 0; } + else pos.x++; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + return size * 8; + } + + void PixelGameEngine::DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawString(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + // Thanks @tucna, spotted bug with col.ALPHA :P + if (m != Pixel::CUSTOM) // Thanks @Megarev, required for "shaders" + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + olc::vi2d PixelGameEngine::GetTextSizeProp(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y += 1; pos.x = 0; } + else pos.x += vFontSpacing[c - 32].y; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + + size.y *= 8; + return size; + } + + void PixelGameEngine::DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawStringProp(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + + if (m != Pixel::CUSTOM) + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + for (int32_t is = 0; is < int(scale); is++) + for (int32_t js = 0; js < int(scale); js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += vFontSpacing[c - 32].y * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { nPixelMode = m; } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { return nPixelMode; } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // 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::olc_UpdateViewport() + { + int32_t ww = vScreenSize.x * vPixelSize.x; + int32_t wh = vScreenSize.y * vPixelSize.y; + float wasp = (float)ww / (float)wh; + + if (bPixelCohesion) + { + vScreenPixelSize = (vWindowSize / vScreenSize); + vViewSize = (vWindowSize / vScreenSize) * vScreenSize; + } + else + { + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / wasp); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * wasp); + } + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + vWindowSize = { x, y }; + olc_UpdateViewport(); + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { nMouseWheelDeltaCache += delta; } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + bHasMouseFocus = true; + vMouseWindowPos = { x, y }; + // Full Screen mode may have a weird viewport we must clamp to + x -= vViewPos.x; + y -= vViewPos.y; + vMousePosCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vMousePosCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vMousePosCache.x >= (int32_t)vScreenSize.x) vMousePosCache.x = vScreenSize.x - 1; + if (vMousePosCache.y >= (int32_t)vScreenSize.y) vMousePosCache.y = vScreenSize.y - 1; + if (vMousePosCache.x < 0) vMousePosCache.x = 0; + if (vMousePosCache.y < 0) vMousePosCache.y = 0; + } + + void PixelGameEngine::olc_UpdateMouseState(int32_t button, bool state) + { pMouseNewState[button] = state; } + + void PixelGameEngine::olc_UpdateKeyState(int32_t key, bool state) + { pKeyNewState[key] = state; } + + void PixelGameEngine::olc_UpdateMouseFocus(bool state) + { bHasMouseFocus = state; } + + void PixelGameEngine::olc_UpdateKeyFocus(bool state) + { bHasInputFocus = state; } + + void PixelGameEngine::olc_Reanimate() + { bAtomActive = true; } + + bool PixelGameEngine::olc_IsRunning() + { return bAtomActive; } + + void PixelGameEngine::olc_Terminate() + { bAtomActive = false; } + + void PixelGameEngine::EngineThread() + { + // Allow platform to do stuff here if needed, since its now in the + // context of this thread + if (platform->ThreadStartUp() == olc::FAIL) return; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) { olc_CoreUpdate(); } + + // Allow the user to free resources if they have overrided the destroy function + if (!OnUserDestroy()) + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + + platform->ThreadCleanUp(); + } + + void PixelGameEngine::olc_PrepareEngine() + { + // Start OpenGL, the context is owned by the game thread + if (platform->CreateGraphics(bFullScreen, bEnableVSYNC, vViewPos, vViewSize) == olc::FAIL) return; + + // Construct default font sheet + olc_ConstructFontSheet(); + + // Create Primary Layer "0" + CreateLayer(); + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDrawTarget(nullptr); + + m_tp1 = std::chrono::system_clock::now(); + m_tp2 = std::chrono::system_clock::now(); + } + + + void PixelGameEngine::olc_CoreUpdate() + { + // Handle Timing + m_tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = m_tp2 - m_tp1; + m_tp1 = m_tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + fLastElapsed = fElapsedTime; + + // Some platforms will need to check for events + platform->HandleSystemEvent(); + + // Compare hardware input states from previous frame + auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) + { + for (uint32_t i = 0; i < nKeyCount; i++) + { + pKeys[i].bPressed = false; + pKeys[i].bReleased = false; + if (pStateNew[i] != pStateOld[i]) + { + if (pStateNew[i]) + { + pKeys[i].bPressed = !pKeys[i].bHeld; + pKeys[i].bHeld = true; + } + else + { + pKeys[i].bReleased = true; + pKeys[i].bHeld = false; + } + } + pStateOld[i] = pStateNew[i]; + } + }; + + ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); + ScanHardware(pMouseState, pMouseOldState, pMouseNewState, nMouseButtons); + + // Cache mouse coordinates so they remain consistent during frame + vMousePos = vMousePosCache; + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + + // renderer->ClearBuffer(olc::BLACK, true); + + // Handle Frame Update + for (auto& ext : vExtensions) ext->OnBeforeUserUpdate(fElapsedTime); + if (!OnUserUpdate(fElapsedTime)) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); + + // 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 (layer->bUpdate) + { + layer->pDrawTarget.Decal()->Update(); + layer->bUpdate = false; + } + + renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); + + // Display Decals in order for this layer + for (auto& decal : layer->vecDecalInstance) + renderer->DrawDecal(decal); + layer->vecDecalInstance.clear(); + } + else + { + // Mwa ha ha.... Have Fun!!! + layer->funcHook(); + } + } + } + + // Present Graphics to screen + renderer->DisplayFrame(); + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + nLastFPS = nFrameCount; + fFrameTimer -= 1.0f; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); + platform->SetWindowTitle(sTitle); + nFrameCount = 0; + } + } + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + + fontDecal = new olc::Decal(fontSprite); + + constexpr std::array vSpacing = { { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00, } }; + + for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); + + } + + 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() {} + void PGEX::OnBeforeUserUpdate(float& fElapsedTime) {} + void PGEX::OnAfterUserUpdate(float fElapsedTime) {} + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; + olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr; + olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr; + std::unique_ptr olc::Sprite::loader = nullptr; +}; +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Renderers - the draw-y bits | +// O------------------------------------------------------------------------------O + +#if !defined(OLC_PGE_HEADLESS) + +#pragma region renderer_ogl10 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL10) + +#if defined(OLC_PLATFORM_WINAPI) + #include + #include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t* wglSwapInterval = nullptr; + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + #include +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + } + typedef int(glSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + static glSwapInterval_t* glSwapIntervalEXT; + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; +#endif + +#if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include +#endif + +namespace olc +{ + class Renderer_OGL10 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; +#endif + + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo + +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bVSYNC) wglSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + if (glSwapIntervalEXT == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (glSwapIntervalEXT != nullptr && !bVSYNC) + glSwapIntervalEXT(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + 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; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + glBegin(GL_QUADS); + glColor4ub(tint.r, tint.g, tint.b, tint.a); + glTexCoord2f(0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glEnd(); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, 0); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + if (nDecalMode == DecalMode::WIREFRAME) + glBegin(GL_LINE_LOOP); + else + glBegin(GL_TRIANGLE_FAN); + + 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(); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region renderer_ogl33 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL33) + +#if defined(OLC_PLATFORM_WINAPI) + #include + #include + #if !defined(__MINGW32__) + #pragma comment(lib, "Dwmapi.lib") + #endif + typedef void __stdcall locSwapInterval_t(GLsizei n); + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; + #define CALLSTYLE __stdcall + #define OGL_LOAD(t, n) (t*)wglGetProcAddress(#n) +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + #include +#endif + +#if defined(OLC_PLATFORM_X11) + namespace X11 + { + #include + } + typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; + #define CALLSTYLE + #define OGL_LOAD(t, n) (t*)glXGetProcAddress((unsigned char*)#n); +#endif + +#if defined(__APPLE__) + #define GL_SILENCE_DEPRECATION + #include + #include + #include +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #include + #define CALLSTYLE + typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); + #define GL_CLAMP GL_CLAMP_TO_EDGE + #define OGL_LOAD(t, n) n; +#endif + +namespace olc +{ + typedef char GLchar; + typedef ptrdiff_t GLsizeiptr; + typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); + typedef GLuint CALLSTYLE locCreateProgram_t(void); + typedef void CALLSTYLE locDeleteShader_t(GLuint shader); +#if defined(OLC_PLATFORM_EMSCRIPTEN) + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); +#else + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +#endif + typedef void CALLSTYLE locCompileShader_t(GLuint shader); + typedef void CALLSTYLE locLinkProgram_t(GLuint program); + typedef void CALLSTYLE locDeleteProgram_t(GLuint program); + typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); + typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); + typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); + typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); + typedef void CALLSTYLE locUseProgram_t(GLuint program); + typedef void CALLSTYLE locBindVertexArray_t(GLuint array); + typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); + typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + + constexpr size_t OLC_MAX_VERTS = 128; + + class Renderer_OGL33 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLDisplay olc_Display; + EGLConfig olc_Config; + EGLContext olc_Context; + EGLSurface olc_Surface; +#endif + +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; + #endif +#endif + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + private: + locCreateShader_t* locCreateShader = nullptr; + locShaderSource_t* locShaderSource = nullptr; + locCompileShader_t* locCompileShader = nullptr; + locDeleteShader_t* locDeleteShader = nullptr; + locCreateProgram_t* locCreateProgram = nullptr; + locDeleteProgram_t* locDeleteProgram = nullptr; + locLinkProgram_t* locLinkProgram = nullptr; + locAttachShader_t* locAttachShader = nullptr; + locBindBuffer_t* locBindBuffer = nullptr; + locBufferData_t* locBufferData = nullptr; + locGenBuffers_t* locGenBuffers = nullptr; + locVertexAttribPointer_t* locVertexAttribPointer = nullptr; + locEnableVertexAttribArray_t* locEnableVertexAttribArray = nullptr; + locUseProgram_t* locUseProgram = nullptr; + locBindVertexArray_t* locBindVertexArray = nullptr; + locGenVertexArrays_t* locGenVertexArrays = nullptr; + locSwapInterval_t* locSwapInterval = nullptr; + locGetShaderInfoLog_t* locGetShaderInfoLog = nullptr; + + uint32_t m_nFS = 0; + uint32_t m_nVS = 0; + uint32_t m_nQuadShader = 0; + uint32_t m_vbQuad = 0; + uint32_t m_vaQuad = 0; + + struct locVertex + { + float pos[3]; + olc::vf2d tex; + olc::Pixel col; + }; + + locVertex pVertexMem[OLC_MAX_VERTS]; + + olc::Renderable rendBlankQuad; + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { + // Create OpenGL Context +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Set Vertical Sync + locSwapInterval = OGL_LOAD(locSwapInterval_t, "wglSwapIntervalEXT"); + if (locSwapInterval && !bVSYNC) locSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + locSwapInterval = OGL_LOAD(locSwapInterval_t, "glXSwapIntervalEXT"); + + if (locSwapInterval == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (locSwapInterval != nullptr && !bVSYNC) + locSwapInterval(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLint const attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; + EGLint const context_config[] = { EGL_CONTEXT_CLIENT_VERSION , 2, EGL_NONE }; + EGLint num_config; + + olc_Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(olc_Display, nullptr, nullptr); + eglChooseConfig(olc_Display, attribute_list, &olc_Config, 1, &num_config); + + /* create an EGL rendering context */ + olc_Context = eglCreateContext(olc_Display, olc_Config, EGL_NO_CONTEXT, context_config); + olc_Surface = eglCreateWindowSurface(olc_Display, olc_Config, NULL, nullptr); + eglMakeCurrent(olc_Display, olc_Surface, olc_Surface, olc_Context); + //eglSwapInterval is currently a NOP, plement anyways in case it becomes supported + locSwapInterval = &eglSwapInterval; + locSwapInterval(olc_Display, bVSYNC ? 1 : 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + #if !defined(OLC_PLATFORM_EMSCRIPTEN) + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + #endif +#endif + // Load External OpenGL Functions + locCreateShader = OGL_LOAD(locCreateShader_t, glCreateShader); + locCompileShader = OGL_LOAD(locCompileShader_t, glCompileShader); + locShaderSource = OGL_LOAD(locShaderSource_t, glShaderSource); + locDeleteShader = OGL_LOAD(locDeleteShader_t, glDeleteShader); + locCreateProgram = OGL_LOAD(locCreateProgram_t, glCreateProgram); + locDeleteProgram = OGL_LOAD(locDeleteProgram_t, glDeleteProgram); + locLinkProgram = OGL_LOAD(locLinkProgram_t, glLinkProgram); + locAttachShader = OGL_LOAD(locAttachShader_t, glAttachShader); + locBindBuffer = OGL_LOAD(locBindBuffer_t, glBindBuffer); + locBufferData = OGL_LOAD(locBufferData_t, glBufferData); + locGenBuffers = OGL_LOAD(locGenBuffers_t, glGenBuffers); + locVertexAttribPointer = OGL_LOAD(locVertexAttribPointer_t, glVertexAttribPointer); + locEnableVertexAttribArray = OGL_LOAD(locEnableVertexAttribArray_t, glEnableVertexAttribArray); + locUseProgram = OGL_LOAD(locUseProgram_t, glUseProgram); + locGetShaderInfoLog = OGL_LOAD(locGetShaderInfoLog_t, glGetShaderInfoLog); +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + locBindVertexArray = OGL_LOAD(locBindVertexArray_t, glBindVertexArray); + locGenVertexArrays = OGL_LOAD(locGenVertexArrays_t, glGenVertexArrays); +#else + locBindVertexArray = glBindVertexArrayOES; + locGenVertexArrays = glGenVertexArraysOES; +#endif + + // Load & Compile Quad Shader - assumes no errors + m_nFS = locCreateShader(0x8B30); + const GLchar* strFS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "out vec4 pixel;\n""in vec2 oTex;\n" + "in vec4 oCol;\n""uniform sampler2D sprTex;\n""void main(){pixel = texture(sprTex, oTex) * oCol;}"; + locShaderSource(m_nFS, 1, &strFS, NULL); + locCompileShader(m_nFS); + + m_nVS = locCreateShader(0x8B31); + const GLchar* strVS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "layout(location = 0) in vec3 aPos;\n""layout(location = 1) in vec2 aTex;\n" + "layout(location = 2) in vec4 aCol;\n""out vec2 oTex;\n""out vec4 oCol;\n" + "void main(){ float p = 1.0 / aPos.z; gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0); oTex = p * aTex; oCol = aCol;}"; + locShaderSource(m_nVS, 1, &strVS, NULL); + locCompileShader(m_nVS); + + m_nQuadShader = locCreateProgram(); + locAttachShader(m_nQuadShader, m_nFS); + locAttachShader(m_nQuadShader, m_nVS); + locLinkProgram(m_nQuadShader); + + // Create Quad + locGenBuffers(1, &m_vbQuad); + locGenVertexArrays(1, &m_vaQuad); + locBindVertexArray(m_vaQuad); + locBindBuffer(0x8892, m_vbQuad); + + locVertex verts[OLC_MAX_VERTS]; + locBufferData(0x8892, sizeof(locVertex) * OLC_MAX_VERTS, verts, 0x88E0); + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); + locBindBuffer(0x8892, 0); + locBindVertexArray(0); + + // Create blank texture for spriteless decals + rendBlankQuad.Create(1, 1); + rendBlankQuad.Sprite()->GetData()[0] = olc::WHITE; + rendBlankQuad.Decal()->Update(); + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglMakeCurrent(olc_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(olc_Display, olc_Context); + eglDestroySurface(olc_Display, olc_Surface); + eglTerminate(olc_Display); + olc_Display = EGL_NO_DISPLAY; + olc_Surface = EGL_NO_SURFACE; + olc_Context = EGL_NO_CONTEXT; +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglSwapBuffers(olc_Display, olc_Surface); +#endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + locUseProgram(m_nQuadShader); + locBindVertexArray(m_vaQuad); + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); +#endif + } + + void SetDecalMode(const olc::DecalMode& mode) override + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; + case olc::DecalMode::MULTIPLICATIVE: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::STENCIL: glBlendFunc(GL_ZERO, GL_SRC_ALPHA); break; + case olc::DecalMode::ILLUMINATE: glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); break; + case olc::DecalMode::WIREFRAME: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + locBindBuffer(0x8892, m_vbQuad); + locVertex verts[4] = { + {{-1.0f, -1.0f, 1.0}, {0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{+1.0f, -1.0f, 1.0}, {1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{-1.0f, +1.0f, 1.0}, {0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + {{+1.0f, +1.0f, 1.0}, {1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + }; + + locBufferData(0x8892, sizeof(locVertex) * 4, verts, 0x88E0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, rendBlankQuad.Decal()->id); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + locBindBuffer(0x8892, m_vbQuad); + + for (uint32_t i = 0; i < decal.points; i++) + pVertexMem[i] = { { decal.pos[i].x, decal.pos[i].y, decal.w[i] }, { decal.uv[i].x, decal.uv[i].y }, decal.tint[i] }; + + locBufferData(0x8892, sizeof(locVertex) * decal.points, pVertexMem, 0x88E0); + + if (nDecalMode == DecalMode::WIREFRAME) + glDrawArrays(GL_LINE_LOOP, 0, decal.points); + else + glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Image loaders | +// O------------------------------------------------------------------------------O + +#pragma region image_gdi +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: GDI+, Windows Only, always exists, a little slow | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_GDI) + +#define min(a, b) ((a < b) ? a : b) +#define max(a, b) ((a > b) ? a : b) +#include +#include +#if defined(__MINGW32__) // Thanks Gusgo & Dandistine, but c'mon mingw!! wtf?! + #include +#else + #include +#endif +#include +#undef min +#undef max + +#if !defined(__MINGW32__) + #pragma comment(lib, "gdiplus.lib") + #pragma comment(lib, "Shlwapi.lib") +#endif + +namespace olc +{ + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + GdiplusStartup(&token, &startupInput, NULL); + } + + ULONG_PTR token; + + ~GDIPlusStartup() + { + // Well, MarcusTU thought this was important :D + Gdiplus::GdiplusShutdown(token); + } + } gdistartup; + + class ImageLoader_GDIPlus : public olc::ImageLoader + { + private: + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + ImageLoader_GDIPlus() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + // clear out existing sprite + spr->pColData.clear(); + + // Open file + UNUSED(pack); + Gdiplus::Bitmap* bmp = nullptr; + if (pack != nullptr) + { + // Load sprite from input stream + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)rb.vMemory.data(), UINT(rb.vMemory.size()))); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + + // Load sprite from file + bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str()); + } + + if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::rcode::FAIL; + spr->width = bmp->GetWidth(); + spr->height = bmp->GetHeight(); + + spr->pColData.resize(spr->width * spr->height); + + for (int y = 0; y < spr->height; y++) + for (int x = 0; x < spr->width; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + spr->SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: GDI+ | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_libpng +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: libpng, default on linux, requires -lpng (libpng-dev) | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_LIBPNG) +#include +namespace olc +{ + void pngReadStream(png_structp pngPtr, png_bytep data, png_size_t length) + { + png_voidp a = png_get_io_ptr(pngPtr); + ((std::istream*)a)->read((char*)data, length); + } + + class ImageLoader_LibPNG : public olc::ImageLoader + { + public: + ImageLoader_LibPNG() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + + // clear out existing sprite + spr->pColData.clear(); + + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_structp png; + png_infop info; + + auto loadPNG = [&]() + { + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + png_bytep* row_pointers; + spr->width = png_get_image_width(png, info); + spr->height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * spr->height); + for (int y = 0; y < spr->height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + // Create sprite array + spr->pColData.resize(spr->width * spr->height); + // Iterate through image rows, converting into sprite format + for (int y = 0; y < spr->height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < spr->width; x++) + { + png_bytep px = &(row[x * 4]); + spr->SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + for (int y = 0; y < spr->height; y++) // Thanks maksym33 + free(row_pointers[y]); + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + }; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + if (pack == nullptr) + { + FILE* f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::rcode::NO_FILE; + png_init_io(png, f); + loadPNG(); + fclose(f); + } + else + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); + png_set_read_fn(png, (png_voidp)&is, pngReadStream); + loadPNG(); + } + + return olc::rcode::OK; + + fail_load: + spr->width = 0; + spr->height = 0; + spr->pColData.clear(); + return olc::rcode::FAIL; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_stb +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h, all systems, very fast | +// O------------------------------------------------------------------------------O +// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h +// MIT License - Copyright(c) 2017 Sean Barrett + +// Note you need to download the above file into your project folder, and +// #define OLC_IMAGE_STB +// #define OLC_PGE_APPLICATION +// #include "olcPixelGameEngine.h" + +#if defined(OLC_IMAGE_STB) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +namespace olc +{ + class ImageLoader_STB : public olc::ImageLoader + { + public: + ImageLoader_STB() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + // clear out existing sprite + spr->pColData.clear(); + // Open file + stbi_uc* bytes = nullptr; + int w = 0, h = 0, cmp = 0; + if (pack != nullptr) + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); + } + + if (!bytes) return olc::rcode::FAIL; + spr->width = w; spr->height = h; + spr->pColData.resize(spr->width * spr->height); + std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); + delete[] bytes; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Platforms | +// O------------------------------------------------------------------------------O + +#pragma region platform_windows +// O------------------------------------------------------------------------------O +// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_WINAPI) + +#if defined(_WIN32) && !defined(__MINGW32__) + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input +#endif + +namespace olc +{ + class Platform_Windows : public olc::Platform + { + private: + HWND olc_hWnd = nullptr; + std::wstring wsAppName; + + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() override { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_hWnd }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE"); + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + olc::vi2d vTopLeft = vWindowPos; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; + vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + + // Keep client size as requested + RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // Thanks scripticuk + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(s).c_str()); +#else + SetWindowText(olc_hWnd, s.c_str()); +#endif + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override { return olc::rcode::FAIL; } + + // Windows Event Handler - this is statically connected to the windows event system + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_MOUSEMOVE: + { + // Thanks @ForAbby (Discord) + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; + case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; + case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; + case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_KEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_LBUTTONDOWN:ptrPGE->olc_UpdateMouseState(0, true); return 0; + case WM_LBUTTONUP: ptrPGE->olc_UpdateMouseState(0, false); return 0; + case WM_RBUTTONDOWN:ptrPGE->olc_UpdateMouseState(1, true); return 0; + case WM_RBUTTONUP: ptrPGE->olc_UpdateMouseState(1, false); return 0; + case WM_MBUTTONDOWN:ptrPGE->olc_UpdateMouseState(2, true); return 0; + case WM_MBUTTONUP: ptrPGE->olc_UpdateMouseState(2, false); return 0; + case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_linux +// O------------------------------------------------------------------------------O +// | START PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_X11) +namespace olc +{ + class Platform_Linux : public olc::Platform + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::Window olc_Window; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + public: + virtual olc::rcode ApplicationStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + XDestroyWindow(olc_Display, olc_Window); + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_Display, &olc_Window, olc_VisualInfo }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + using namespace X11; + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, vWindowPos.x, vWindowPos.y, + vWindowSize.x, vWindowSize.y, + 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, + CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + vWindowSize.x = gwa.width; + vWindowSize.y = gwa.height; + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + X11::XStoreName(olc_Display, olc_Window, s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + using namespace X11; + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + ptrPGE->olc_UpdateWindowSize(xce.width, xce.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + XKeyEvent* e = (XKeyEvent*)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + XKeyEvent* e = (XKeyEvent*)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, true); break; + case 2: ptrPGE->olc_UpdateMouseState(2, true); break; + case 3: ptrPGE->olc_UpdateMouseState(1, true); break; + case 4: ptrPGE->olc_UpdateMouseWheel(120); break; + case 5: ptrPGE->olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, false); break; + case 2: ptrPGE->olc_UpdateMouseState(2, false); break; + case 3: ptrPGE->olc_UpdateMouseState(1, false); break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + ptrPGE->olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + ptrPGE->olc_UpdateKeyFocus(true); + } + else if (xev.type == FocusOut) + { + ptrPGE->olc_UpdateKeyFocus(false); + } + else if (xev.type == ClientMessage) + { + ptrPGE->olc_Terminate(); + } + } + return olc::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_glut +// O------------------------------------------------------------------------------O +// | START PLATFORM: GLUT (used to make it simple for Apple) | +// O------------------------------------------------------------------------------O +// +// VERY IMPORTANT!!! The Apple port was originally created by @Mumflr (discord) +// and the repo for the development of this project can be found here: +// https://github.com/MumflrFumperdink/olcPGEMac which contains maccy goodness +// and support on how to setup your build environment. +// +// "MASSIVE MASSIVE THANKS TO MUMFLR" - Javidx9 +#if defined(OLC_PLATFORM_GLUT) +namespace olc { + + class Platform_GLUT : public olc::Platform + { + public: + static std::atomic* bActiveRef; + + virtual olc::rcode ApplicationStartUp() override { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + static void ExitMainLoop() { + if (!ptrPGE->OnUserDestroy()) { + *bActiveRef = true; + return; + } + platform->ThreadCleanUp(); + platform->ApplicationCleanUp(); + exit(0); + } + +#if defined(__APPLE__) + static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { + static const SEL deltaYSel = sel_registerName("deltaY"); + + double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); + + for (int i = 0; i < abs(deltaY); i++) { + if (deltaY > 0) { + ptrPGE->olc_UpdateMouseWheel(-1); + } + else if (deltaY < 0) { + ptrPGE->olc_UpdateMouseWheel(1); + } + } + } +#endif + static void ThreadFunct() { +#if defined(__APPLE__) + static bool hasEnabledCocoa = false; + if (!hasEnabledCocoa) { + // Objective-C Wizardry + Class NSApplicationClass = objc_getClass("NSApplication"); + + // NSApp = [NSApplication sharedApplication] + SEL sharedApplicationSel = sel_registerName("sharedApplication"); + id NSApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, sharedApplicationSel); + // window = [NSApp mainWindow] + SEL mainWindowSel = sel_registerName("mainWindow"); + id window = ((id(*)(id, SEL))objc_msgSend)(NSApp, mainWindowSel); + + // [window setStyleMask: NSWindowStyleMaskClosable | ~NSWindowStyleMaskResizable] + SEL setStyleMaskSel = sel_registerName("setStyleMask:"); + ((void (*)(id, SEL, NSUInteger))objc_msgSend)(window, setStyleMaskSel, 7); + + hasEnabledCocoa = true; + } +#endif + if (!*bActiveRef) { + ExitMainLoop(); + return; + } + glutPostRedisplay(); + } + + static void DrawFunct() { + ptrPGE->olc_CoreUpdate(); + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { +#if defined(__APPLE__) + Class GLUTViewClass = objc_getClass("GLUTView"); + + SEL scrollWheelSel = sel_registerName("scrollWheel:"); + bool resultAddMethod = class_addMethod(GLUTViewClass, scrollWheelSel, (IMP)scrollWheelUpdate, "v@:@"); + assert(resultAddMethod); +#endif + + renderer->PrepareDevice(); + + if (bFullScreen) + { + vWindowSize.x = glutGet(GLUT_SCREEN_WIDTH); + vWindowSize.y = glutGet(GLUT_SCREEN_HEIGHT); + glutFullScreen(); + } + else + { + if (vWindowSize.x > glutGet(GLUT_SCREEN_WIDTH) || vWindowSize.y > glutGet(GLUT_SCREEN_HEIGHT)) + { + perror("ERROR: The specified window dimensions do not fit on your screen\n"); + return olc::FAIL; + } + glutReshapeWindow(vWindowSize.x, vWindowSize.y - 1); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys['A'] = Key::A; mapKeys['B'] = Key::B; mapKeys['C'] = Key::C; mapKeys['D'] = Key::D; mapKeys['E'] = Key::E; + mapKeys['F'] = Key::F; mapKeys['G'] = Key::G; mapKeys['H'] = Key::H; mapKeys['I'] = Key::I; mapKeys['J'] = Key::J; + mapKeys['K'] = Key::K; mapKeys['L'] = Key::L; mapKeys['M'] = Key::M; mapKeys['N'] = Key::N; mapKeys['O'] = Key::O; + mapKeys['P'] = Key::P; mapKeys['Q'] = Key::Q; mapKeys['R'] = Key::R; mapKeys['S'] = Key::S; mapKeys['T'] = Key::T; + mapKeys['U'] = Key::U; mapKeys['V'] = Key::V; mapKeys['W'] = Key::W; mapKeys['X'] = Key::X; mapKeys['Y'] = Key::Y; + mapKeys['Z'] = Key::Z; + + mapKeys[GLUT_KEY_F1] = Key::F1; mapKeys[GLUT_KEY_F2] = Key::F2; mapKeys[GLUT_KEY_F3] = Key::F3; mapKeys[GLUT_KEY_F4] = Key::F4; + mapKeys[GLUT_KEY_F5] = Key::F5; mapKeys[GLUT_KEY_F6] = Key::F6; mapKeys[GLUT_KEY_F7] = Key::F7; mapKeys[GLUT_KEY_F8] = Key::F8; + mapKeys[GLUT_KEY_F9] = Key::F9; mapKeys[GLUT_KEY_F10] = Key::F10; mapKeys[GLUT_KEY_F11] = Key::F11; mapKeys[GLUT_KEY_F12] = Key::F12; + + mapKeys[GLUT_KEY_DOWN] = Key::DOWN; mapKeys[GLUT_KEY_LEFT] = Key::LEFT; mapKeys[GLUT_KEY_RIGHT] = Key::RIGHT; mapKeys[GLUT_KEY_UP] = Key::UP; + mapKeys[13] = Key::ENTER; + + mapKeys[127] = Key::BACK; mapKeys[27] = Key::ESCAPE; + mapKeys[9] = Key::TAB; mapKeys[GLUT_KEY_HOME] = Key::HOME; + mapKeys[GLUT_KEY_END] = Key::END; mapKeys[GLUT_KEY_PAGE_UP] = Key::PGUP; mapKeys[GLUT_KEY_PAGE_DOWN] = Key::PGDN; mapKeys[GLUT_KEY_INSERT] = Key::INS; + mapKeys[32] = Key::SPACE; mapKeys[46] = Key::PERIOD; + + mapKeys[48] = Key::K0; mapKeys[49] = Key::K1; mapKeys[50] = Key::K2; mapKeys[51] = Key::K3; mapKeys[52] = Key::K4; + mapKeys[53] = Key::K5; mapKeys[54] = Key::K6; mapKeys[55] = Key::K7; mapKeys[56] = Key::K8; mapKeys[57] = Key::K9; + + // NOTE: MISSING KEYS :O + + glutKeyboardFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, true); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, true); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutKeyboardUpFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, false); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, false); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + //No ALT in PGE + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + //Special keys + glutSpecialFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutSpecialUpFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + glutMouseFunc([](int button, int state, int x, int y) -> void { + switch (button) { + case GLUT_LEFT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(0, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(0, true); + break; + case GLUT_MIDDLE_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(2, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(2, true); + break; + case GLUT_RIGHT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(1, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(1, true); + break; + } + }); + + auto mouseMoveCall = [](int x, int y) -> void { + ptrPGE->olc_UpdateMouse(x, y); + }; + + glutMotionFunc(mouseMoveCall); + glutPassiveMotionFunc(mouseMoveCall); + + glutEntryFunc([](int state) -> void { + if (state == GLUT_ENTERED) ptrPGE->olc_UpdateKeyFocus(true); + else if (state == GLUT_LEFT) ptrPGE->olc_UpdateKeyFocus(false); + }); + + glutDisplayFunc(DrawFunct); + glutIdleFunc(ThreadFunct); + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + glutSetWindowTitle(s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override { + glutMainLoop(); + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + return olc::OK; + } + }; + + std::atomic* Platform_GLUT::bActiveRef{ nullptr }; + + //Custom Start + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + olc_PrepareEngine(); + if (!OnUserCreate()) return olc::FAIL; + Platform_GLUT::bActiveRef = &bAtomActive; + glutWMCloseFunc(Platform_GLUT::ExitMainLoop); + bAtomActive = true; + platform->StartSystemEventLoop(); + + //This code will not even be run but why not + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: GLUT | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#pragma region platform_emscripten +// O------------------------------------------------------------------------------O +// | START PLATFORM: Emscripten - Totally Game Changing... | +// O------------------------------------------------------------------------------O + +// +// Firstly a big mega thank you to members of the OLC Community for sorting this +// out. Making a browser compatible version has been a priority for quite some +// time, but I lacked the expertise to do it. This awesome feature is possible +// because a group of former strangers got together and formed friendships over +// their shared passion for code. If anything demonstrates how powerful helping +// each other can be, it's this. - Javidx9 + +// Emscripten Platform: MaGetzUb, Moros1138, Slavka, Dandistine, Gorbit99, Bispoo +// also: Ishidex, Gusgo99, SlicEnDicE, Alexio + + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + +#include +#include + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline int olc_OnPageUnload() + { olc::platform->ApplicationCleanUp(); return 0; } +} + +namespace olc +{ + class Platform_Emscripten : public olc::Platform + { + public: + + virtual olc::rcode ApplicationStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ApplicationCleanUp() override + { ThreadCleanUp(); return olc::rcode::OK; } + + virtual olc::rcode ThreadStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { renderer->DestroyDevice(); return olc::OK; } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + emscripten_set_canvas_element_size("#canvas", vWindowSize.x, vWindowSize.y); + + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; + mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_8; + + // Keyboard Callbacks + emscripten_set_keydown_callback("#canvas", 0, 1, keyboard_callback); + emscripten_set_keyup_callback("#canvas", 0, 1, keyboard_callback); + + // Mouse Callbacks + emscripten_set_wheel_callback("#canvas", 0, 1, wheel_callback); + emscripten_set_mousedown_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mouseup_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mousemove_callback("#canvas", 0, 1, mouse_callback); + + // Touch Callbacks + emscripten_set_touchstart_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchmove_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchend_callback("#canvas", 0, 1, touch_callback); + + // Canvas Focus Callbacks + emscripten_set_blur_callback("#canvas", 0, 1, focus_callback); + emscripten_set_focus_callback("#canvas", 0, 1, focus_callback); + +#pragma warning disable format + EM_ASM( window.onunload = Module._olc_OnPageUnload; ); + + // IMPORTANT! - Sorry About This... + // + // In order to handle certain browser based events, such as resizing and + // going to full screen, we have to effectively inject code into the container + // running the PGE. Yes, I vomited about 11 times too when the others were + // convincing me this is the future. Well, this isnt the future, and if it + // were to be, I want no part of what must be a miserable distopian free + // for all of anarchic code injection to get rudimentary events like "Resize()". + // + // Wake up people! Of course theres a spoon. There has to be to keep feeding + // the giant web baby. + + + // Fullscreen and Resize Observers + EM_ASM({ + + // cache for reuse + Module._olc_EmscriptenShellCss = "width: 100%; height: 70vh; margin-left: auto; margin-right: auto;"; + + // width / height = aspect ratio + Module._olc_WindowAspectRatio = $0 / $1; + Module.canvas.parentNode.addEventListener("resize", function(e) { + + if (e.defaultPrevented) { e.stopPropagation(); return; } + var viewWidth = e.detail.width; + var viewHeight = e.detail.width / Module._olc_WindowAspectRatio; + if (viewHeight > e.detail.height) + { + viewHeight = e.detail.height; + viewWidth = e.detail.height * Module._olc_WindowAspectRatio; + } + + if (Module.canvas.parentNode.className == 'emscripten_border') + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss + " width: " + viewWidth.toString() + "px; height: " + viewHeight.toString() + "px;"; + + Module.canvas.setAttribute("width", viewWidth); + Module.canvas.setAttribute("height", viewHeight); + + if (document.fullscreenElement != null) + { + var top = (e.detail.height - viewHeight) / 2; + var left = (e.detail.width - viewWidth) / 2; + Module.canvas.style.position = "fixed"; + Module.canvas.style.top = top.toString() + "px"; + Module.canvas.style.left = left.toString() + "px"; + Module.canvas.style.width = ""; + Module.canvas.style.height = ""; + } + + // trigger PGE update + Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); + // this is really only needed when enter/exiting fullscreen + Module.canvas.focus(); + // prevent this event from ever affecting the document beyond this element + e.stopPropagation(); + }); + + // helper function to prevent repeating the same code everywhere + Module._olc_ResizeCanvas = function() + { + // yes, we still have to wait, sigh.. + setTimeout(function() + { + // if default template, stretch width as well + if (Module.canvas.parentNode.className == 'emscripten_border') + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; + + // override it's styling so we can get it's stretched size + Module.canvas.style.cssText = "width: 100%; height: 100%; outline: none;"; + + // setup custom resize event + var resizeEvent = new CustomEvent('resize', + { + detail: { + width: Module.canvas.clientWidth, + height : Module.canvas.clientHeight + }, + bubbles : true, + cancelable : true + }); + + // trigger custom resize event on canvas element + Module.canvas.dispatchEvent(resizeEvent); + }, 50); + }; + + + // Disable Refresh Gesture on mobile + document.body.style.cssText += " overscroll-behavior-y: contain;"; + + if (Module.canvas.parentNode.className == 'emscripten_border') + { + // force body to have no margin in emscripten's minimal shell + document.body.style.margin = "0"; + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; + } + + Module._olc_ResizeCanvas(); + + // observe and react to resizing of the container element + var resizeObserver = new ResizeObserver(function(entries) {Module._olc_ResizeCanvas();}).observe(Module.canvas.parentNode); + + // observe and react to changes that occur when entering/exiting fullscreen + var mutationObserver = new MutationObserver(function(mutationsList, observer) + { + // a change has occurred, let's check them out! + for (var i = 0; i < mutationsList.length; i++) + { + // cycle through all of the newly added elements + for (var j = 0; j < mutationsList[i].addedNodes.length; j++) + { + // if this element is a our canvas, trigger resize + if (mutationsList[i].addedNodes[j].id == 'canvas') + Module._olc_ResizeCanvas(); + } + } + }).observe(Module.canvas.parentNode, + { + attributes: false, + childList : true, + subtree : false + }); + + // add resize listener on window + window.addEventListener("resize", function(e) { Module._olc_ResizeCanvas(); }); + + }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers +#pragma warning restore format + return olc::rcode::OK; + } + + // Interface PGE's UpdateWindowSize, for use in Javascript + void UpdateWindowSize(int width, int height) + { + ptrPGE->olc_UpdateWindowSize(width, height); + } + + //TY Gorbit + static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + ptrPGE->olc_UpdateKeyFocus(false); + ptrPGE->olc_UpdateMouseFocus(false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + ptrPGE->olc_UpdateKeyFocus(true); + ptrPGE->olc_UpdateMouseFocus(true); + } + + return 0; + } + + //TY Moros + static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], true); + + // THANK GOD!! for this compute function. And thanks Dandistine for pointing it out! + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], false); + + //Consume keyboard events so that keys like F1 and F5 don't do weird things + return EM_TRUE; + } + + //TY Moros + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + ptrPGE->olc_UpdateMouseWheel(-1 * e->deltaY); + + return EM_TRUE; + } + + //TY Bispoo + static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) + { + // Move + if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + } + + // Start + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + ptrPGE->olc_UpdateMouseState(0, true); + } + + // End + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + ptrPGE->olc_UpdateMouseState(0, false); + } + + return EM_TRUE; + } + + //TY Moros + static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) + { + //Mouse Movement + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) + ptrPGE->olc_UpdateMouse(e->targetX, e->targetY); + + + //Mouse button press + if (e->button == 0) // left click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(0, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(0, false); + } + + if (e->button == 2) // right click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(1, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(1, false); + + } + + if (e->button == 1) // middle click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(2, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(2, false); + + //at the moment only middle mouse needs to consume events. + return EM_TRUE; + } + + return EM_FALSE; + } + + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { emscripten_set_window_title(s.c_str()); return olc::OK; } + + virtual olc::rcode StartSystemEventLoop() override + { return olc::OK; } + + virtual olc::rcode HandleSystemEvent() override + { return olc::OK; } + + static void MainLoop() + { + olc::Platform::ptrPGE->olc_CoreUpdate(); + if (!ptrPGE->olc_IsRunning()) + { + if (ptrPGE->OnUserDestroy()) + { + emscripten_cancel_main_loop(); + platform->ApplicationCleanUp(); + } + else + { + ptrPGE->olc_Reanimate(); + } + } + } + }; + + //Emscripten needs a special Start function + //Much of this is usually done in EngineThread, but that isn't used here + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Some implementations may form an event loop here + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Consider the "thread" started + bAtomActive = true; + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + platform->StartSystemEventLoop(); + + //This causes a heap memory corruption in Emscripten for some reason + //Platform_Emscripten::bActiveRef = &bAtomActive; + emscripten_set_main_loop(&Platform_Emscripten::MainLoop, 0, 1); + + // Wait for thread to be exited + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + return olc::OK; + } +} + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline void olc_PGE_UpdateWindowSize(int width, int height) + { + emscripten_set_canvas_element_size("#canvas", width, height); + // Thanks slavka + ((olc::Platform_Emscripten*)olc::platform.get())->UpdateWindowSize(width, height); + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: Emscripten | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#endif // Headless + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Auto-Configuration | +// O------------------------------------------------------------------------------O +#pragma region pge_config +namespace olc +{ + void PixelGameEngine::olc_ConfigureSystem() + { + +#if !defined(OLC_PGE_HEADLESS) + +#if defined(OLC_IMAGE_GDI) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_LIBPNG) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_STB) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_CUSTOM_EX) + olc::Sprite::loader = std::make_unique(); +#endif + + + + +#if defined(OLC_PLATFORM_WINAPI) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_X11) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_GLUT) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_CUSTOM_EX) + platform = std::make_unique(); +#endif + + + +#if defined(OLC_GFX_OPENGL10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL33) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGLES2) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX11) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_CUSTOM_EX) + renderer = std::make_unique(); +#endif + + // Associate components with PGE instance + platform->ptrPGE = this; + renderer->ptrPGE = this; +#else + olc::Sprite::loader = nullptr; + platform = nullptr; + renderer = nullptr; +#endif + } +} + +#pragma endregion + +#endif // End OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | END OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +