From 70acbd8c543ffeb03649d3b870fde8ecb86ec068 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 16 Jan 2019 21:09:09 +0000 Subject: [PATCH] Rays --- C++/Pixel-Engine/Rays/CMakeLists.txt | 72 + C++/Pixel-Engine/Rays/main.cpp | 47 + C++/Pixel-Engine/Rays/math.h | 223 +++ C++/Pixel-Engine/Rays/olcPixelGameEngine.h | 2047 ++++++++++++++++++++ C++/Pixel-Engine/Rays/output.o | Bin 0 -> 162824 bytes C++/Pixel-Engine/Rays/rect.cpp | 153 ++ C++/Pixel-Engine/Rays/rect.h | 69 + 7 files changed, 2611 insertions(+) create mode 100644 C++/Pixel-Engine/Rays/CMakeLists.txt create mode 100644 C++/Pixel-Engine/Rays/main.cpp create mode 100644 C++/Pixel-Engine/Rays/math.h create mode 100644 C++/Pixel-Engine/Rays/olcPixelGameEngine.h create mode 100755 C++/Pixel-Engine/Rays/output.o create mode 100644 C++/Pixel-Engine/Rays/rect.cpp create mode 100644 C++/Pixel-Engine/Rays/rect.h diff --git a/C++/Pixel-Engine/Rays/CMakeLists.txt b/C++/Pixel-Engine/Rays/CMakeLists.txt new file mode 100644 index 0000000..afde260 --- /dev/null +++ b/C++/Pixel-Engine/Rays/CMakeLists.txt @@ -0,0 +1,72 @@ +# This CMakeList.txt currently only supports compilation of +#a program on windows or linux, but only with the dependancies +#for oclPixelGameEngine.h + +# NOTE: THIS CMAKELIST WILL NOT INSTALL DEPENDANCIES, IT WILL JUST FIND THEM AND +#COMPILE / LINK THEM RESPECTIVELY, YOU NEED TO INSTALL THEM YOURSELF + +# Any issues, submit an issue, or contact the author, "Ben (plane000)#8618" on Discord + +# Currently linked / compiled by default is: +#Threads (pthread), OpenGL, GLX, libPNG, X11 + +cmake_minimum_required(VERSION 3.7) +project(Rage) # You can change this with your project name! + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} CMakeModules/) # Don't worry what this does, it just gives more compatability options with libraries! +cmake_policy(SET CMP0037 OLD) # This just allows the / charicter in path names + +set(ExecutableName output) # You can change this to whatever you wish the executable outputed to be, don't worry about a .exe, i'll do that! +set(SourceDir ./) # Change src/ to wherever your code is + + +file(GLOB_RECURSE SourceFiles + ${SourceDir}/*.cpp +) + +set(THREADS_PREFER_PTHREAD_FLAD ON) +find_package(Threads REQUIRED) +find_package(OpenGL REQUIRED) + + +if (UNIX) + find_package(X11 REQUIRED) + find_package(PNG REQUIRED) + include_directories( + ${PNG_INCLUDE_DIR} + ) + + # Set platform spesific output names + set(Executable ${ExecutableName}.o) +endif (UNIX) +if (WIN32) + target_include_directories( + ${WinSDK} + ) + + # Set platform spesific output names + set(Executable ${ExecutableName}.exe) +endif (WIN32) + +add_executable(${Executable} + ${SourceFiles} +) + +target_link_libraries(${Executable} + Threads::Threads + OpenGL::OpenGL + OpenGL::GL + OpenGL::GLX +) + +if (UNIX) + target_link_libraries(${Executable} + ${X11_LIBRARIES} + PNG::PNG + ) +endif (UNIX) +if (WIN32) + target_link_libraries(${Executable} + ${WinSDK} + ) +endif (WIN32) diff --git a/C++/Pixel-Engine/Rays/main.cpp b/C++/Pixel-Engine/Rays/main.cpp new file mode 100644 index 0000000..4c0ab25 --- /dev/null +++ b/C++/Pixel-Engine/Rays/main.cpp @@ -0,0 +1,47 @@ +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include "rect.h" + +#include + +Rect rect; +bool isHoverd = false; + +class RaysApp : public olc::PixelGameEngine { +public: + RaysApp() { + sAppName = "Rays"; + } + + bool OnUserCreate() override { + rect.SetRect(100, 100, 200, 100); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override { + Clear(olc::BLACK); + if (!isHoverd) { + DrawRect(rect.x, rect.y, rect.w, rect.h, olc::RED); + } else { + FillRect(rect.x, rect.y, rect.w, rect.h, olc::RED); + } + + std::cout << "MouseX: " << GetMouseX() << " MouseY: " << GetMouseY() << std::endl; + + if (rect.Contains(new Vec2(GetMouseX(), GetMouseY()))) { + isHoverd = true; + } else { + isHoverd = false; + } + + return true; + } +}; + +int main(int argc, char** argv) { + RaysApp app; + if (app.Construct(500, 300, 2, 2)) + app.Start(); + return 0; +} diff --git a/C++/Pixel-Engine/Rays/math.h b/C++/Pixel-Engine/Rays/math.h new file mode 100644 index 0000000..35ccb3f --- /dev/null +++ b/C++/Pixel-Engine/Rays/math.h @@ -0,0 +1,223 @@ +#ifndef _MATH_H_ +#define _MATH_H_ + +const float DEG2RAD = 0.01745329251994329576923690768f; +const float RAD2DEG = 57.2957795130823208767981548141f; + +inline float ToRadian(const float Degree) { + return (Degree * DEG2RAD); +} + +inline float ToDegree(const float Radian) { + return (Radian * RAD2DEG); +} + +template +struct Vec4 { + T x, y, z, w; + template + Vec4(P x, P y, P z, P w) : x(x), y(y), z(z), w(w) {} + template + Vec4(P all) : x(all), y(all), z(all), w(all) {} + Vec4() : x(0), y(0), z(0), w(0) {} + inline Vec4& dot(const Vec4& v) { + return (x * v.x + y * v.y + z * v.z + w * v.w); + } + inline const Vec4& operator+() { + return *this; + } + inline Vec4& operator-() { + return Vec4(-x, -y, -z, -w); + } + inline Vec4& operator+(const Vec4& v) { + return new Vec4(x + v.x, y + v.y, z + v.z, w + v.w); + } + inline Vec4& operator-(const Vec4& v) { + return new Vec4(x - v.x, y - v.y, z - v.z, w - v.w); + } + inline Vec4& operator*(const Vec4& v) { + return new Vec4(x * v.x, y * v.y, z * v.z, w * v.w); + } + inline Vec4& operator/(const Vec4& v) { + return new Vec4(x / v.x, y / v.y, z / v.z, w / v.w); + } + inline Vec4& operator+=(const Vec4& v) { + x+=v.x; y+=v.y; z+=v.z; w+=v.w; + return *this; + } + inline Vec4& operator-=(const Vec4& v) { + x-=v.x; y-=v.y; z-=v.z; w-=v.w; + return *this; + } + inline Vec4& operator*=(const Vec4& v) { + x*=v.x; y*=v.y; z*=v.z; w*=v.w; + return *this; + } + inline Vec4& operator/=(const Vec4& v) { + x/=v.x; y/=v.y; z/=v.z; w/=v.w; + return *this; + } + template + inline Vec4& operator+=(P s) { + x+=s; y+=s; z+=s; w+=s; + return *this; + } + template + inline Vec4& operator-=(P s) { + x-=s; y-=s; z-=s; w-=s; + return *this; + } + template + inline Vec4& operator*=(P s) { + x*=s; y*=s; z*=s; w*=s; + return *this; + } + template + inline Vec4& operator/=(P s) { + x/=s; y/=s; z/=s; w/=s; + return *this; + } +}; + +template +struct Vec3 { + T x, y, z; + template + Vec3(P x, P y, P z) : x(x), y(y), z(z) {} + template + Vec3(P all) : x(all), y(all), z(all) {} + Vec3() : x(0), y(0), z(0) {} + inline Vec3& cross(const Vec3& v) { + return new Vec3( + (y * v.z - z * v.y), + (x * v.z - z * v.x), + (x * v.y - y * v.x) + ); + } + inline Vec3& dot(const Vec3& v) { + return (x * v.x + y * v.y + z * v.z); + } + inline const Vec3& operator+() { + return *this; + } + inline Vec3& operator-() { + return Vec3(-x, -y, -z); + } + inline Vec3& operator+(const Vec3& v) { + return new Vec3(x + v.x, y + v.y, z + v.z); + } + inline Vec3& operator-(const Vec3& v) { + return new Vec3(x - v.x, y - v.y, z - v.z); + } + inline Vec3& operator*(const Vec3& v) { + return new Vec3(x * v.x, y * v.y, z * v.z); + } + inline Vec3& operator/(const Vec3& v) { + return new Vec3(x / v.x, y / v.y, z / v.z); + } + inline Vec3& operator+=(const Vec3& v) { + x+=v.x; y+=v.y; z+=v.z; + return *this; + } + inline Vec3& operator-=(const Vec3& v) { + x-=v.x; y-=v.y; z-=v.z; + return *this; + } + inline Vec3& operator*=(const Vec3& v) { + x*=v.x; y*=v.y; z*=v.z; + return *this; + } + inline Vec3& operator/=(const Vec3& v) { + x/=v.x; y/=v.y; z/=v.z; + return *this; + } + template + inline Vec3& operator+=(P s) { + x+=s; y+=s; z+=s; + return *this; + } + template + inline Vec3& operator-=(P s) { + x-=s; y-=s; z-=s; + return *this; + } + template + inline Vec3& operator*=(P s) { + x*=s; y*=s; z*=s; + return *this; + } + template + inline Vec3& operator/=(P s) { + x/=s; y/=s; z/=s; + return *this; + } +}; + +template +struct Vec2 { + T x, y; + template + Vec2(P x, P y) : x(x), y(y) {} + template + Vec2(P all) : x(all), y(all) {} + Vec2() : x(0), y(0) {} + inline const Vec2& operator+() { + return *this; + } + inline Vec2& dot(const Vec3& v) { + return (x * v.x + y * v.y); + } + inline Vec2& operator-() { + return Vec3(-x, -y); + } + inline Vec2& operator+(const Vec2& v) { + return new Vec2(x + v.x, y + v.y); + } + inline Vec2& operator-(const Vec2& v) { + return new Vec2(x - v.x, y - v.y); + } + inline Vec2& operator*(const Vec2& v) { + return new Vec2(x * v.x, y * v.y); + } + inline Vec2& operator/(const Vec2& v) { + return new Vec2(x / v.x, y / v.y); + } + inline Vec2& operator+=(const Vec2& v) { + x+=v.x; y+=v.y; + return *this; + } + inline Vec2& operator-=(const Vec2& v) { + x-=v.x; y-=v.y; + return *this; + } + inline Vec2& operator*=(const Vec2& v) { + x*=v.x; y*=v.y; + return *this; + } + inline Vec2& operator/=(const Vec2& v) { + x/=v.x; y/=v.y; + return *this; + } + template + inline Vec2& operator+=(P s) { + x+=s; y+=s; + return *this; + } + template + inline Vec2& operator-=(P s) { + x-=s; y-=s; + return *this; + } + template + inline Vec2& operator*=(P s) { + x*=s; y*=s; + return *this; + } + template + inline Vec2& operator/=(P s) { + x/=s; y/=s; + return *this; + } +}; + +#endif \ No newline at end of file diff --git a/C++/Pixel-Engine/Rays/olcPixelGameEngine.h b/C++/Pixel-Engine/Rays/olcPixelGameEngine.h new file mode 100644 index 0000000..1dfc208 --- /dev/null +++ b/C++/Pixel-Engine/Rays/olcPixelGameEngine.h @@ -0,0 +1,2047 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.11 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + The Original & Best... :P + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprsing and wonderful + success for me, and I'm delighted how people have reacted so + positively towards it, so thanks for that. + + However, there are limitations that I simply cannot avoid. + Firstly, I need to maintain several different versions of + it to accommodate users on Windows7, 8, 10, Linux, Mac, + Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities + and the effect is becoming underwhelming. The engine itself + is not slow at all, but the process that Windows uses to + draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours + and glyphs. Sadly I have no control over this, and recent + videos that are extremely graphical (for a command prompt :P ) + have been dipping to unacceptable framerates. As the channel + has been popular with aspiring game developers, I'm concerned + that the visual appeal of the command prompt is perhaps + limited to us oldies, and I dont want to alienate younger + learners. Finally, I'd like to demonstrate many more + algorithms and image processing that exist in the graphical + domain, for which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look + and feel to the programmer is almost identical, so all of my + existing code from the videos is easily portable, and the + programmer uses this file in exactly the same way. But I've + decided that rather than just build a command prompt emulator, + that I would at least harness some modern(ish) portable + technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is + written in a cross-platform style, uses modern(ish) C++ + conventions and most importantly, renders much much faster. I + will use this version when my applications are predominantly + graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command + prompt silliness to come yet, but evolution is important!! + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + 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 + + 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, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Thanks + ~~~~~~ + I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, + JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice + & MagetzUb for advice, ideas and testing, and I'd like to extend + my appreciation to the 14K YouTube followers and 1K Discord server + members who give me the motivation to keep going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + 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, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __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 + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + 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), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //============================================================= + + 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 tru for all frames between pressed and released events + }; + + //============================================================= + + class ResourcePack + { + public: + ResourcePack(); + ~ResourcePack(); + struct sEntry : public std::streambuf { + uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); }}; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + void SetPixel(int32_t x, int32_t y, Pixel p); + Pixel Sample(float x, float y); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + 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, ENTER, PAUSE, SCROLL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h); + olc::rcode Start(); + + public: // 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 a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // 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); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual void Draw(int32_t x, int32_t y, 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); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, 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); + // 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); + // 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); + // 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); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // 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); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + +#ifdef _WIN32 + // Windows specific window handling + HWND olc_hWnd = nullptr; + HWND olc_WindowCreate(); + std::wstring wsAppName; + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +#else + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + Consider the following project structure: + + Class1.h - Includes olcPixelGameEngine.h, overrides olc::PixelGameEngine + Class1.cpp - #define OLC_PGE_APPLICATION #include "Class1.h" + Class2.h - Includes Class1.h, which includes olcPixelGameEngine.h + Class2.cpp - #define OLC_PGE_APPLICATION #include "Class2.h" + main.cpp - Includes Class1.h and Class2.h + + If all of this is a bit too confusing, you can split this file in two! + Everything below this comment block can go into olcPixelGameEngineOOP.cpp + and everything above it can go into olcPixelGameEngineOOP.h + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + +// std::wstring ConvertS2W(std::string s) +// { +// #ifdef _WIN32 +// 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); +// std::wstring w(buffer); +// delete[] buffer; +// return w; +// #endif +// //#ifdef __MINGW32__ +// // wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; +// // mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); +// // buffer[sImageFile.length()] = L'\0'; +// // wsImageFile = buffer; +// // delete[] buffer; +// //#else +// } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + // std::istream is(&(pack->GetStreamBuffer(sImageFile))); + // ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _WIN32 + // Use GDI+ + std::wstring wsImageFile; +#ifdef __MINGW32__ + wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; + mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); + buffer[sImageFile.length()] = L'\0'; + wsImageFile = buffer; + delete [] buffer; +#else + wsImageFile = ConvertS2W(sImageFile); +#endif + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + 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; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + 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) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + 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)]; + } + } + + void Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + pColData[y*width + x] = p; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = (int32_t)(x * (float)width); + int32_t sy = (int32_t)(y * (float)height); + return GetPixel(sx, sy); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = p; + + // Read file into memory + e.data = new uint8_t[e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + size_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(size_t)); + for (size_t i = 0; i < nMapEntries; i++) + { + size_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(size_t)); + + std::string sFileName(nFilePathSize, ' '); + for (size_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + //e.second.setg + e.second._config(); + //e.second.pubsetbuf((char*)e.second.data, e.second.nFileSize); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + if (!olc_WindowCreate()) + return olc::FAIL; + + // Load libraries required for PNG file interaction +#ifdef _WIN32 + // Windows use GDI+ + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); +#else + // Linux use libpng + +#endif + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + +#ifdef _WIN32 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + void PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return; + + + if (nPixelMode == Pixel::NORMAL) + { + pDrawTarget->SetPixel(x, y, p); + return; + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + pDrawTarget->SetPixel(x, y, p); + return; + } + + 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; + pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + return; + } + + if (nPixelMode == Pixel::CUSTOM) + { + pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + return; + } + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + 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; + } + + Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + Draw(x - x0, y - y0, p);//upper left left + Draw(x - y0, y - x0, p);//upper upper left + Draw(x + y0, y - x0, p);//upper upper right + Draw(x + x0, y - y0, p);//upper right right + Draw(x - x0, y + y0, p);//lower left left + Draw(x - y0, y + x0, p);//lower lower left + Draw(x + y0, y + x0, p);//lower lower right + Draw(x + x0, y + y0, p);//lower right right + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + 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; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + 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)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, 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); + } + + // 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 SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + 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) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); 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) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + 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= 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 dx1) { // swap values + 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= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= 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 (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + 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(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + 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) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + 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(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 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); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + 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) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + nMousePosX = x / (int32_t)nPixelWidth; + nMousePosY = y / (int32_t)nPixelHeight; + + if (nMousePosX >= (int32_t)nScreenWidth) + nMousePosX = nScreenWidth - 1; + if (nMousePosY >= (int32_t)nScreenHeight) + nMousePosY = nScreenHeight - 1; + + if (nMousePosX < 0) + nMousePosX = 0; + if (nMousePosY < 0) + nMousePosY = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // 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); + glViewport(0, 0, gwa.width, gwa.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + pMouseNewState[xev.xbutton.button-1] = true; + } + else if (xev.type == ButtonRelease) + { + pMouseNewState[xev.xbutton.button-1] = false; + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + + + 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; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + 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; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE; + RECT rWndRect = { 0, 0, (LONG)nScreenWidth * (LONG)nPixelWidth, (LONG)nScreenHeight * (LONG)nPixelHeight }; + + // Keep client size as requested + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + 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_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; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + 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 false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + 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; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 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"); + + // Create Keyboard Mapping + 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_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_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; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + 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) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + 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"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif diff --git a/C++/Pixel-Engine/Rays/output.o b/C++/Pixel-Engine/Rays/output.o new file mode 100755 index 0000000000000000000000000000000000000000..a7a78d705a9d05c483ca5839fc5b8246cc0dfbe7 GIT binary patch literal 162824 zcmeFadwf*Y)jmGq8W2rTP`p$kY71Ty2oOYM0*nqyWCWt|f=NgML_-pDfnbeBL)sk2 zsi_r9t+v$Kma46(^%ALKfXJl2z8bC7)KX26wiAPzR$h&k%J+HJ-sjAo$s9!A_w)PX z_c2P&S(m-`+H0@9_Bwm-b8?Gs(S<{jl05d$P|pP(LOpp3ubxx|v)pjr z1{55BeeY3)x36Y7RiCK=c@^AzsVZk*&2qHOq$ono5#M| z_qXl_&k|jqn_aqf1@=|9-g9nQ>53Vd=avH2>#9U{c+#u`isB4y8Wciu9BD5{O+B*Zu)y(!To=_A@8KoKdjt8e$$xw zU`zP()@YCC=meC%zz-7Pe+1Itg4TF8%(VOS^vPqR%RqdT(~I+XpV=Yo?2w z4K8t}9pfTVe7Me~A5V7amw&n7H@Nui8W(??axaj{!7dhW{;Xld6 zZhyf9l*m7axb({(UHs=W7k!R(kzeWJSKD3eP~hS}Pr1bZ>yJs?t^+RmdtKUllS{w< z5(!QsyZzM#e}s$uuOFFM&T5x-4Y-WcGMD(a!=>J5T>9&2m-fEkVz<#Q{kzR&ewpi1 z?~N|}D_s0!jfGz0>{7+%eL~-UPE_xQZ_`}^Ub{IM#u|EvBjEn!c z#F_Oj`aJ7`pXf5aCb`)27cO%0UHpX41&PMX(=PtM8HsZu{s&y_mhNJQ{Vw_(bkRS< zC2m~k;wOJ_(KFR$Jf7vE&n%a`Zla6dcDcy8!o?0@7rXt+1%HoAy&)I*H7&gD5` zxy$%E!zEt5>e62yx!CPkm;B)!m-)KSrN54FX)mAa((o_-=Ql3>@`;Q5TU_k_1DCj( z;=*4B{zP%Nca^{y+l$R_kTv1ly$L~>Q zEb}WWs*0zs1aNstRZU6VG~W8ki}CxSl5#Dh8qhrGSB@)CzVfwFptNLNWkpquLN2Xc zVb_las>Wr2l((Ryx~8gP{rrk@h$nO2{M8i|)g_mgR@WAm5nj5kuyS7Q%9SNmtSIlI zlA1tOMNxKfaaBpRU~&qtFPUFkRaH{1%2$;X)|A)+rM5g}OWZHb;q43$ZWqq?Irn)P z8S0+muYh?f)@9e!RF$r%ttqMY(_YlE`_4LwXDt7UX}3V$y4R$SCmy$ zttqTTp_+=Sl1tG!o;csjDn3FV2?|8TnK5Gs%!8 zr6_!DVOeRh11XcOD=jvfU$f-Wr7TleR=K*6-UZuGNg6-hqk(+Y8j^36Ug2GJO>xng zXR0pCzv|MZH8b<`i|Xn!GBT#FD6B3mVzc293yYT4WMmYrE<_`%3QKFM7Z&-J7UGw$ zaH%hFNs&j&W=wa=loYO6QM)oxG2hJUl9KByR<87wLFUx_g%#ELOMxjatpYqvVEWja ziekhVs&sf_=TG&m1^=?kFU?4oHWaQXU7L}?_LLP?S4-!JOZmXHv}P8(eobjP{EVeo zOQur3VkMdk^%LqoKSL_{5_XzVScygz`<7g?G!wxRHPUbjN>&7*V3xyD4%_5I-IAs0`Ae26abLLSDi=k2E~^y1K!KTz6TCp^rRs&*sO-WfofZsHyN&!4(5P_)+P zTb93cW7Xnza1$mrmw3ittqiBZ;ZauLyf+frPcWr7)4*Kn`hOQ!&WQH zD%MH4n1NJWf}pqtlc~%2Qf52c(6Ho^L%BvqzKWPWXpk_>=>T*{6QIlgH?@3;ft6KM zSXo$v0d8CUi#rkf*>?KkvVacLOKWDRS!zc9(i+5J6=4oD+Gka))qPruP(wXXr6|c)?|QDJDP( zX*Dx2U@`G)JyElx<)uqYGdWLV7&8LVHq_4;4~LwM291Zq^dNRXaiR1BW}fN!M%-9g zTV7OCinJaYm)SGp;ZN(871eO>%$2w<%*e>kUxNgLsa#EBr~eY}I5Qs)B!xAF{|j%7 zPQj%zovqTcI3eo^9!>x^H^M1{t~N*PT>0fQf@w?#l+4oU%!yXHm`-`b7aevpR3715 z6K$@O;~X-?OfO8c zw3G>{szl<)mm9rE&W&+9qX>yzdBwE+%co{ctzM6%m^DaDD~hh4R<5%eP8o+cS}Io* z7G1xxu(ZsFnUhn^7i}z(mmGPPSDB^LD~DA*p3=~+shmoo@hm&WmQ}36vt~(ERYjGZ zBYt7MoS7kDKaL(wr)Nq+`M=4j4%hOTh>@!hZIJw{{C{apS@lAcj(e{5B}hSvmLUO| zp8ut7gXac>*NSqrh=HWu=UXe!BAEZvS5{Q5E37KcM@C*%h=rc~n!;7$8l}?^Anl;4 z<6p*f_P(t7sqBq2dwOko=?%3d`IR+Q$h-3GgbtL<;*ynxwPjqd!O};`!lfDcK0H#c zUCV558C!o?Zu8|lbru_q{0h0{k~ML`9W5-zVqA2>u8>)* z^8YlIz=&T8@2Om$&lMmn2^7H>@xKh>%%Y0g8r46C*Oyo^vO~a^X)>daB-Al!p#6w; z$ZmB4Jl8cO`hAH6bh@mG*`wm{nw(j^zTC-9zG%Fa3=>OrYt$+o?p75+2epKkpIg2T z3�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 literal 0 HcmV?d00001 diff --git a/C++/Pixel-Engine/Rays/rect.cpp b/C++/Pixel-Engine/Rays/rect.cpp new file mode 100644 index 0000000..ff2263f --- /dev/null +++ b/C++/Pixel-Engine/Rays/rect.cpp @@ -0,0 +1,153 @@ +#include "rect.h" + +Rect::Rect() { + Clear(); +} + +Rect::Rect(int x, int y, int w, int h) { + SetRect(x, y, w, h); +} + +void Rect::Clear() { + SetRect(0, 0, 0, 0); +} + +std::string Rect::ToString() { + std::string res = "("; + res += std::to_string(x); + res += ", "; + res += std::to_string(y); + res += ", "; + res += std::to_string(w); + res += ", "; + res += std::to_string(h); + res += ")"; + return res; +} + +bool Rect::Intersects(Rect* rect) { + int leftA = x; + int rightA = x + w; + int topA = y; + int bottomA = y + h; + + int leftB = rect->x; + int rightB = rect->x + rect->w; + int topB = rect->y; + int bottomB = rect->y + rect->h; + + if (bottomA <= topB) return false; + if (topA >= bottomB) return false; + if (rightA <= leftB) return false; + if (leftA >= rightB) return false; + + return true; +} + +// bool Rect::Intersects(int x, int y, int w, int h) { +// return Intersects(&CreateRect(x, y, w, h)); +// } + +bool Rect::Contains(Rect* rect) { + return (rect->x >= x && rect->Right() <= (x + w) && rect->y >= y && rect->Bottom() <= (y + h)); +} + +bool Rect::Contains(Vec2* point) { + return (point->x >= x && point->x <= (x + w) && point->y >= y && point->y <= (y + h)); +} + +bool Rect::Contains(int x, int y, int w, int h) { + Rect tempRect(x, y, w, h); + return Contains(&tempRect); +} + +Vec2* Rect::Position() { + Vec2* res = new Vec2(x, y); + return res; +} + +Vec2* Rect::Center() { + Vec2* res = new Vec2(x + (w / 2), y + (h / 2)); + return res; +} + +int Rect::CenterX() { + return (x + (w / 2)); +} + +int Rect::CenterY() { + return (y + (h / 2)); +} + +int Rect::Left() { + return x; +} + +int Rect::Right() { + return (x + w); +} + +int Rect::Top() { + return y; +} + +int Rect::Bottom() { + return y + h; +} + +int Rect::Perimiter() { + return (w + w + h + h); +} + +int Rect::Area() { + return (w + h); +} + +int Rect::GetX() { + return x; +} + +int Rect::GetY() { + return y; +} + +int Rect::GetW() { + return w; +} + +int Rect::GetH() { + return h; +} + +void Rect::SetRect(int x, int y, int w, int h) { + this->x = x; + this->y = y; + this->w = w; + this->h = h; +} + +void Rect::SetSize(Vec2* size) { + this->x = size->x; + this->y = size->y; +} + +void Rect::SetPos(Vec2* pos) { + this->w = pos->x; + this->h = pos->y; +} + +void Rect::Translate(Vec2* offset) { + this->x += offset->x; + this->y += offset->y; +} + +void Rect::TranslateX(int x) { + this->x += x; +} + +void Rect::TranslateY(int y) { + this->y += y; +} + +Rect::~Rect() { +} diff --git a/C++/Pixel-Engine/Rays/rect.h b/C++/Pixel-Engine/Rays/rect.h new file mode 100644 index 0000000..b5f5227 --- /dev/null +++ b/C++/Pixel-Engine/Rays/rect.h @@ -0,0 +1,69 @@ +#ifndef _RECT_H_ +#define _RECT_H_ + +#include +#include "math.h" + +class Rect { +public: + Rect(); + Rect(int x, int y, int w, int h); + void Clear(); + + static Rect CreateRect(int x, int y, int w, int h) { + Rect tempRect(x, y, w, h); + return tempRect; + } + + Rect operator+(Rect* rect) { + return Rect(this->x + rect->x, this->y + this->x, w, h); + } + Rect operator-(Rect* rect) { + return Rect(this->x - rect->x, this->y - this->x, w, h); + } + bool operator==(const Rect* rect) { + return (x == rect->x && y == rect->y && w == rect->w && h == rect->h); + } + bool operator!=(const Rect* rect) { + return !(x == rect->x && y == rect->y && w == rect->w && h == rect->h); + } + + std::string ToString(); + + bool Intersects(Rect* rect); + // bool Intersects(int x, int y, int w, int h); + + bool Contains(Rect* rect); + bool Contains(Vec2* point); + bool Contains(int x, int y, int w, int h); + + Vec2* Position(); + Vec2* Center(); + int CenterX(); + int CenterY(); + + int Left(); + int Right(); + int Top(); + int Bottom(); + int Perimiter(); + int Area(); + + int GetX(); + int GetY(); + int GetW(); + int GetH(); + + void SetRect(int x, int y, int w, int h); + void SetSize(Vec2* size); + void SetPos(Vec2* pos); + void Translate(Vec2* offset); + void TranslateX(int x); + void TranslateY(int y); + + int x, y, w, h; + + virtual ~Rect(); +}; + +#endif \ No newline at end of file