From 1f507afa3d71c4c79aa4d4ee71b079f2dc573a83 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 25 Apr 2020 18:30:29 +0100 Subject: [PATCH] Added Dungeon Warping --- Videos/OneLoneCoder_PGE_DungeonWarping.cpp | 431 +++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 Videos/OneLoneCoder_PGE_DungeonWarping.cpp diff --git a/Videos/OneLoneCoder_PGE_DungeonWarping.cpp b/Videos/OneLoneCoder_PGE_DungeonWarping.cpp new file mode 100644 index 0000000..9241612 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_DungeonWarping.cpp @@ -0,0 +1,431 @@ +/* + Dungeon Warping via Orthographic Projections + "For my Mother-In-Law, you will be missed..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2020 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. + + Relevant Video: https://youtu.be/Ql5VZGkL23o + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Community Blog: https://community.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018, 2019, 2020 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +/* + + NOTE! This program requires a tile spritesheet NOT + provided in this github. You only need a few tiles, + see video for details. + +*/ + +class olcDungeon : public olc::PixelGameEngine +{ +public: + olcDungeon() + { + sAppName = "Dungeon Explorer"; + } + + struct Renderable + { + Renderable() {} + + void Load(const std::string& sFile) + { + sprite = new olc::Sprite(sFile); + decal = new olc::Decal(sprite); + } + + ~Renderable() + { + delete decal; + delete sprite; + } + + olc::Sprite* sprite = nullptr; + olc::Decal* decal = nullptr; + }; + + struct vec3d + { + float x, y, z; + }; + + struct sQuad + { + vec3d points[4]; + olc::vf2d tile; + }; + + struct sCell + { + bool wall = false; + olc::vi2d id[6]{ }; + }; + + class World + { + public: + World() + { + + } + + void Create(int w, int h) + { + size = { w, h }; + vCells.resize(w * h); + } + + sCell& GetCell(const olc::vi2d& v) + { + if (v.x >= 0 && v.x < size.x && v.y >= 0 && v.y < size.y) + return vCells[v.y * size.x + v.x]; + else + return NullCell; + } + + public: + olc::vi2d size; + + private: + std::vector vCells; + sCell NullCell; + }; + + World world; + Renderable rendSelect; + Renderable rendAllWalls; + + olc::vf2d vCameraPos = { 0.0f, 0.0f }; + float fCameraAngle = 0.0f; + float fCameraAngleTarget = fCameraAngle; + float fCameraPitch = 5.5f; + float fCameraZoom = 16.0f; + + bool bVisible[6]; + + olc::vi2d vCursor = { 0, 0 }; + olc::vi2d vTileCursor = { 0,0 }; + olc::vi2d vTileSize = { 32, 32 }; + + enum Face + { + Floor = 0, + North = 1, + East = 2, + South = 3, + West = 4, + Top = 5 + }; + +public: + bool OnUserCreate() override + { + rendSelect.Load("./gfx/dng_select.png"); + rendAllWalls.Load("./gfx/oldDungeon.png"); + + world.Create(64, 64); + + for (int y=0; y CreateCube(const olc::vi2d& vCell, const float fAngle, const float fPitch, const float fScale, const vec3d& vCamera) + { + // Unit Cube + std::array unitCube, rotCube, worldCube, projCube; + unitCube[0] = { 0.0f, 0.0f, 0.0f }; + unitCube[1] = { fScale, 0.0f, 0.0f }; + unitCube[2] = { fScale, -fScale, 0.0f }; + unitCube[3] = { 0.0f, -fScale, 0.0f }; + unitCube[4] = { 0.0f, 0.0f, fScale }; + unitCube[5] = { fScale, 0.0f, fScale }; + unitCube[6] = { fScale, -fScale, fScale }; + unitCube[7] = { 0.0f, -fScale, fScale }; + + // Translate Cube in X-Z Plane + for (int i = 0; i < 8; i++) + { + unitCube[i].x += (vCell.x * fScale - vCamera.x); + unitCube[i].y += -vCamera.y; + unitCube[i].z += (vCell.y * fScale - vCamera.z); + } + + // Rotate Cube in Y-Axis around origin + float s = sin(fAngle); + float c = cos(fAngle); + for (int i = 0; i < 8; i++) + { + rotCube[i].x = unitCube[i].x * c + unitCube[i].z * s; + rotCube[i].y = unitCube[i].y; + rotCube[i].z = unitCube[i].x * -s + unitCube[i].z * c; + } + + // Rotate Cube in X-Axis around origin (tilt slighly overhead) + s = sin(fPitch); + c = cos(fPitch); + for (int i = 0; i < 8; i++) + { + worldCube[i].x = rotCube[i].x; + worldCube[i].y = rotCube[i].y * c - rotCube[i].z * s; + worldCube[i].z = rotCube[i].y * s + rotCube[i].z * c; + } + + // Project Cube Orthographically - Unit Cube Viewport + //float fLeft = -ScreenWidth() * 0.5f; + //float fRight = ScreenWidth() * 0.5f; + //float fTop = ScreenHeight() * 0.5f; + //float fBottom = -ScreenHeight() * 0.5f; + //float fNear = 0.1f; + //float fFar = 100.0f;*/ + //for (int i = 0; i < 8; i++) + //{ + // projCube[i].x = (2.0f / (fRight - fLeft)) * worldCube[i].x - ((fRight + fLeft) / (fRight - fLeft)); + // projCube[i].y = (2.0f / (fTop - fBottom)) * worldCube[i].y - ((fTop + fBottom) / (fTop - fBottom)); + // projCube[i].z = (2.0f / (fFar - fNear)) * worldCube[i].z - ((fFar + fNear) / (fFar - fNear)); + // projCube[i].x *= -fRight; + // projCube[i].y *= -fTop; + // projCube[i].x += fRight; + // projCube[i].y += fTop; + //} + + // Project Cube Orthographically - Full Screen Centered + for (int i = 0; i < 8; i++) + { + projCube[i].x = worldCube[i].x + ScreenWidth() * 0.5f; + projCube[i].y = worldCube[i].y + ScreenHeight() * 0.5f; + projCube[i].z = worldCube[i].z; + } + + return projCube; + } + + + + void CalculateVisibleFaces(std::array& cube) + { + auto CheckNormal = [&](int v1, int v2, int v3) + { + olc::vf2d a = { cube[v1].x, cube[v1].y }; + olc::vf2d b = { cube[v2].x, cube[v2].y }; + olc::vf2d c = { cube[v3].x, cube[v3].y }; + return (b - a).cross(c - a) > 0; + }; + + bVisible[Face::Floor] = CheckNormal(4, 0, 1); + bVisible[Face::South] = CheckNormal(3, 0, 1); + bVisible[Face::North] = CheckNormal(6, 5, 4); + bVisible[Face::East] = CheckNormal(7, 4, 0); + bVisible[Face::West] = CheckNormal(2, 1, 5); + bVisible[Face::Top] = CheckNormal(7, 3, 2); + } + + void GetFaceQuads(const olc::vi2d& vCell, const float fAngle, const float fPitch, const float fScale, const vec3d& vCamera, std::vector &render) + { + std::array projCube = CreateCube(vCell, fAngle, fPitch, fScale, vCamera); + + auto& cell = world.GetCell(vCell); + + auto MakeFace = [&](int v1, int v2, int v3, int v4, Face f) + { + render.push_back({ projCube[v1], projCube[v2], projCube[v3], projCube[v4], cell.id[f] }); + }; + + if (!cell.wall) + { + if(bVisible[Face::Floor]) MakeFace(4, 0, 1, 5, Face::Floor); + } + else + { + if (bVisible[Face::South]) MakeFace(3, 0, 1, 2, Face::South); + if (bVisible[Face::North]) MakeFace(6, 5, 4, 7, Face::North); + if (bVisible[Face::East]) MakeFace(7, 4, 0, 3, Face::East); + if (bVisible[Face::West]) MakeFace(2, 1, 5, 6, Face::West); + if (bVisible[Face::Top]) MakeFace(7, 3, 2, 6, Face::Top); + } + } + + + bool OnUserUpdate(float fElapsedTime) override + { + // Grab mouse for convenience + olc::vi2d vMouse = { GetMouseX(), GetMouseY() }; + + // Edit mode - Selection from tile sprite sheet + if (GetKey(olc::Key::TAB).bHeld) + { + DrawSprite({ 0, 0 }, rendAllWalls.sprite); + DrawRect(vTileCursor * vTileSize, vTileSize); + if (GetMouse(0).bPressed) vTileCursor = vMouse / vTileSize; + return true; + } + + // WS keys to tilt camera + if (GetKey(olc::Key::W).bHeld) fCameraPitch += 1.0f * fElapsedTime; + if (GetKey(olc::Key::S).bHeld) fCameraPitch -= 1.0f * fElapsedTime; + + // DA Keys to manually rotate camera + if (GetKey(olc::Key::D).bHeld) fCameraAngleTarget += 1.0f * fElapsedTime; + if (GetKey(olc::Key::A).bHeld) fCameraAngleTarget -= 1.0f * fElapsedTime; + + // QZ Keys to zoom in or out + if (GetKey(olc::Key::Q).bHeld) fCameraZoom += 5.0f * fElapsedTime; + if (GetKey(olc::Key::Z).bHeld) fCameraZoom -= 5.0f * fElapsedTime; + + // Numpad keys used to rotate camera to fixed angles + if (GetKey(olc::Key::NP2).bPressed) fCameraAngleTarget = 3.14159f * 0.0f; + if (GetKey(olc::Key::NP1).bPressed) fCameraAngleTarget = 3.14159f * 0.25f; + if (GetKey(olc::Key::NP4).bPressed) fCameraAngleTarget = 3.14159f * 0.5f; + if (GetKey(olc::Key::NP7).bPressed) fCameraAngleTarget = 3.14159f * 0.75f; + if (GetKey(olc::Key::NP8).bPressed) fCameraAngleTarget = 3.14159f * 1.0f; + if (GetKey(olc::Key::NP9).bPressed) fCameraAngleTarget = 3.14159f * 1.25f; + if (GetKey(olc::Key::NP6).bPressed) fCameraAngleTarget = 3.14159f * 1.5f; + if (GetKey(olc::Key::NP3).bPressed) fCameraAngleTarget = 3.14159f * 1.75f; + + // Numeric keys apply selected tile to specific face + if (GetKey(olc::Key::K1).bPressed) world.GetCell(vCursor).id[Face::North] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K2).bPressed) world.GetCell(vCursor).id[Face::East] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K3).bPressed) world.GetCell(vCursor).id[Face::South] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K4).bPressed) world.GetCell(vCursor).id[Face::West] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K5).bPressed) world.GetCell(vCursor).id[Face::Floor] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K6).bPressed) world.GetCell(vCursor).id[Face::Top] = vTileCursor * vTileSize; + + // Smooth camera + fCameraAngle += (fCameraAngleTarget - fCameraAngle) * 10.0f * fElapsedTime; + + // Arrow keys to move the selection cursor around map (boundary checked) + if (GetKey(olc::Key::LEFT).bPressed) vCursor.x--; + if (GetKey(olc::Key::RIGHT).bPressed) vCursor.x++; + if (GetKey(olc::Key::UP).bPressed) vCursor.y--; + if (GetKey(olc::Key::DOWN).bPressed) vCursor.y++; + if (vCursor.x < 0) vCursor.x = 0; + if (vCursor.y < 0) vCursor.y = 0; + if (vCursor.x >= world.size.x) vCursor.x = world.size.x - 1; + if (vCursor.y >= world.size.y) vCursor.y = world.size.y - 1; + + // Place block with space + if (GetKey(olc::Key::SPACE).bPressed) + { + world.GetCell(vCursor).wall = !world.GetCell(vCursor).wall; + } + + // Position camera in world + vCameraPos = { vCursor.x + 0.5f, vCursor.y + 0.5f }; + vCameraPos *= fCameraZoom; + + // Rendering + + // 1) Create dummy cube to extract visible face information + // Cull faces that cannot be seen + std::array cullCube = CreateCube({ 0, 0 }, fCameraAngle, fCameraPitch, fCameraZoom, { vCameraPos.x, 0.0f, vCameraPos.y }); + CalculateVisibleFaces(cullCube); + + // 2) Get all visible sides of all visible "tile cubes" + std::vector vQuads; + for(int y = 0; y