From 3f28ec7020a89a460a5a4a96ad5d024bec6d9041 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 19 Jan 2019 23:20:57 +0000 Subject: [PATCH] Added Big Project CCC#1 --- CarCrimeCity/Part1/City_Roads1_mip0.png | Bin 0 -> 42193 bytes .../Part1/OneLoneCoder_CarCrimeCity1.cpp | 670 ++++++ CarCrimeCity/Part1/car_top1.png | Bin 0 -> 16926 bytes CarCrimeCity/Part1/example1.city | Bin 0 -> 32776 bytes CarCrimeCity/Part1/olcPGEX_Graphics3D.h | 1174 ++++++++++ CarCrimeCity/Part1/olcPixelGameEngine.h | 2067 +++++++++++++++++ 6 files changed, 3911 insertions(+) create mode 100644 CarCrimeCity/Part1/City_Roads1_mip0.png create mode 100644 CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp create mode 100644 CarCrimeCity/Part1/car_top1.png create mode 100644 CarCrimeCity/Part1/example1.city create mode 100644 CarCrimeCity/Part1/olcPGEX_Graphics3D.h create mode 100644 CarCrimeCity/Part1/olcPixelGameEngine.h diff --git a/CarCrimeCity/Part1/City_Roads1_mip0.png b/CarCrimeCity/Part1/City_Roads1_mip0.png new file mode 100644 index 0000000000000000000000000000000000000000..46cec50ff954a866ba264e4844151b07299b3e2e GIT binary patch literal 42193 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�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 B1+4%8 literal 0 HcmV?d00001 diff --git a/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp b/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp new file mode 100644 index 0000000..8a4c7a7 --- /dev/null +++ b/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp @@ -0,0 +1,670 @@ +/* + BIG PROJECT - Top Down City Based Car Crime Game Part #1 + "Probably gonna regret starting this one..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 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. + + Instructions: + ~~~~~~~~~~~~~ + This is the source that accompanies part 1 of the video series which + can be viewed via the link below. Here you can create and edit a city + from a top down perspective ad navigate it with the car. + + Using the mouse left click you can select cells. Using right click clears + all the selected cells. + + "E" - Lowers building height + "T" - Raises building height + "R" - Places road + "Z, X" - Zoom + "Up, Left, Right" - Control car + "F5" - Save current city + "F8" - Load existing city + + A default city is provided for you - "example1.city", please ensure + you have this file also. + + Relevant Video: https://youtu.be/mD6b_hP17WI + + 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include "olcPGEX_Graphics3D.h" + +#include +#include +#include +#include +#include +#include +#include + +// Override base class with your custom functionality +class CarCrimeCity : public olc::PixelGameEngine +{ +public: + CarCrimeCity() + { + sAppName = "Car Crime City"; + } + +private: + + // Define the cell + struct sCell + { + int nHeight = 0; + int nWorldX = 0; + int nWorldY = 0; + bool bRoad = false; + bool bBuilding = true; + }; + + // Map variables + int nMapWidth; + int nMapHeight; + sCell *pMap; + + olc::Sprite *sprAll; + olc::Sprite *sprGround; + olc::Sprite *sprRoof; + olc::Sprite *sprFrontage; + olc::Sprite *sprWindows; + olc::Sprite *sprRoad[12]; + olc::Sprite *sprCar; + + float fCameraX = 0.0f; + float fCameraY = 0.0f; + float fCameraZ = -10.0f; + + olc::GFX3D::mesh meshCube; + olc::GFX3D::mesh meshFlat; + olc::GFX3D::mesh meshWallsOut; + + float fCarAngle = 0.0f; + float fCarSpeed = 2.0f; + olc::GFX3D::vec3d vecCarVel = { 0,0,0 }; + olc::GFX3D::vec3d vecCarPos = { 0,0,0 }; + + + int nMouseWorldX = 0; + int nMouseWorldY = 0; + int nOldMouseWorldX = 0; + int nOldMouseWorldY = 0; + + bool bMouseDown = false; + std::unordered_set setSelectedCells; + + olc::GFX3D::PipeLine pipeRender; + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::vec3d vUp = { 0,1,0 }; + olc::GFX3D::vec3d vEye = { 0,0,-10 }; + olc::GFX3D::vec3d vLookDir = { 0,0,1 }; + + olc::GFX3D::vec3d viewWorldTopLeft, viewWorldBottomRight; + + + void SaveCity(std::string sFilename) + { + std::ofstream file(sFilename, std::ios::out | std::ios::binary); + file.write((char*)&nMapWidth, sizeof(int)); + file.write((char*)&nMapHeight, sizeof(int)); + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + file.write((char*)&pMap[y*nMapWidth + x], sizeof(sCell)); + } + } + } + + void LoadCity(std::string sFilename) + { + std::ifstream file(sFilename, std::ios::in | std::ios::binary); + file.read((char*)&nMapWidth, sizeof(int)); + file.read((char*)&nMapHeight, sizeof(int)); + delete[] pMap; + pMap = new sCell[nMapWidth * nMapHeight]; + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + file.read((char*)&pMap[y*nMapWidth + x], sizeof(sCell)); + } + } + } + +public: + bool OnUserCreate() override + { + // Load Sprite Sheet + sprAll = new olc::Sprite("City_Roads1_mip0.png"); + + // Here we break up the sprite sheet into individual textures. This is more + // out of convenience than anything else, as it keeps the texture coordinates + // easy to manipulate + + // Building Lowest Floor + sprFrontage = new olc::Sprite(32, 96); + SetDrawTarget(sprFrontage); + DrawPartialSprite(0, 0, sprAll, 288, 64, 32, 96); + + // Building Windows + sprWindows = new olc::Sprite(32, 96); + SetDrawTarget(sprWindows); + DrawPartialSprite(0, 0, sprAll, 320, 64, 32, 96); + + // Plain Grass Field + sprGround = new olc::Sprite(96, 96); + SetDrawTarget(sprGround); + DrawPartialSprite(0, 0, sprAll, 192, 0, 96, 96); + + // Building Roof + sprRoof = new olc::Sprite(96, 96); + SetDrawTarget(sprRoof); + DrawPartialSprite(0, 0, sprAll, 352, 64, 96, 96); + + // There are 12 Road Textures, aranged in a 3x4 grid + for (int r = 0; r < 12; r++) + { + sprRoad[r] = new olc::Sprite(96, 96); + SetDrawTarget(sprRoad[r]); + DrawPartialSprite(0, 0, sprAll, (r%3)*96, (r/3)*96, 96, 96); + } + + // Don't foregt to set the draw target back to being the main screen (been there... wasted 1.5 hours :| ) + SetDrawTarget(nullptr); + + // The Yellow Car + sprCar = new olc::Sprite("car_top.png"); + + + + // Define the city map, a 64x32 array of Cells. Initialise cells + // to be just grass fields + nMapWidth = 64; + nMapHeight = 32; + pMap = new sCell[nMapWidth * nMapHeight]; + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + pMap[y*nMapWidth + x].bRoad = false; + pMap[y*nMapWidth + x].nHeight = 0; + pMap[y*nMapWidth + x].nWorldX = x; + pMap[y*nMapWidth + x].nWorldY = y; + } + } + + + // Now we'll hand construct some meshes. These are DELIBERATELY simple and not optimised (see a later video) + // Here the geometry is unit in size (1x1x1) + + // A Full cube - Always useful for debugging + meshCube.tris = + { + // SOUTH + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // EAST + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // NORTH + { 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // WEST + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // TOP + { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // BOTTOM + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + }; + + // A Flat quad + meshFlat.tris = + { + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + }; + + // The four outer walls of a cell + meshWallsOut.tris = + { + // EAST + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, }, + + // WEST + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // TOP + { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, }, + { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // BOTTOM + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + }; + + + // Initialise the 3D Graphics PGE Extension. This is required + // to setup internal buffers to the same size as the main output + olc::GFX3D::ConfigureDisplay(); + + // Configure the rendering pipeline with projection and viewport properties + pipeRender.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f, 0.0f, 0.0f, ScreenWidth(), ScreenHeight()); + + // Also make a projection matrix, we might need this later + matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f); + + LoadCity("example1.city"); + + // Ok, lets go! + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Directly manipulate camera + //if (GetKey(olc::Key::W).bHeld) fCameraY -= 2.0f * fElapsedTime; + //if (GetKey(olc::Key::S).bHeld) fCameraY += 2.0f * fElapsedTime; + //if (GetKey(olc::Key::A).bHeld) fCameraX -= 2.0f * fElapsedTime; + //if (GetKey(olc::Key::D).bHeld) fCameraX += 2.0f * fElapsedTime; + if (GetKey(olc::Key::Z).bHeld) fCameraZ += 5.0f * fElapsedTime; + if (GetKey(olc::Key::X).bHeld) fCameraZ -= 5.0f * fElapsedTime; + + if (GetKey(olc::Key::F5).bReleased) SaveCity("example1.city"); + if (GetKey(olc::Key::F8).bReleased) LoadCity("example1.city"); + + // === Handle User Input for Editing == + + // If there are no selected cells, then only edit the cell under the current mouse cursor + // otherwise iterate through the set of sleected cells and apply to all of them + + // Check that cell exists in valid 2D map space + if (nMouseWorldX >= 0 && nMouseWorldX < nMapWidth && nMouseWorldY >= 0 && nMouseWorldY < nMapHeight) + { + // Press "R" to toggle Road flag for selected cell(s) + if (GetKey(olc::Key::R).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->bRoad = !cell->bRoad; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].bRoad = !pMap[nMouseWorldY*nMapWidth + nMouseWorldX].bRoad; + } + + // Press "T" to increase height for selected cell(s) + if (GetKey(olc::Key::T).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->nHeight++; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].nHeight++; + } + + // Press "E" to decrease height for selected cell(s) + if (GetKey(olc::Key::E).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->nHeight--; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].nHeight--; + } + } + + + // === Car User Input === + + if (GetKey(olc::Key::LEFT).bHeld) fCarAngle -= 4.0f * fElapsedTime; + if (GetKey(olc::Key::RIGHT).bHeld) fCarAngle += 4.0f * fElapsedTime; + + olc::GFX3D::vec3d a = { 1, 0, 0 }; + olc::GFX3D::mat4x4 m = olc::GFX3D::Math::Mat_MakeRotationZ(fCarAngle); + vecCarVel = olc::GFX3D::Math::Mat_MultiplyVector(m, a); + + if (GetKey(olc::Key::UP).bHeld) + { + vecCarPos.x += vecCarVel.x * fCarSpeed * fElapsedTime; + vecCarPos.y += vecCarVel.y * fCarSpeed * fElapsedTime; + } + + // === Position Camera === + + // Our camera currently follows the car, and the car stays in the middle of + // the screen. We need to know where the camera is before we can work with + // on screen interactions + fCameraY = vecCarPos.y; + fCameraX = vecCarPos.x; + vEye = { fCameraX,fCameraY,fCameraZ }; + olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir); + + // Setup the camera properties for the pipeline - aka "view" transform + pipeRender.SetCamera(vEye, vLookTarget, vUp); + + + // === Calculate Mouse Position on Ground Plane === + + // Here we take the screen coordinate of the mouse, transform it into normalised space (-1 --> +1) + // for both axes. Instead of inverting and multiplying by the projection matrix, we only need the + // aspect ratio parameters, with which we'll scale the mouse coordinate. This new point is then + // multiplied by the inverse of the look at matrix (camera view matrix) aka a point at matrix, to + // transform the new point into world space. + // + // Now, the thing is, a 2D point is no good on its own, our world has depth. If we treat the 2D + // point as a ray cast from (0, 0)->(mx, my), we can see where this ray intersects with a plane + // at a specific depth. + + // Create a point at matrix, if you recall, this is the inverse of the look at matrix + // used by the camera + olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp); + + // Assume the origin of the mouse ray is the middle of the screen... + olc::GFX3D::vec3d vecMouseOrigin = { 0.0f, 0.0f, 0.0f }; + + // ...and that a ray is cast to the mouse location from the origin. Here we translate + // the mouse coordinates into viewport coordinates + olc::GFX3D::vec3d vecMouseDir = { + 2.0f * ((GetMouseX() / (float)ScreenWidth()) - 0.5f) / matProj.m[0][0], + 2.0f * ((GetMouseY() / (float)ScreenHeight()) - 0.5f) / matProj.m[1][1], + 1.0f, + 0.0f }; + + // Now transform the origin point and ray direction by the inverse of the camera + vecMouseOrigin = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseOrigin); + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + + // Extend the mouse ray to a large length + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + + // Offset the mouse ray by the mouse origin + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + + // All of our intersections for mouse checks occur in the ground plane (z=0), so + // define a plane at that location + olc::GFX3D::vec3d plane_p = { 0.0f, 0.0f, 0.0f }; + olc::GFX3D::vec3d plane_n = { 0.0f, 0.0f, 1.0f }; + + // Calculate Mouse Location in plane, by doing a line/plane intersection test + float t = 0.0f; + olc::GFX3D::vec3d mouse3d = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + + + // === Now we have the mouse in 3D! Handle mouse user input === + + // Left click & left click drag selects cells by adding them to the set of selected cells + // Here I make sure only to do this if the cell under the mouse has changed from the + // previous frame, but the set will also reject duplicate cells being added + if (GetMouse(0).bHeld && ((nMouseWorldX != nOldMouseWorldX) || (nMouseWorldY != nOldMouseWorldY))) + setSelectedCells.emplace(&pMap[nMouseWorldY * nMapWidth + nMouseWorldX]); + + // Single clicking cells also adds them + if (GetMouse(0).bPressed) + setSelectedCells.emplace(&pMap[nMouseWorldY * nMapWidth + nMouseWorldX]); + + // If the user right clicks, the set of selected cells is emptied + if (GetMouse(1).bReleased) + setSelectedCells.clear(); + + // Cache the current mouse position to use during comparison in next frame + nOldMouseWorldX = nMouseWorldX; + nOldMouseWorldY = nMouseWorldY; + + nMouseWorldX = (int)mouse3d.x; + nMouseWorldY = (int)mouse3d.y; + + + + + // === Rendering === + + // Right, now we're gonna render the scene! + + // First Clear the screen and the depth buffer + Clear(olc::BLUE); + olc::GFX3D::ClearDepth(); + + // === Calculate Visible World === + + // Since we now have the transforms to convert screen space into ground plane space, we + // can calculate the visible extents of the world, regardless of zoom level! The method is + // exactly the same for the mouse, but we use fixed screen coordinates that represent the + // top left, and bottom right of the screen + + // Work out Top Left Ground Cell + vecMouseDir = { -1.0f / matProj.m[0][0],-1.0f / matProj.m[1][1], 1.0f, 0.0f }; + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + viewWorldTopLeft = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + // Work out Bottom Right Ground Cell + vecMouseDir = { 1.0f / matProj.m[0][0], 1.0f / matProj.m[1][1], 1.0f, 0.0f }; + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + viewWorldBottomRight = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + // Calculate visible tiles + //int nStartX = 0; + //int nEndX = nMapWidth; + //int nStartY = 0; + //int nEndY = nMapHeight; + + int nStartX = std::max(0, (int)viewWorldTopLeft.x - 1); + int nEndX = std::min(nMapWidth, (int)viewWorldBottomRight.x + 1); + int nStartY = std::max(0, (int)viewWorldTopLeft.y - 1); + int nEndY = std::min(nMapHeight, (int)viewWorldBottomRight.y + 1); + + + // Iterate through all the cells we wish to draw. Each cell is 1x1 and elevates in the Z -Axis + for (int x = nStartX; x < nEndX; x++) + { + for (int y = nStartY; y < nEndY; y++) + { + if (pMap[y*nMapWidth + x].bRoad) + { + // Cell is a road, look at neighbouring cells. If they are roads also, + // then choose the appropriate texture that joins up correctly + + int road = 0; + auto r = [&](int i, int j) + { + return pMap[(y + j) * nMapWidth + (x + i)].bRoad; + }; + + if (r(0, -1) && r(0, +1) && !r(-1, 0) && !r(+1, 0)) road = 0; + if (!r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) road = 1; + + if (!r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 3; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) road = 4; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 5; + + if (r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 6; + if (r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) road = 7; + if (r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 8; + + if (r(0, -1) && !r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 9; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) road = 10; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 11; + + // Create a translation transform to position the cell in the world + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, 0.0f); + pipeRender.SetTransform(matWorld); + + // Set the appropriate texture to use + pipeRender.SetTexture(sprRoad[road]); + + // Draw a flat quad + pipeRender.Render(meshFlat.tris); + + } + else // Not Road + { + // If the cell is not considered road, then draw it appropriately + + if (pMap[y*nMapWidth + x].nHeight < 0) + { + // Cell is blank - for now ;-P + } + + if (pMap[y*nMapWidth + x].nHeight == 0) + { + // Cell is ground, draw a flat grass quad at height 0 + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, 0.0f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprGround); + pipeRender.Render(meshFlat.tris); + } + + if (pMap[y*nMapWidth + x].nHeight > 0) + { + // Cell is Building, for now, we'll draw each storey as a seperate mesh + int h, t; + t = pMap[y*nMapWidth + x].nHeight; + + for (h = 0; h < t; h++) + { + // Create a transform that positions the storey according to its height + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, -(h + 1) * 0.2f); + pipeRender.SetTransform(matWorld); + + // Choose a texture, if its ground level, use the "street level front", otherwise use windows + pipeRender.SetTexture(h == 0 ? sprFrontage : sprWindows); + pipeRender.Render(meshWallsOut.tris); + } + + // Top the building off with a roof + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, -(h) * 0.2f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprRoof); + pipeRender.Render(meshFlat.tris); + } + } + } + } + + // Draw Selected Cells, iterate through the set of cells, and draw a wireframe quad at ground level + // to indicate it is in the selection set + for (auto &cell : setSelectedCells) + { + // Draw CursorCube + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(cell->nWorldX, cell->nWorldY, 0.0f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprRoof); + pipeRender.Render(meshFlat.tris, olc::GFX3D::RENDER_WIRE); + } + + // Draw Car, a few transforms required for this + + // 1) Offset the car to the middle of the quad + olc::GFX3D::mat4x4 matCarOffset = olc::GFX3D::Math::Mat_MakeTranslation(-0.5f, -0.5f, -0.0f); + // 2) The quad is currently unit square, scale it to be more rectangular and smaller than the cells + olc::GFX3D::mat4x4 matCarScale = olc::GFX3D::Math::Mat_MakeScale(0.4f, 0.2f, 1.0f); + // 3) Combine into matrix + olc::GFX3D::mat4x4 matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCarOffset, matCarScale); + // 4) Rotate the car around its offset origin, according to its angle + olc::GFX3D::mat4x4 matCarRot = olc::GFX3D::Math::Mat_MakeRotationZ(fCarAngle); + matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCar, matCarRot); + // 5) Translate the car into its position in the world. Give it a little elevation so its baove the ground + olc::GFX3D::mat4x4 matCarTrans = olc::GFX3D::Math::Mat_MakeTranslation(vecCarPos.x, vecCarPos.y, -0.01f); + matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCar, matCarTrans); + + // Set the car texture to the pipeline + pipeRender.SetTexture(sprCar); + // Apply "world" transform to pipeline + pipeRender.SetTransform(matCar); + + // The car has transparency, so enable it + SetPixelMode(olc::Pixel::ALPHA); + // Render the quad + pipeRender.Render(meshFlat.tris); + // Set transparency back to none to optimise drawing other pixels + SetPixelMode(olc::Pixel::NORMAL); + + + // Draw the current camera position for debug information + //DrawString(10, 30, "CX: " + std::to_string(fCameraX) + " CY: " + std::to_string(fCameraY) + " CZ: " + std::to_string(fCameraZ)); + return true; + } +}; + + + +int main() +{ + CarCrimeCity demo; + if (demo.Construct(768, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/CarCrimeCity/Part1/car_top1.png b/CarCrimeCity/Part1/car_top1.png new file mode 100644 index 0000000000000000000000000000000000000000..15ceb1d9684af85408c87d1e1398293763c240d8 GIT binary patch literal 16926 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 literal 0 HcmV?d00001 diff --git a/CarCrimeCity/Part1/example1.city b/CarCrimeCity/Part1/example1.city new file mode 100644 index 0000000000000000000000000000000000000000..47dfb0ba41cc1f2391547cbf3aab1556f6f50830 GIT binary patch literal 32776 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 KAAjTAp8vlH?JM*E literal 0 HcmV?d00001 diff --git a/CarCrimeCity/Part1/olcPGEX_Graphics3D.h b/CarCrimeCity/Part1/olcPGEX_Graphics3D.h new file mode 100644 index 0000000..954e776 --- /dev/null +++ b/CarCrimeCity/Part1/olcPGEX_Graphics3D.h @@ -0,0 +1,1174 @@ +/* + olcPGEX_Graphics3D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | 3D Rendering - v0.1 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + support for software rendering 3D graphics. + + NOTE!!! This file is under development and may change! + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_GFX3D +#define OLC_PGEX_GFX3D + +#include +#include +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX3D : public olc::PGEX + { + + public: + + struct vec2d + { + float x = 0; + float y = 0; + float z = 0; + }; + + struct vec3d + { + float x = 0; + float y = 0; + float z = 0; + float w = 1; // Need a 4th term to perform sensible matrix vector multiplication + }; + + struct triangle + { + vec3d p[3]; + vec2d t[3]; + olc::Pixel col; + }; + + struct mat4x4 + { + float m[4][4] = { 0 }; + }; + + struct mesh + { + std::vector tris; + }; + + class Math + { + public: + inline Math(); + public: + inline static vec3d Mat_MultiplyVector(mat4x4 &m, vec3d &i); + inline static mat4x4 Mat_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2); + inline static mat4x4 Mat_MakeIdentity(); + inline static mat4x4 Mat_MakeRotationX(float fAngleRad); + inline static mat4x4 Mat_MakeRotationY(float fAngleRad); + inline static mat4x4 Mat_MakeRotationZ(float fAngleRad); + inline static mat4x4 Mat_MakeScale(float x, float y, float z); + inline static mat4x4 Mat_MakeTranslation(float x, float y, float z); + inline static mat4x4 Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar); + inline static mat4x4 Mat_PointAt(vec3d &pos, vec3d &target, vec3d &up); + inline static mat4x4 Mat_QuickInverse(mat4x4 &m); // Only for Rotation/Translation Matrices + inline static mat4x4 Mat_Inverse(olc::GFX3D::mat4x4 &m); + + inline static vec3d Vec_Add(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Sub(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Mul(vec3d &v1, float k); + inline static vec3d Vec_Div(vec3d &v1, float k); + inline static float Vec_DotProduct(vec3d &v1, vec3d &v2); + inline static float Vec_Length(vec3d &v); + inline static vec3d Vec_Normalise(vec3d &v); + inline static vec3d Vec_CrossProduct(vec3d &v1, vec3d &v2); + inline static vec3d Vec_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd, float &t); + + inline static int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2); + }; + + enum RENDERFLAGS + { + RENDER_WIRE = 0x01, + RENDER_FLAT = 0x02, + RENDER_TEXTURED = 0x04, + RENDER_CULL_CW = 0x08, + RENDER_CULL_CCW = 0x10, + RENDER_DEPTH = 0x20, + }; + + + class PipeLine + { + public: + PipeLine(); + + public: + void SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight); + void SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up); + void SetTransform(olc::GFX3D::mat4x4 &transform); + void SetTexture(olc::Sprite *texture); + void SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col); + uint32_t Render(std::vector &triangles, uint32_t flags = RENDER_CULL_CW | RENDER_TEXTURED | RENDER_DEPTH); + + private: + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::mat4x4 matView; + olc::GFX3D::mat4x4 matWorld; + olc::Sprite *sprTexture; + float fViewX; + float fViewY; + float fViewW; + float fViewH; + }; + + + + public: + //static const int RF_TEXTURE = 0x00000001; + //static const int RF_ = 0x00000002; + + inline static void ConfigureDisplay(); + inline static void ClearDepth(); + inline static void AddTriangleToScene(olc::GFX3D::triangle &tri); + inline static void RenderScene(); + + inline static void DrawTriangleFlat(olc::GFX3D::triangle &tri); + inline static void DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col = olc::WHITE); + inline static void DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr); + inline static void TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr); + + // Draws a sprite with the transform applied + //inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + + private: + static float* m_DepthBuffer; + }; +} + + + + +namespace olc +{ + olc::GFX3D::Math::Math() + { + + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Mat_MultiplyVector(olc::GFX3D::mat4x4 &m, olc::GFX3D::vec3d &i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeIdentity() + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationX(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationY(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationZ(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeScale(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = x; + matrix.m[1][1] = y; + matrix.m[2][2] = z; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeTranslation(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MultiplyMatrix(olc::GFX3D::mat4x4 &m1, olc::GFX3D::mat4x4 &m2) + { + olc::GFX3D::mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_PointAt(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &target, olc::GFX3D::vec3d &up) + { + // Calculate new forward direction + olc::GFX3D::vec3d newForward = Vec_Sub(target, pos); + newForward = Vec_Normalise(newForward); + + // Calculate new Up direction + olc::GFX3D::vec3d a = Vec_Mul(newForward, Vec_DotProduct(up, newForward)); + olc::GFX3D::vec3d newUp = Vec_Sub(up, a); + newUp = Vec_Normalise(newUp); + + // New Right direction is easy, its just cross product + olc::GFX3D::vec3d newRight = Vec_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_QuickInverse(olc::GFX3D::mat4x4 &m) // Only for Rotation/Translation Matrices + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_Inverse(olc::GFX3D::mat4x4 &m) + { + double det; + + + mat4x4 matInv; + + matInv.m[0][0] = m.m[1][1] * m.m[2][2] * m.m[3][3] - m.m[1][1] * m.m[2][3] * m.m[3][2] - m.m[2][1] * m.m[1][2] * m.m[3][3] + m.m[2][1] * m.m[1][3] * m.m[3][2] + m.m[3][1] * m.m[1][2] * m.m[2][3] - m.m[3][1] * m.m[1][3] * m.m[2][2]; + matInv.m[1][0] = -m.m[1][0] * m.m[2][2] * m.m[3][3] + m.m[1][0] * m.m[2][3] * m.m[3][2] + m.m[2][0] * m.m[1][2] * m.m[3][3] - m.m[2][0] * m.m[1][3] * m.m[3][2] - m.m[3][0] * m.m[1][2] * m.m[2][3] + m.m[3][0] * m.m[1][3] * m.m[2][2]; + matInv.m[2][0] = m.m[1][0] * m.m[2][1] * m.m[3][3] - m.m[1][0] * m.m[2][3] * m.m[3][1] - m.m[2][0] * m.m[1][1] * m.m[3][3] + m.m[2][0] * m.m[1][3] * m.m[3][1] + m.m[3][0] * m.m[1][1] * m.m[2][3] - m.m[3][0] * m.m[1][3] * m.m[2][1]; + matInv.m[3][0] = -m.m[1][0] * m.m[2][1] * m.m[3][2] + m.m[1][0] * m.m[2][2] * m.m[3][1] + m.m[2][0] * m.m[1][1] * m.m[3][2] - m.m[2][0] * m.m[1][2] * m.m[3][1] - m.m[3][0] * m.m[1][1] * m.m[2][2] + m.m[3][0] * m.m[1][2] * m.m[2][1]; + matInv.m[0][1] = -m.m[0][1] * m.m[2][2] * m.m[3][3] + m.m[0][1] * m.m[2][3] * m.m[3][2] + m.m[2][1] * m.m[0][2] * m.m[3][3] - m.m[2][1] * m.m[0][3] * m.m[3][2] - m.m[3][1] * m.m[0][2] * m.m[2][3] + m.m[3][1] * m.m[0][3] * m.m[2][2]; + matInv.m[1][1] = m.m[0][0] * m.m[2][2] * m.m[3][3] - m.m[0][0] * m.m[2][3] * m.m[3][2] - m.m[2][0] * m.m[0][2] * m.m[3][3] + m.m[2][0] * m.m[0][3] * m.m[3][2] + m.m[3][0] * m.m[0][2] * m.m[2][3] - m.m[3][0] * m.m[0][3] * m.m[2][2]; + matInv.m[2][1] = -m.m[0][0] * m.m[2][1] * m.m[3][3] + m.m[0][0] * m.m[2][3] * m.m[3][1] + m.m[2][0] * m.m[0][1] * m.m[3][3] - m.m[2][0] * m.m[0][3] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[2][3] + m.m[3][0] * m.m[0][3] * m.m[2][1]; + matInv.m[3][1] = m.m[0][0] * m.m[2][1] * m.m[3][2] - m.m[0][0] * m.m[2][2] * m.m[3][1] - m.m[2][0] * m.m[0][1] * m.m[3][2] + m.m[2][0] * m.m[0][2] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[2][2] - m.m[3][0] * m.m[0][2] * m.m[2][1]; + matInv.m[0][2] = m.m[0][1] * m.m[1][2] * m.m[3][3] - m.m[0][1] * m.m[1][3] * m.m[3][2] - m.m[1][1] * m.m[0][2] * m.m[3][3] + m.m[1][1] * m.m[0][3] * m.m[3][2] + m.m[3][1] * m.m[0][2] * m.m[1][3] - m.m[3][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][2] = -m.m[0][0] * m.m[1][2] * m.m[3][3] + m.m[0][0] * m.m[1][3] * m.m[3][2] + m.m[1][0] * m.m[0][2] * m.m[3][3] - m.m[1][0] * m.m[0][3] * m.m[3][2] - m.m[3][0] * m.m[0][2] * m.m[1][3] + m.m[3][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][2] = m.m[0][0] * m.m[1][1] * m.m[3][3] - m.m[0][0] * m.m[1][3] * m.m[3][1] - m.m[1][0] * m.m[0][1] * m.m[3][3] + m.m[1][0] * m.m[0][3] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[1][3] - m.m[3][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][2] = -m.m[0][0] * m.m[1][1] * m.m[3][2] + m.m[0][0] * m.m[1][2] * m.m[3][1] + m.m[1][0] * m.m[0][1] * m.m[3][2] - m.m[1][0] * m.m[0][2] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[1][2] + m.m[3][0] * m.m[0][2] * m.m[1][1]; + matInv.m[0][3] = -m.m[0][1] * m.m[1][2] * m.m[2][3] + m.m[0][1] * m.m[1][3] * m.m[2][2] + m.m[1][1] * m.m[0][2] * m.m[2][3] - m.m[1][1] * m.m[0][3] * m.m[2][2] - m.m[2][1] * m.m[0][2] * m.m[1][3] + m.m[2][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][3] = m.m[0][0] * m.m[1][2] * m.m[2][3] - m.m[0][0] * m.m[1][3] * m.m[2][2] - m.m[1][0] * m.m[0][2] * m.m[2][3] + m.m[1][0] * m.m[0][3] * m.m[2][2] + m.m[2][0] * m.m[0][2] * m.m[1][3] - m.m[2][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][3] = -m.m[0][0] * m.m[1][1] * m.m[2][3] + m.m[0][0] * m.m[1][3] * m.m[2][1] + m.m[1][0] * m.m[0][1] * m.m[2][3] - m.m[1][0] * m.m[0][3] * m.m[2][1] - m.m[2][0] * m.m[0][1] * m.m[1][3] + m.m[2][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][3] = m.m[0][0] * m.m[1][1] * m.m[2][2] - m.m[0][0] * m.m[1][2] * m.m[2][1] - m.m[1][0] * m.m[0][1] * m.m[2][2] + m.m[1][0] * m.m[0][2] * m.m[2][1] + m.m[2][0] * m.m[0][1] * m.m[1][2] - m.m[2][0] * m.m[0][2] * m.m[1][1]; + + det = m.m[0][0] * matInv.m[0][0] + m.m[0][1] * matInv.m[1][0] + m.m[0][2] * matInv.m[2][0] + m.m[0][3] * matInv.m[3][0]; + // if (det == 0) return false; + + det = 1.0 / det; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + matInv.m[i][j] *= (float)det; + + return matInv; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Add(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Sub(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Mul(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Div(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float olc::GFX3D::Math::Vec_DotProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z; + } + + float olc::GFX3D::Math::Vec_Length(olc::GFX3D::vec3d &v) + { + return sqrtf(Vec_DotProduct(v, v)); + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Normalise(olc::GFX3D::vec3d &v) + { + float l = Vec_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_CrossProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_IntersectPlane(olc::GFX3D::vec3d &plane_p, olc::GFX3D::vec3d &plane_n, olc::GFX3D::vec3d &lineStart, olc::GFX3D::vec3d &lineEnd, float &t) + { + plane_n = Vec_Normalise(plane_n); + float plane_d = -Vec_DotProduct(plane_n, plane_p); + float ad = Vec_DotProduct(lineStart, plane_n); + float bd = Vec_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + olc::GFX3D::vec3d lineStartToEnd = Vec_Sub(lineEnd, lineStart); + olc::GFX3D::vec3d lineToIntersect = Vec_Mul(lineStartToEnd, t); + return Vec_Add(lineStart, lineToIntersect); + } + + + int olc::GFX3D::Math::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Math::Vec_Normalise(plane_n); + + out_tri1.t[0] = in_tri.t[0]; + out_tri2.t[0] = in_tri.t[0]; + out_tri1.t[1] = in_tri.t[1]; + out_tri2.t[1] = in_tri.t[1]; + out_tri1.t[2] = in_tri.t[2]; + out_tri2.t[2] = in_tri.t[2]; + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d &p) + { + vec3d n = Math::Vec_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Math::Vec_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.t[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.t[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.t[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.t[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.t[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.t[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col = olc::MAGENTA;// in_tri.col; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[1].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[1].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[1].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.t[2].x = t * (outside_tex[1]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[1]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[1]->z - inside_tex[0]->z) + inside_tex[0]->z; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col = olc::GREEN;// in_tri.col; + out_tri2.col = olc::RED;// in_tri.col; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + out_tri1.p[1] = *inside_points[1]; + out_tri1.t[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[2].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[1] = *inside_points[1]; + out_tri2.t[1] = *inside_tex[1]; + out_tri2.p[0] = out_tri1.p[2]; + out_tri2.t[0] = out_tri1.t[2]; + out_tri2.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.t[2].x = t * (outside_tex[0]->x - inside_tex[1]->x) + inside_tex[1]->x; + out_tri2.t[2].y = t * (outside_tex[0]->y - inside_tex[1]->y) + inside_tex[1]->y; + out_tri2.t[2].z = t * (outside_tex[0]->z - inside_tex[1]->z) + inside_tex[1]->z; + return 2; // Return two newly formed triangles which form a quad + } + + return 0; + } + + void GFX3D::DrawTriangleFlat(olc::GFX3D::triangle &tri) + { + pge->FillTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, tri.col); + } + + void GFX3D::DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col) + { + pge->DrawTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, col); + } + + void GFX3D::TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr) + + { + if (y2 < y1) + { + std::swap(y1, y2); + std::swap(x1, x2); + std::swap(u1, u2); + std::swap(v1, v2); + std::swap(w1, w2); + } + + if (y3 < y1) + { + std::swap(y1, y3); + std::swap(x1, x3); + std::swap(u1, u3); + std::swap(v1, v3); + std::swap(w1, w3); + } + + if (y3 < y2) + { + std::swap(y2, y3); + std::swap(x2, x3); + std::swap(u2, u3); + std::swap(v2, v3); + std::swap(w2, w3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + + float tex_u, tex_v, tex_w; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0, dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + } + } + } + + + void GFX3D::DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr) + { + if (tri.p[1].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[1].y); + std::swap(tri.p[0].x, tri.p[1].x); + std::swap(tri.t[0].x, tri.t[1].x); + std::swap(tri.t[0].y, tri.t[1].y); + std::swap(tri.t[0].z, tri.t[1].z); + } + + if (tri.p[2].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[2].y); + std::swap(tri.p[0].x, tri.p[2].x); + std::swap(tri.t[0].x, tri.t[2].x); + std::swap(tri.t[0].y, tri.t[2].y); + std::swap(tri.t[0].z, tri.t[2].z); + } + + if (tri.p[2].y < tri.p[1].y) + { + std::swap(tri.p[1].y, tri.p[2].y); + std::swap(tri.p[1].x, tri.p[2].x); + std::swap(tri.t[1].x, tri.t[2].x); + std::swap(tri.t[1].y, tri.t[2].y); + std::swap(tri.t[1].z, tri.t[2].z); + } + + int dy1 = tri.p[1].y - tri.p[0].y; + int dx1 = tri.p[1].x - tri.p[0].x; + float dv1 = tri.t[1].y - tri.t[0].y; + float du1 = tri.t[1].x - tri.t[0].x; + float dz1 = tri.t[1].z - tri.t[0].z; + + int dy2 = tri.p[2].y - tri.p[0].y; + int dx2 = tri.p[2].x - tri.p[0].x; + float dv2 = tri.t[2].y - tri.t[0].y; + float du2 = tri.t[2].x - tri.t[0].x; + float dz2 = tri.t[2].z - tri.t[0].z; + + float tex_x, tex_y, tex_z; + + float du1_step = 0, dv1_step = 0, du2_step = 0, dv2_step = 0, dz1_step = 0, dz2_step = 0; + float dax_step = 0, dbx_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dz2_step = dz2 / (float)abs(dy2); + + + + if (dy1) + { + for (int i = tri.p[0].y; i <= tri.p[1].y; i++) + { + int ax = tri.p[0].x + (i - tri.p[0].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[0].x + (float)(i - tri.p[0].y) * du1_step; + float tex_sv = tri.t[0].y + (float)(i - tri.p[0].y) * dv1_step; + float tex_sz = tri.t[0].z + (float)(i - tri.p[0].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + t += tstep; + } + + + } + } + + dy1 = tri.p[2].y - tri.p[1].y; + dx1 = tri.p[2].x - tri.p[1].x; + dv1 = tri.t[2].y - tri.t[1].y; + du1 = tri.t[2].x - tri.t[1].x; + dz1 = tri.t[2].z - tri.t[1].z; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + + du1_step = 0, dv1_step = 0;// , dz1_step = 0;// , du2_step = 0, dv2_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy1) + { + for (int i = tri.p[1].y; i <= tri.p[2].y; i++) + { + int ax = tri.p[1].x + (i - tri.p[1].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[1].x + (float)(i - tri.p[1].y) * du1_step; + float tex_sv = tri.t[1].y + (float)(i - tri.p[1].y) * dv1_step; + float tex_sz = tri.t[1].z + (float)(i - tri.p[1].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + + t += tstep; + } + } + } + + } + + float* GFX3D::m_DepthBuffer = nullptr; + + void GFX3D::ConfigureDisplay() + { + m_DepthBuffer = new float[pge->ScreenWidth() * pge->ScreenHeight()]{ 0 }; + } + + + void GFX3D::ClearDepth() + { + memset(m_DepthBuffer, 0, pge->ScreenWidth() * pge->ScreenHeight() * sizeof(float)); + } + + + + + GFX3D::PipeLine::PipeLine() + { + + } + + void GFX3D::PipeLine::SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight) + { + matProj = GFX3D::Math::Mat_MakeProjection(fFovDegrees, fAspectRatio, fNear, fFar); + fViewX = fLeft; + fViewY = fTop; + fViewW = fWidth; + fViewH = fHeight; + } + + void GFX3D::PipeLine::SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up) + { + matView = GFX3D::Math::Mat_PointAt(pos, lookat, up); + matView = GFX3D::Math::Mat_QuickInverse(matView); + } + + void GFX3D::PipeLine::SetTransform(olc::GFX3D::mat4x4 &transform) + { + matWorld = transform; + } + + void GFX3D::PipeLine::SetTexture(olc::Sprite *texture) + { + sprTexture = texture; + } + + void GFX3D::PipeLine::SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col) + { + + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags) + { + // Calculate Transformation Matrix + mat4x4 matWorldView = Math::Mat_MultiplyMatrix(matWorld, matView); + //matWorldViewProj = Math::Mat_MultiplyMatrix(matWorldView, matProj); + + // Store triangles for rastering later + std::vector vecTrianglesToRaster; + + int nTriangleDrawnCount = 0; + + // Process Triangles + for (auto &tri : triangles) + { + GFX3D::triangle triTransformed; + + // Just copy through texture coordinates + triTransformed.t[0] = { tri.t[0].x, tri.t[0].y, tri.t[0].z }; + triTransformed.t[1] = { tri.t[1].x, tri.t[1].y, tri.t[1].z }; + triTransformed.t[2] = { tri.t[2].x, tri.t[2].y, tri.t[2].z }; // Think! + + // Transform Triangle from object into projected space + triTransformed.p[0] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[0]); + triTransformed.p[1] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[1]); + triTransformed.p[2] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[2]); + + // Calculate Triangle Normal in WorldView Space + GFX3D::vec3d normal, line1, line2; + line1 = GFX3D::Math::Vec_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = GFX3D::Math::Vec_Sub(triTransformed.p[2], triTransformed.p[0]); + normal = GFX3D::Math::Vec_CrossProduct(line1, line2); + normal = GFX3D::Math::Vec_Normalise(normal); + + // Cull triangles that face away from viewer + if (flags & RENDER_CULL_CW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) > 0.0f) continue; + if (flags & RENDER_CULL_CCW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) < 0.0f) continue; + + // If Lighting, calculate shading + triTransformed.col = olc::WHITE; + + // Clip triangle against near plane + int nClippedTriangles = 0; + triangle clipped[2]; + nClippedTriangles = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triTransformed, clipped[0], clipped[1]); + + // This may yield two new triangles + for (int n = 0; n < nClippedTriangles; n++) + { + triangle triProjected = clipped[n]; + + // Project new triangle + triProjected.p[0] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[2]); + + // Apply Projection to Verts + triProjected.p[0].x = triProjected.p[0].x / triProjected.p[0].w; + triProjected.p[1].x = triProjected.p[1].x / triProjected.p[1].w; + triProjected.p[2].x = triProjected.p[2].x / triProjected.p[2].w; + + triProjected.p[0].y = triProjected.p[0].y / triProjected.p[0].w; + triProjected.p[1].y = triProjected.p[1].y / triProjected.p[1].w; + triProjected.p[2].y = triProjected.p[2].y / triProjected.p[2].w; + + triProjected.p[0].z = triProjected.p[0].z / triProjected.p[0].w; + triProjected.p[1].z = triProjected.p[1].z / triProjected.p[1].w; + triProjected.p[2].z = triProjected.p[2].z / triProjected.p[2].w; + + // Apply Projection to Tex coords + triProjected.t[0].x = triProjected.t[0].x / triProjected.p[0].w; + triProjected.t[1].x = triProjected.t[1].x / triProjected.p[1].w; + triProjected.t[2].x = triProjected.t[2].x / triProjected.p[2].w; + + triProjected.t[0].y = triProjected.t[0].y / triProjected.p[0].w; + triProjected.t[1].y = triProjected.t[1].y / triProjected.p[1].w; + triProjected.t[2].y = triProjected.t[2].y / triProjected.p[2].w; + + triProjected.t[0].z = 1.0f / triProjected.p[0].w; + triProjected.t[1].z = 1.0f / triProjected.p[1].w; + triProjected.t[2].z = 1.0f / triProjected.p[2].w; + + // Clip against viewport in screen space + // Clip triangles against all four screen edges, this could yield + // a bunch of triangles, so create a queue that we traverse to + // ensure we only test new triangles generated against planes + triangle sclipped[2]; + std::list listTriangles; + + + // Add initial triangle + listTriangles.push_back(triProjected); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 1: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, +1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 2: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 3: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ +1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + } + + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(sclipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto &triRaster : listTriangles) + { + // Scale to viewport + /*triRaster.p[0].x *= -1.0f; + triRaster.p[1].x *= -1.0f; + triRaster.p[2].x *= -1.0f; + triRaster.p[0].y *= -1.0f; + triRaster.p[1].y *= -1.0f; + triRaster.p[2].y *= -1.0f;*/ + vec3d vOffsetView = { 1,1,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + triRaster.p[0].x *= 0.5f * fViewW; + triRaster.p[0].y *= 0.5f * fViewH; + triRaster.p[1].x *= 0.5f * fViewW; + triRaster.p[1].y *= 0.5f * fViewH; + triRaster.p[2].x *= 0.5f * fViewW; + triRaster.p[2].y *= 0.5f * fViewH; + vOffsetView = { fViewX,fViewY,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + + // For now, just draw triangle + + if (flags & RENDER_TEXTURED) + { + TexturedTriangle( + triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, + triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, + triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, + sprTexture); + } + + if (flags & RENDER_WIRE) + { + DrawTriangleWire(triRaster, olc::RED); + } + + if (flags & RENDER_FLAT) + { + DrawTriangleFlat(triRaster); + } + + nTriangleDrawnCount++; + } + } + } + + return nTriangleDrawnCount; + } +} + +#endif \ No newline at end of file diff --git a/CarCrimeCity/Part1/olcPixelGameEngine.h b/CarCrimeCity/Part1/olcPixelGameEngine.h new file mode 100644 index 0000000..37c3ae8 --- /dev/null +++ b/CarCrimeCity/Part1/olcPixelGameEngine.h @@ -0,0 +1,2067 @@ +/* + 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) - 0.5f); + int32_t sy = (int32_t)((y * (float)height) - 0.5f); + 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) + { + 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 = "OneLoneCoder.com - Pixel Game Engine - " + 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, "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_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 \ No newline at end of file