Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
670 lines (533 sloc)
23.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 <vector> | |
#include <list> | |
#include <algorithm> | |
#include <utility> | |
#include <string> | |
#include <unordered_set> | |
#include <fstream> | |
// 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<sCell*> 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; | |
} |