From 02a7059a7cc90bfd566f8981f115a37676498d3e Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 27 Mar 2019 11:55:43 +0000 Subject: [PATCH] Verlet intergrated cloth simulation --- C++/Verlet Cloth/.vscode/settings.json | 56 + C++/Verlet Cloth/main.cpp | 62 + C++/Verlet Cloth/olcPixelGameEngine.h | 2069 ++++++++++++++++++++++++ C++/Verlet Cloth/output | Bin 0 -> 165992 bytes 4 files changed, 2187 insertions(+) create mode 100644 C++/Verlet Cloth/.vscode/settings.json create mode 100644 C++/Verlet Cloth/main.cpp create mode 100644 C++/Verlet Cloth/olcPixelGameEngine.h create mode 100755 C++/Verlet Cloth/output diff --git a/C++/Verlet Cloth/.vscode/settings.json b/C++/Verlet Cloth/.vscode/settings.json new file mode 100644 index 0000000..f6f3c8e --- /dev/null +++ b/C++/Verlet Cloth/.vscode/settings.json @@ -0,0 +1,56 @@ +{ + "files.associations": { + "atomic": "cpp", + "chrono": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "list": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "xthread": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocinfo": "cpp", + "xlocnum": "cpp", + "xmemory": "cpp", + "xmemory0": "cpp", + "xstddef": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp" + } +} \ No newline at end of file diff --git a/C++/Verlet Cloth/main.cpp b/C++/Verlet Cloth/main.cpp new file mode 100644 index 0000000..c615f4d --- /dev/null +++ b/C++/Verlet Cloth/main.cpp @@ -0,0 +1,62 @@ +#include +#include + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +struct Vec2f { + float x, y; +}; + +struct MassPoint { + Vec2f sPosition; + Vec2f sVelocity; + + void step() { + sPosition.x += sVelocity.x; + sPosition.y += sVelocity.y; + } +}; + +class VerletCloth : public olc::PixelGameEngine { +public: + std::vector sPoints; + + VerletCloth() { + sAppName = "Verlet Cloth Simulation"; + } + + bool OnUserCreate() override { + + sPoints.push_back({{ 100.0f, 100.0f }, { 1.0f, 4.0f }}); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override { + Clear(olc::WHITE); + + m_fTimeCounter += fElapsedTime; + + if (m_fTimeCounter >= 0.016f) { + m_fTimeCounter = 0.0f; + for (auto& sPoint : sPoints){ + sPoint.step(); + FillRect(sPoint.sPosition.x, sPoint.sPosition.y, 4, 4, olc::BLACK); + } + } + + return true; + } +private: + float m_fTimeCounter = 0.0f; +}; + +int main(int argc, char** argv) { + VerletCloth app; + + app.Construct(1000, 600, 1, 1); + app.Start(); + + return 0; +} diff --git a/C++/Verlet Cloth/olcPixelGameEngine.h b/C++/Verlet Cloth/olcPixelGameEngine.h new file mode 100644 index 0000000..4458a61 --- /dev/null +++ b/C++/Verlet Cloth/olcPixelGameEngine.h @@ -0,0 +1,2069 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.12 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + 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 + Patreon: https://www.patreon.com/javidx9 + + 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 + Ralakus, Gorbit99, raoul & MagetzUb for advice, ideas and testing, and I'd like + to extend my appreciation to the 23K YouTube followers and 1.5K 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 + + 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 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* 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, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + 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); + Pixel::Mode GetPixelMode(); + // 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 + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + 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 = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_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 = (uint32_t)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[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + 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) + { + w--; h--; + 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; + } + + 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) + { 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_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; + + 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, ""); + + // 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_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_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; + + 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++/Verlet Cloth/output b/C++/Verlet Cloth/output new file mode 100755 index 0000000000000000000000000000000000000000..1316f47436d2f0f1c5ff46c3655b69144caec5dc GIT binary patch literal 165992 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%0A&>J<0`hbeL`Cc~G2g51MQu1ujikXi{4D)^ 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