Removed stuff i dont care about

This commit is contained in:
Ben
2019-02-09 22:11:13 +00:00
parent 7b3840c000
commit 30438a82ea
21 changed files with 0 additions and 6026 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -1,670 +0,0 @@
/*
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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,500 +0,0 @@
/*
OneLoneCoder.com - Programming Balls! #2 Circle Vs Edge Collisions
"...totally overkill for pong..." - @Javidx9
Background
~~~~~~~~~~
Collision detection engines can get quite complicated. This program shows the interactions
between circular objects of different sizes and masses. Use Left mouse button to select
and drag a ball to examin static collisions, and use Right mouse button to apply velocity
to the balls as if using a pool/snooker/billiards cue.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
Part #1 https://youtu.be/LPzyNOHY3A4
Part #2 https://youtu.be/ebq7L2Wtbl4
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
struct sBall
{
float px, py;
float vx, vy;
float ax, ay;
float ox, oy;
float radius;
float mass;
float friction;
int score;
int id;
float fSimTimeRemaining;
olc::Pixel col;
};
struct sLineSegment
{
float sx, sy;
float ex, ey;
float radius;
};
class CirclePhysics : public olc::PixelGameEngine
{
public:
CirclePhysics()
{
sAppName = "Circles V Edges";
}
private:
vector<sBall> vecBalls;
vector<sLineSegment> vecLines;
vector<pair<float, float>> modelCircle;
sBall* pSelectedBall = nullptr;
olc::Sprite *spriteBalls = nullptr;
sLineSegment* pSelectedLine = nullptr;
bool bSelectedLineStart = false;
void AddBall(float x, float y, float r = 5.0f, int s = 0)
{
sBall b;
b.px = x; b.py = y;
b.vx = 0; b.vy = 0;
b.ax = 0; b.ay = 0;
b.ox = 0; b.oy = 0;
b.radius = r;
b.mass = r * 10.0f;
b.friction = 0.0f;
b.score = s;
b.fSimTimeRemaining = 0.0f;
b.id = vecBalls.size();
b.col = olc::Pixel(rand() % 200 + 55, rand() % 200 + 55, rand() % 200 + 55);
vecBalls.emplace_back(b);
}
olc::Sprite spr;
public:
bool OnUserCreate()
{
float fBallRadius = 4.0f;
for (int i = 0; i <100; i++)
AddBall(((float)rand()/(float)RAND_MAX) * ScreenWidth(), ((float)rand() / (float)RAND_MAX) * ScreenHeight(), fBallRadius);
AddBall(28.0f, 33.0, fBallRadius * 3);
AddBall(28.0f, 35.0, fBallRadius * 2);
float fLineRadius = 4.0f;
vecLines.push_back({ 12.0f, 4.0f, 64.0f, 4.0f, fLineRadius });
vecLines.push_back({ 76.0f, 4.0f, 132.0f, 4.0f, fLineRadius });
vecLines.push_back({ 12.0f, 68.0f, 64.0f, 68.0f, fLineRadius });
vecLines.push_back({ 76.0f, 68.0f, 132.0f, 68.0f, fLineRadius });
vecLines.push_back({ 4.0f, 12.0f, 4.0f, 60.0f, fLineRadius });
vecLines.push_back({ 140.0f, 12.0f, 140.0f, 60.0f, fLineRadius });
return true;
}
bool OnUserUpdate(float fElapsedTime)
{
auto DoCirclesOverlap = [](float x1, float y1, float r1, float x2, float y2, float r2)
{
return fabs((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= ((r1 + r2) * (r1 + r2));
};
auto IsPointInCircle = [](float x1, float y1, float r1, float px, float py)
{
return fabs((x1 - px)*(x1 - px) + (y1 - py)*(y1 - py)) < (r1 * r1);
};
if (GetMouse(0).bPressed)
{
// Check for selected ball
pSelectedBall = nullptr;
for (auto &ball : vecBalls)
{
if (IsPointInCircle(ball.px, ball.py, ball.radius, GetMouseX(), GetMouseY()))
{
pSelectedBall = &ball;
break;
}
}
// Check for selected line segment end
pSelectedLine = nullptr;
for (auto &line : vecLines)
{
if (IsPointInCircle(line.sx, line.sy, line.radius, GetMouseX(), GetMouseY()))
{
pSelectedLine = &line;
bSelectedLineStart = true;
break;
}
if (IsPointInCircle(line.ex, line.ey, line.radius, GetMouseX(), GetMouseY()))
{
pSelectedLine = &line;
bSelectedLineStart = false;
break;
}
}
}
if (GetMouse(0).bHeld)
{
if (pSelectedLine != nullptr)
{
if (bSelectedLineStart)
{
pSelectedLine->sx = GetMouseX();
pSelectedLine->sy = GetMouseY();
}
else
{
pSelectedLine->ex = GetMouseX();
pSelectedLine->ey = GetMouseY();
}
}
}
if (GetMouse(0).bReleased)
{
if (pSelectedBall != nullptr)
{
// Apply velocity
pSelectedBall->vx = 5.0f * ((pSelectedBall->px) - GetMouseX());
pSelectedBall->vy = 5.0f * ((pSelectedBall->py) - GetMouseY());
}
pSelectedBall = nullptr;
pSelectedLine = nullptr;
}
if (GetMouse(1).bHeld)
{
for (auto &ball : vecBalls)
{
ball.vx += (GetMouseX() - ball.px) * 0.01f;
ball.vy += (GetMouseY() - ball.py) * 0.01f;
}
}
vector<pair<sBall*, sBall*>> vecCollidingPairs;
vector<sBall*> vecFakeBalls;
// Threshold indicating stability of object
float fStable = 0.005f;
// Multiple simulation updates with small time steps permit more accurate physics
// and realistic results at the expense of CPU time of course
int nSimulationUpdates = 4;
// Multiple collision trees require more steps to resolve. Normally we would
// continue simulation until the object has no simulation time left for this
// epoch, however this is risky as the system may never find stability, so we
// can clamp it here
int nMaxSimulationSteps = 15;
// Break up the frame elapsed time into smaller deltas for each simulation update
float fSimElapsedTime = fElapsedTime / (float)nSimulationUpdates;
// Main simulation loop
for (int i = 0; i < nSimulationUpdates; i++)
{
// Set all balls time to maximum for this epoch
for (auto &ball : vecBalls)
ball.fSimTimeRemaining = fSimElapsedTime;
// Erode simulation time on a per objec tbasis, depending upon what happens
// to it during its journey through this epoch
for (int j = 0; j < nMaxSimulationSteps; j++)
{
// Update Ball Positions
for (auto &ball : vecBalls)
{
if (ball.fSimTimeRemaining > 0.0f)
{
ball.ox = ball.px; // Store original position this epoch
ball.oy = ball.py;
ball.ax = -ball.vx * 0.8f; // Apply drag and gravity
ball.ay = -ball.vy * 0.8f + 100.0f;
ball.vx += ball.ax * ball.fSimTimeRemaining; // Update Velocity
ball.vy += ball.ay * ball.fSimTimeRemaining;
ball.px += ball.vx * ball.fSimTimeRemaining; // Update position
ball.py += ball.vy * ball.fSimTimeRemaining;
// Crudely wrap balls to screen - note this cause issues when collisions occur on screen boundaries
if (ball.px < 0) ball.px += (float)ScreenWidth();
if (ball.px >= ScreenWidth()) ball.px -= (float)ScreenWidth();
if (ball.py < 0) ball.py += (float)ScreenHeight();
if (ball.py >= ScreenHeight()) ball.py -= (float)ScreenHeight();
// Stop ball when velocity is neglible
if (fabs(ball.vx*ball.vx + ball.vy*ball.vy) < fStable)
{
ball.vx = 0;
ball.vy = 0;
}
}
}
// Work out static collisions with walls and displace balls so no overlaps
for (auto &ball : vecBalls)
{
float fDeltaTime = ball.fSimTimeRemaining;
// Against Edges
for (auto &edge : vecLines)
{
// Check that line formed by velocity vector, intersects with line segment
float fLineX1 = edge.ex - edge.sx;
float fLineY1 = edge.ey - edge.sy;
float fLineX2 = ball.px - edge.sx;
float fLineY2 = ball.py - edge.sy;
float fEdgeLength = fLineX1 * fLineX1 + fLineY1 * fLineY1;
// This is nifty - It uses the DP of the line segment vs the line to the object, to work out
// how much of the segment is in the "shadow" of the object vector. The min and max clamp
// this to lie between 0 and the line segment length, which is then normalised. We can
// use this to calculate the closest point on the line segment
float t = std::max(0.0f, std::min(fEdgeLength, (fLineX1 * fLineX2 + fLineY1 * fLineY2))) / fEdgeLength;
// Which we do here
float fClosestPointX = edge.sx + t * fLineX1;
float fClosestPointY = edge.sy + t * fLineY1;
// And once we know the closest point, we can check if the ball has collided with the segment in the
// same way we check if two balls have collided
float fDistance = sqrtf((ball.px - fClosestPointX)*(ball.px - fClosestPointX) + (ball.py - fClosestPointY)*(ball.py - fClosestPointY));
if (fDistance <= (ball.radius + edge.radius))
{
// Collision has occurred - treat collision point as a ball that cannot move. To make this
// compatible with the dynamic resolution code below, we add a fake ball with an infinite mass
// so it behaves like a solid object when the momentum calculations are performed
sBall *fakeball = new sBall();
fakeball->radius = edge.radius;
fakeball->mass = ball.mass * 0.8f;
fakeball->px = fClosestPointX;
fakeball->py = fClosestPointY;
fakeball->vx = -ball.vx; // We will use these later to allow the lines to impart energy into ball
fakeball->vy = -ball.vy; // if the lines are moving, i.e. like pinball flippers
// Store Fake Ball
vecFakeBalls.push_back(fakeball);
// Add collision to vector of collisions for dynamic resolution
vecCollidingPairs.push_back({ &ball, fakeball });
// Calculate displacement required
float fOverlap = 1.0f * (fDistance - ball.radius - fakeball->radius);
// Displace Current Ball away from collision
ball.px -= fOverlap * (ball.px - fakeball->px) / fDistance;
ball.py -= fOverlap * (ball.py - fakeball->py) / fDistance;
}
}
// Against other balls
for (auto &target : vecBalls)
{
if (ball.id != target.id) // Do not check against self
{
if (DoCirclesOverlap(ball.px, ball.py, ball.radius, target.px, target.py, target.radius))
{
// Collision has occured
vecCollidingPairs.push_back({ &ball, &target });
// Distance between ball centers
float fDistance = sqrtf((ball.px - target.px)*(ball.px - target.px) + (ball.py - target.py)*(ball.py - target.py));
// Calculate displacement required
float fOverlap = 0.5f * (fDistance - ball.radius - target.radius);
// Displace Current Ball away from collision
ball.px -= fOverlap * (ball.px - target.px) / fDistance;
ball.py -= fOverlap * (ball.py - target.py) / fDistance;
// Displace Target Ball away from collision - Note, this should affect the timing of the target ball
// and it does, but this is absorbed by the target ball calculating its own time delta later on
target.px += fOverlap * (ball.px - target.px) / fDistance;
target.py += fOverlap * (ball.py - target.py) / fDistance;
}
}
}
// Time displacement - we knew the velocity of the ball, so we can estimate the distance it should have covered
// however due to collisions it could not do the full distance, so we look at the actual distance to the collision
// point and calculate how much time that journey would have taken using the speed of the object. Therefore
// we can now work out how much time remains in that timestep.
float fIntendedSpeed = sqrtf(ball.vx * ball.vx + ball.vy * ball.vy);
float fIntendedDistance = fIntendedSpeed * ball.fSimTimeRemaining;
float fActualDistance = sqrtf((ball.px - ball.ox)*(ball.px - ball.ox) + (ball.py - ball.oy)*(ball.py - ball.oy));
float fActualTime = fActualDistance / fIntendedSpeed;
// After static resolution, there may be some time still left for this epoch, so allow simulation to continue
ball.fSimTimeRemaining = ball.fSimTimeRemaining - fActualTime;
}
// Now work out dynamic collisions
float fEfficiency = 1.00f;
for (auto c : vecCollidingPairs)
{
sBall *b1 = c.first, *b2 = c.second;
// Distance between balls
float fDistance = sqrtf((b1->px - b2->px)*(b1->px - b2->px) + (b1->py - b2->py)*(b1->py - b2->py));
// Normal
float nx = (b2->px - b1->px) / fDistance;
float ny = (b2->py - b1->py) / fDistance;
// Tangent
float tx = -ny;
float ty = nx;
// Dot Product Tangent
float dpTan1 = b1->vx * tx + b1->vy * ty;
float dpTan2 = b2->vx * tx + b2->vy * ty;
// Dot Product Normal
float dpNorm1 = b1->vx * nx + b1->vy * ny;
float dpNorm2 = b2->vx * nx + b2->vy * ny;
// Conservation of momentum in 1D
float m1 = fEfficiency * (dpNorm1 * (b1->mass - b2->mass) + 2.0f * b2->mass * dpNorm2) / (b1->mass + b2->mass);
float m2 = fEfficiency * (dpNorm2 * (b2->mass - b1->mass) + 2.0f * b1->mass * dpNorm1) / (b1->mass + b2->mass);
// Update ball velocities
b1->vx = tx * dpTan1 + nx * m1;
b1->vy = ty * dpTan1 + ny * m1;
b2->vx = tx * dpTan2 + nx * m2;
b2->vy = ty * dpTan2 + ny * m2;
}
// Remove collisions
vecCollidingPairs.clear();
// Remove fake balls
for (auto &b : vecFakeBalls) delete b;
vecFakeBalls.clear();
}
}
// Clear Screen
FillRect(0, 0, ScreenWidth(), ScreenHeight(), olc::Pixel(0, 0, 0));
// Draw Lines
for (auto line : vecLines)
{
FillCircle(line.sx, line.sy, line.radius, olc::Pixel(255,255,255));
FillCircle(line.ex, line.ey, line.radius, olc::Pixel(128, 128, 128));
float nx = -(line.ey - line.sy);
float ny = (line.ex - line.sx);
float d = sqrt(nx*nx + ny * ny);
nx /= d;
ny /= d;
DrawLine((line.sx + nx * line.radius), (line.sy + ny * line.radius), (line.ex + nx * line.radius), (line.ey + ny * line.radius), olc::Pixel(255, 255, 255));
DrawLine((line.sx - nx * line.radius), (line.sy - ny * line.radius), (line.ex - nx * line.radius), (line.ey - ny * line.radius), olc::Pixel(255, 255, 255));
}
// Draw Balls
for (auto ball : vecBalls)
{
FillCircle(ball.px, ball.py, ball.radius, ball.col);
}
// Draw Cue
if (pSelectedBall != nullptr)
DrawLine(pSelectedBall->px, pSelectedBall->py, GetMouseX(), GetMouseY(), olc::Pixel(0, 0, 255));
return true;
}
};
int main()
{
CirclePhysics game;
if (game.Construct(320, 240, 4, 4))
game.Start();
else
wcout << L"Could not construct console" << endl;
return 0;
};

View File

@@ -1,189 +0,0 @@
/*
OneLoneCoder_PGE_ExtensionTestGFX2D.cpp
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
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
// Include the olcPixelGameEngine
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
// To use an extension, just include it
#define OLC_PGE_GRAPHICS2D
#include "olcPGEX_Graphics2D.h"
class TestExtension : public olc::PixelGameEngine
{
public:
TestExtension()
{
sAppName = "Testing Graphics2D";
}
public:
bool OnUserCreate() override
{
for (int i = 0; i < 16; i++)
listEvents.push_back("");
spr = new olc::Sprite("new_piskel.png");
return true;
}
std::list<std::string> listEvents;
float fTotalTime = 0.0f;
olc::Sprite *spr;
bool OnUserUpdate(float fElapsedTime) override
{
// Clear Screen
SetPixelMode(olc::Pixel::NORMAL);
Clear(olc::BLUE);
// Draw Primitives
DrawCircle(32, 32, 30); // Circle
DrawCircle(96, 32, 30); // Circle
float mx = (float)GetMouseX();
float my = (float)GetMouseY();
float px1 = mx - 32, px2 = mx - 96;
float py1 = my - 32, py2 = my - 32;
float pr1 = 1.0f / sqrtf(px1*px1 + py1*py1);
float pr2 = 1.0f / sqrtf(px2*px2 + py2*py2);
px1 = 22.0f * (px1 * pr1) + 32.0f;
py1 = 22.0f * (py1 * pr1) + 32.0f;
px2 = 22.0f * (px2 * pr2) + 96.0f;
py2 = 22.0f * (py2 * pr2) + 32.0f;
FillCircle((int32_t)px1, (int32_t)py1, 8, olc::CYAN);
FillCircle((int32_t)px2, (int32_t)py2, 8, olc::CYAN);
DrawLine(10, 70, 54, 70); // Lines
DrawLine(54, 70, 70, 54);
DrawRect(10, 80, 54, 30);
FillRect(10, 80, 54, 30);
// Multiline Text
std::string mpos = "Your Mouse Position is:\nX=" + std::to_string(mx) + "\nY=" + std::to_string(my);
DrawString(10, 130, mpos);
auto AddEvent = [&](std::string s)
{
listEvents.push_back(s);
listEvents.pop_front();
};
if (GetMouse(0).bPressed) AddEvent("Mouse Button 0 Down");
if (GetMouse(0).bReleased) AddEvent("Mouse Button 0 Up");
if (GetMouse(1).bPressed) AddEvent("Mouse Button 1 Down");
if (GetMouse(1).bReleased) AddEvent("Mouse Button 1 Up");
if (GetMouse(2).bPressed) AddEvent("Mouse Button 2 Down");
if (GetMouse(2).bReleased) AddEvent("Mouse Button 2 Up");
// Draw Event Log
int nLog = 0;
for (auto &s : listEvents)
{
DrawString(200, nLog * 8 + 20, s, olc::Pixel(nLog * 16, nLog * 16, nLog * 16));
nLog++;
}
std::string notes = "CDEFGAB";
// Test Text scaling and colours
DrawString(0, 360, "Text Scale = 1", olc::WHITE, 1);
DrawString(0, 368, "Text Scale = 2", olc::BLUE, 2);
DrawString(0, 384, "Text Scale = 3", olc::RED, 3);
DrawString(0, 408, "Text Scale = 4", olc::YELLOW, 4);
DrawString(0, 440, "Text Scale = 5", olc::GREEN, 5);
fTotalTime += fElapsedTime;
float fAngle = fTotalTime;
// Draw Sprite using extension, first create a transformation stack
olc::GFX2D::Transform2D t1;
// Traslate sprite so center of image is at 0,0
t1.Translate(-250, -35);
// Scale the sprite
t1.Scale(1 * sinf(fAngle) + 1, 1 * sinf(fAngle) + 1);
// Rotate it
t1.Rotate(fAngle*2.0f);
// Translate to 0,100
t1.Translate(0, 100);
// Rotate different speed
t1.Rotate(fAngle / 3);
// Translate to centre of screen
t1.Translate(320, 240);
SetPixelMode(olc::Pixel::ALPHA);
// Use extension to draw sprite with transform applied
olc::GFX2D::DrawSprite(spr, t1);
DrawSprite((int32_t)mx, (int32_t)my, spr, 4);
return true;
}
};
int main()
{
TestExtension demo;
if (demo.Construct(640, 480, 2, 2))
demo.Start();
return 0;
}

View File

@@ -1,426 +0,0 @@
/*
OneLoneCoder.com - Path Finding #2 - Wave Propagation & Potential Fields
"...never get lost again, so long as you know where you are" - @Javidx9
Background
~~~~~~~~~~
A nice follow up alternative to the A* Algorithm. Wave propagation is less
applicable to multiple objects with multiple destinations, but fantatsic
for multiple objects all reaching the same destination.
WARNING! This code is NOT OPTIMAL!! It is however very robust. There
are many ways to optimise this further.
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
Patreon: https://www.patreon/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
Part #1 https://youtu.be/icZj67PTFhc
Part #2 https://youtu.be/0ihciMKlcP8
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <vector>
#include <list>
#include <algorithm>
#include <utility>
// Override base class with your custom functionality
class PathFinding_FlowFields : public olc::PixelGameEngine
{
public:
PathFinding_FlowFields()
{
sAppName = "PathFinding - Flow Fields";
}
private:
int nMapWidth;
int nMapHeight;
int nCellSize;
int nBorderWidth;
bool *bObstacleMap;
int *nFlowFieldZ;
float *fFlowFieldY;
float *fFlowFieldX;
int nStartX;
int nStartY;
int nEndX;
int nEndY;
int nWave = 1;
public:
bool OnUserCreate() override
{
nBorderWidth = 4;
nCellSize = 32;
nMapWidth = ScreenWidth() / nCellSize;
nMapHeight = ScreenHeight() / nCellSize;
bObstacleMap = new bool[nMapWidth * nMapHeight]{ false };
nFlowFieldZ = new int[nMapWidth * nMapHeight]{ 0 };
fFlowFieldX = new float[nMapWidth * nMapHeight]{ 0 };
fFlowFieldY = new float[nMapWidth * nMapHeight]{ 0 };
nStartX = 3;
nStartY = 7;
nEndX = 12;
nEndY = 7;
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Little convenience lambda 2D -> 1D
auto p = [&](int x, int y) { return y * nMapWidth + x; };
// User Input
int nSelectedCellX = GetMouseX() / nCellSize;
int nSelectedCellY = GetMouseY() / nCellSize;
if (GetMouse(0).bReleased)
{
// Toggle Obstacle if mouse left clicked
bObstacleMap[p(nSelectedCellX, nSelectedCellY)] =
!bObstacleMap[p(nSelectedCellX, nSelectedCellY)];
}
if (GetMouse(1).bReleased)
{
nStartX = nSelectedCellX;
nStartY = nSelectedCellY;
}
if (GetKey(olc::Key::Q).bReleased)
{
nWave++;
}
if (GetKey(olc::Key::A).bReleased)
{
nWave--;
if (nWave == 0)
nWave = 1;
}
// 1) Prepare flow field, add a boundary, and add obstacles
// by setting the flow Field Height (Z) to -1
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
// Set border or obstacles
if (x == 0 || y == 0 || x == (nMapWidth - 1) || y == (nMapHeight - 1) || bObstacleMap[p(x, y)])
{
nFlowFieldZ[p(x, y)] = -1;
}
else
{
nFlowFieldZ[p(x, y)] = 0;
}
}
}
// 2) Propagate a wave (i.e. flood-fill) from target location. Here I use
// a tuple, of {x, y, distance} - though you could use a struct or
// similar.
std::list<std::tuple<int, int, int>> nodes;
// Add the first discovered node - the target location, with a distance of 1
nodes.push_back({ nEndX, nEndY, 1 });
while (!nodes.empty())
{
// Each iteration through the discovered nodes may create newly discovered
// nodes, so I maintain a second list. It's important not to contaminate
// the list being iterated through.
std::list<std::tuple<int, int, int>> new_nodes;
// Now iterate through each discovered node. If it has neighbouring nodes
// that are empty space and undiscovered, add those locations to the
// new nodes list
for (auto &n : nodes)
{
int x = std::get<0>(n); // Map X-Coordinate
int y = std::get<1>(n); // Map Y-Coordinate
int d = std::get<2>(n); // Distance From Target Location
// Set distance count for this node. NOte that when we add nodes we add 1
// to this distance. This emulates propagating a wave across the map, where
// the front of that wave increments each iteration. In this way, we can
// propagate distance information 'away from target location'
nFlowFieldZ[p(x, y)] = d;
// Add neigbour nodes if unmarked, i.e their "height" is 0. Any discovered
// node or obstacle will be non-zero
// Check East
if ((x + 1) < nMapWidth && nFlowFieldZ[p(x + 1, y)] == 0)
new_nodes.push_back({ x + 1, y, d + 1 });
// Check West
if ((x - 1) >= 0 && nFlowFieldZ[p(x - 1, y)] == 0)
new_nodes.push_back({ x - 1, y, d + 1 });
// Check South
if ((y + 1) < nMapHeight && nFlowFieldZ[p(x, y + 1)] == 0)
new_nodes.push_back({ x, y + 1, d + 1 });
// Check North
if ((y - 1) >= 0 && nFlowFieldZ[p(x, y - 1)] == 0)
new_nodes.push_back({ x, y - 1, d + 1 });
}
// We will now have potentially multiple nodes for a single location. This means our
// algorithm will never complete! So we must remove duplicates form our new node list.
// Im doing this with some clever code - but it is not performant(!) - it is merely
// convenient. I'd suggest doing away with overhead structures like linked lists and sorts
// if you are aiming for fastest path finding.
// Sort the nodes - This will stack up nodes that are similar: A, B, B, B, B, C, D, D, E, F, F
new_nodes.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
// In this instance I dont care how the values are sorted, so long as nodes that
// represent the same location are adjacent in the list. I can use the p() lambda
// to generate a unique 1D value for a 2D coordinate, so I'll sort by that.
return p(std::get<0>(n1), std::get<1>(n1)) < p(std::get<0>(n2), std::get<1>(n2));
});
// Use "unique" function to remove adjacent duplicates : A, B, -, -, -, C, D, -, E, F -
// and also erase them : A, B, C, D, E, F
new_nodes.unique([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return p(std::get<0>(n1), std::get<1>(n1)) == p(std::get<0>(n2), std::get<1>(n2));
});
// We've now processed all the discoverd nodes, so clear the list, and add the newly
// discovered nodes for processing on the next iteration
nodes.clear();
nodes.insert(nodes.begin(), new_nodes.begin(), new_nodes.end());
// When there are no more newly discovered nodes, we have "flood filled" the entire
// map. The propagation phase of the algorithm is complete
}
// 3) Create Path. Starting a start location, create a path of nodes until you reach target
// location. At each node find the neighbour with the lowest "distance" score.
std::list<std::pair<int, int>> path;
path.push_back({ nStartX, nStartY });
int nLocX = nStartX;
int nLocY = nStartY;
bool bNoPath = false;
while (!(nLocX == nEndX && nLocY == nEndY) && !bNoPath)
{
std::list<std::tuple<int, int, int>> listNeighbours;
// 4-Way Connectivity
if ((nLocY - 1) >= 0 && nFlowFieldZ[p(nLocX, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY - 1, nFlowFieldZ[p(nLocX, nLocY - 1)] });
if ((nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY, nFlowFieldZ[p(nLocX + 1, nLocY)] });
if ((nLocY + 1) < nMapHeight && nFlowFieldZ[p(nLocX, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY + 1, nFlowFieldZ[p(nLocX, nLocY + 1)] });
if ((nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY, nFlowFieldZ[p(nLocX - 1, nLocY)] });
// 8-Way Connectivity
if ((nLocY - 1) >= 0 && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY - 1, nFlowFieldZ[p(nLocX - 1, nLocY - 1)] });
if ((nLocY - 1) >= 0 && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY - 1, nFlowFieldZ[p(nLocX + 1, nLocY - 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY + 1, nFlowFieldZ[p(nLocX - 1, nLocY + 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY + 1, nFlowFieldZ[p(nLocX + 1, nLocY + 1)] });
// Sprt neigbours based on height, so lowest neighbour is at front
// of list
listNeighbours.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return std::get<2>(n1) < std::get<2>(n2); // Compare distances
});
if (listNeighbours.empty()) // Neighbour is invalid or no possible path
bNoPath = true;
else
{
nLocX = std::get<0>(listNeighbours.front());
nLocY = std::get<1>(listNeighbours.front());
path.push_back({ nLocX, nLocY });
}
}
// 4) Create Flow "Field"
for (int x = 1; x < nMapWidth - 1; x++)
{
for (int y = 1; y < nMapHeight - 1; y++)
{
float vx = 0.0f;
float vy = 0.0f;
vy -= (float)((nFlowFieldZ[p(x, y + 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y + 1)]) - nFlowFieldZ[p(x, y)]);
vx -= (float)((nFlowFieldZ[p(x + 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x + 1, y)]) - nFlowFieldZ[p(x, y)]);
vy += (float)((nFlowFieldZ[p(x, y - 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y - 1)]) - nFlowFieldZ[p(x, y)]);
vx += (float)((nFlowFieldZ[p(x - 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x - 1, y)]) - nFlowFieldZ[p(x, y)]);
float r = 1.0f / sqrtf(vx*vx + vy * vy);
fFlowFieldX[p(x, y)] = vx * r;
fFlowFieldY[p(x, y)] = vy * r;
}
}
// Draw Map
Clear(olc::BLACK);
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
olc::Pixel colour = olc::BLUE;
if (bObstacleMap[p(x, y)])
colour = olc::GREY;
if (nWave == nFlowFieldZ[p(x, y)])
colour = olc::DARK_CYAN;
if (x == nStartX && y == nStartY)
colour = olc::GREEN;
if (x == nEndX && y == nEndY)
colour = olc::RED;
// Draw Base
FillRect(x * nCellSize, y * nCellSize, nCellSize - nBorderWidth, nCellSize - nBorderWidth, colour);
// Draw "potential" or "distance" or "height" :D
//DrawString(x * nCellSize, y * nCellSize, std::to_string(nFlowFieldZ[p(x, y)]), olc::WHITE);
if (nFlowFieldZ[p(x, y)] > 0)
{
float ax[4], ay[4];
float fAngle = atan2f(fFlowFieldY[p(x, y)], fFlowFieldX[p(x, y)]);
float fRadius = (float)(nCellSize - nBorderWidth) / 2.0f;
int fOffsetX = x * nCellSize + ((nCellSize - nBorderWidth) / 2);
int fOffsetY = y * nCellSize + ((nCellSize - nBorderWidth) / 2);
ax[0] = cosf(fAngle) * fRadius + fOffsetX;
ay[0] = sinf(fAngle) * fRadius + fOffsetY;
ax[1] = cosf(fAngle) * -fRadius + fOffsetX;
ay[1] = sinf(fAngle) * -fRadius + fOffsetY;
ax[2] = cosf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetX;
ay[2] = sinf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetY;
ax[3] = cosf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetX;
ay[3] = sinf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetY;
DrawLine(ax[0], ay[0], ax[1], ay[1], olc::CYAN);
DrawLine(ax[0], ay[0], ax[2], ay[2], olc::CYAN);
DrawLine(ax[0], ay[0], ax[3], ay[3], olc::CYAN);
}
}
}
bool bFirstPoint = true;
int ox, oy;
for (auto &a : path)
{
if (bFirstPoint)
{
ox = a.first;
oy = a.second;
bFirstPoint = false;
}
else
{
DrawLine(
ox * nCellSize + ((nCellSize - nBorderWidth) / 2),
oy * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.first * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.second * nCellSize + ((nCellSize - nBorderWidth) / 2), olc::YELLOW);
ox = a.first;
oy = a.second;
FillCircle(ox * nCellSize + ((nCellSize - nBorderWidth) / 2), oy * nCellSize + ((nCellSize - nBorderWidth) / 2), 10, olc::YELLOW);
}
}
return true;
}
};
int main()
{
PathFinding_FlowFields demo;
if (demo.Construct(512, 480, 2, 2))
demo.Start();
return 0;
}

View File

@@ -1,434 +0,0 @@
/*
Convex Polygon Collision Detection
"Don't you dare try concave ones..." - 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:
~~~~~~~~~~~~~
Use arrow keys to control pentagon
Use WASD to control triangle
F1..F4 selects algorithm
Relevant Video: https://youtu.be/7Ik2vowGcU0
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
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <vector>
#include <algorithm>
// Override base class with your custom functionality
class PolygonCollisions : public olc::PixelGameEngine
{
public:
PolygonCollisions()
{
sAppName = "Polygon Collisions";
}
struct vec2d
{
float x;
float y;
};
struct polygon
{
std::vector<vec2d> p; // Transformed Points
vec2d pos; // Position of shape
float angle; // Direction of shape
std::vector<vec2d> o; // "Model" of shape
bool overlap = false; // Flag to indicate if overlap has occurred
};
std::vector<polygon> vecShapes;
int nMode = 0;
public:
bool OnUserCreate() override
{
// Create Pentagon
polygon s1;
float fTheta = 3.14159f * 2.0f / 5.0f;
s1.pos = { 100, 100 };
s1.angle = 0.0f;
for (int i = 0; i < 5; i++)
{
s1.o.push_back({ 30.0f * cosf(fTheta * i), 30.0f * sinf(fTheta * i) });
s1.p.push_back({ 30.0f * cosf(fTheta * i), 30.0f * sinf(fTheta * i) });
}
// Create Triangle
polygon s2;
fTheta = 3.14159f * 2.0f / 3.0f;
s2.pos = { 200, 150 };
s2.angle = 0.0f;
for (int i = 0; i < 3; i++)
{
s2.o.push_back({ 20.0f * cosf(fTheta * i), 20.0f * sinf(fTheta * i) });
s2.p.push_back({ 20.0f * cosf(fTheta * i), 20.0f * sinf(fTheta * i) });
}
// Create Quad
polygon s3;
s3.pos = { 50, 200 };
s3.angle = 0.0f;
s3.o.push_back({ -30, -30 });
s3.o.push_back({ -30, +30 });
s3.o.push_back({ +30, +30 });
s3.o.push_back({ +30, -30 });
s3.p.resize(4);
vecShapes.push_back(s1);
vecShapes.push_back(s2);
vecShapes.push_back(s3);
return true;
}
bool ShapeOverlap_SAT(polygon &r1, polygon &r2)
{
polygon *poly1 = &r1;
polygon *poly2 = &r2;
for (int shape = 0; shape < 2; shape++)
{
if (shape == 1)
{
poly1 = &r2;
poly2 = &r1;
}
for (int a = 0; a < poly1->p.size(); a++)
{
int b = (a + 1) % poly1->p.size();
vec2d axisProj = { -(poly1->p[b].y - poly1->p[a].y), poly1->p[b].x - poly1->p[a].x };
float d = sqrtf(axisProj.x * axisProj.x + axisProj.y * axisProj.y);
axisProj = { axisProj.x / d, axisProj.y / d };
// Work out min and max 1D points for r1
float min_r1 = INFINITY, max_r1 = -INFINITY;
for (int p = 0; p < poly1->p.size(); p++)
{
float q = (poly1->p[p].x * axisProj.x + poly1->p[p].y * axisProj.y);
min_r1 = std::min(min_r1, q);
max_r1 = std::max(max_r1, q);
}
// Work out min and max 1D points for r2
float min_r2 = INFINITY, max_r2 = -INFINITY;
for (int p = 0; p < poly2->p.size(); p++)
{
float q = (poly2->p[p].x * axisProj.x + poly2->p[p].y * axisProj.y);
min_r2 = std::min(min_r2, q);
max_r2 = std::max(max_r2, q);
}
if (!(max_r2 >= min_r1 && max_r1 >= min_r2))
return false;
}
}
return true;
}
bool ShapeOverlap_SAT_STATIC(polygon &r1, polygon &r2)
{
polygon *poly1 = &r1;
polygon *poly2 = &r2;
float overlap = INFINITY;
for (int shape = 0; shape < 2; shape++)
{
if (shape == 1)
{
poly1 = &r2;
poly2 = &r1;
}
for (int a = 0; a < poly1->p.size(); a++)
{
int b = (a + 1) % poly1->p.size();
vec2d axisProj = { -(poly1->p[b].y - poly1->p[a].y), poly1->p[b].x - poly1->p[a].x };
// Optional normalisation of projection axis enhances stability slightly
//float d = sqrtf(axisProj.x * axisProj.x + axisProj.y * axisProj.y);
//axisProj = { axisProj.x / d, axisProj.y / d };
// Work out min and max 1D points for r1
float min_r1 = INFINITY, max_r1 = -INFINITY;
for (int p = 0; p < poly1->p.size(); p++)
{
float q = (poly1->p[p].x * axisProj.x + poly1->p[p].y * axisProj.y);
min_r1 = std::min(min_r1, q);
max_r1 = std::max(max_r1, q);
}
// Work out min and max 1D points for r2
float min_r2 = INFINITY, max_r2 = -INFINITY;
for (int p = 0; p < poly2->p.size(); p++)
{
float q = (poly2->p[p].x * axisProj.x + poly2->p[p].y * axisProj.y);
min_r2 = std::min(min_r2, q);
max_r2 = std::max(max_r2, q);
}
// Calculate actual overlap along projected axis, and store the minimum
overlap = std::min(std::min(max_r1, max_r2) - std::max(min_r1, min_r2), overlap);
if (!(max_r2 >= min_r1 && max_r1 >= min_r2))
return false;
}
}
// If we got here, the objects have collided, we will displace r1
// by overlap along the vector between the two object centers
vec2d d = { r2.pos.x - r1.pos.x, r2.pos.y - r1.pos.y };
float s = sqrtf(d.x*d.x + d.y*d.y);
r1.pos.x -= overlap * d.x / s;
r1.pos.y -= overlap * d.y / s;
return false;
}
// Use edge/diagonal intersections.
bool ShapeOverlap_DIAGS(polygon &r1, polygon &r2)
{
polygon *poly1 = &r1;
polygon *poly2 = &r2;
for (int shape = 0; shape < 2; shape++)
{
if (shape == 1)
{
poly1 = &r2;
poly2 = &r1;
}
// Check diagonals of polygon...
for (int p = 0; p < poly1->p.size(); p++)
{
vec2d line_r1s = poly1->pos;
vec2d line_r1e = poly1->p[p];
// ...against edges of the other
for (int q = 0; q < poly2->p.size(); q++)
{
vec2d line_r2s = poly2->p[q];
vec2d line_r2e = poly2->p[(q + 1) % poly2->p.size()];
// Standard "off the shelf" line segment intersection
float h = (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r1e.y) - (line_r1s.x - line_r1e.x) * (line_r2e.y - line_r2s.y);
float t1 = ((line_r2s.y - line_r2e.y) * (line_r1s.x - line_r2s.x) + (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r2s.y)) / h;
float t2 = ((line_r1s.y - line_r1e.y) * (line_r1s.x - line_r2s.x) + (line_r1e.x - line_r1s.x) * (line_r1s.y - line_r2s.y)) / h;
if (t1 >= 0.0f && t1 < 1.0f && t2 >= 0.0f && t2 < 1.0f)
{
return true;
}
}
}
}
return false;
}
// Use edge/diagonal intersections.
bool ShapeOverlap_DIAGS_STATIC(polygon &r1, polygon &r2)
{
polygon *poly1 = &r1;
polygon *poly2 = &r2;
for (int shape = 0; shape < 2; shape++)
{
if (shape == 1)
{
poly1 = &r2;
poly2 = &r1;
}
// Check diagonals of this polygon...
for (int p = 0; p < poly1->p.size(); p++)
{
vec2d line_r1s = poly1->pos;
vec2d line_r1e = poly1->p[p];
vec2d displacement = { 0,0 };
// ...against edges of this polygon
for (int q = 0; q < poly2->p.size(); q++)
{
vec2d line_r2s = poly2->p[q];
vec2d line_r2e = poly2->p[(q + 1) % poly2->p.size()];
// Standard "off the shelf" line segment intersection
float h = (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r1e.y) - (line_r1s.x - line_r1e.x) * (line_r2e.y - line_r2s.y);
float t1 = ((line_r2s.y - line_r2e.y) * (line_r1s.x - line_r2s.x) + (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r2s.y)) / h;
float t2 = ((line_r1s.y - line_r1e.y) * (line_r1s.x - line_r2s.x) + (line_r1e.x - line_r1s.x) * (line_r1s.y - line_r2s.y)) / h;
if (t1 >= 0.0f && t1 < 1.0f && t2 >= 0.0f && t2 < 1.0f)
{
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
}
}
r1.pos.x += displacement.x * (shape == 0 ? -1 : +1);
r1.pos.y += displacement.y * (shape == 0 ? -1 : +1);
}
}
// Cant overlap if static collision is resolved
return false;
}
bool OnUserUpdate(float fElapsedTime) override
{
if (GetKey(olc::Key::F1).bReleased) nMode = 0;
if (GetKey(olc::Key::F2).bReleased) nMode = 1;
if (GetKey(olc::Key::F3).bReleased) nMode = 2;
if (GetKey(olc::Key::F4).bReleased) nMode = 3;
// Shape 1
if (GetKey(olc::Key::LEFT).bHeld) vecShapes[0].angle -= 2.0f * fElapsedTime;
if (GetKey(olc::Key::RIGHT).bHeld) vecShapes[0].angle += 2.0f * fElapsedTime;
if (GetKey(olc::Key::UP).bHeld)
{
vecShapes[0].pos.x += cosf(vecShapes[0].angle) * 60.0f * fElapsedTime;
vecShapes[0].pos.y += sinf(vecShapes[0].angle) * 60.0f * fElapsedTime;
}
if (GetKey(olc::Key::DOWN).bHeld)
{
vecShapes[0].pos.x -= cosf(vecShapes[0].angle) * 60.0f * fElapsedTime;
vecShapes[0].pos.y -= sinf(vecShapes[0].angle) * 60.0f * fElapsedTime;
}
// Shape 2
if (GetKey(olc::Key::A).bHeld) vecShapes[1].angle -= 2.0f * fElapsedTime;
if (GetKey(olc::Key::D).bHeld) vecShapes[1].angle += 2.0f * fElapsedTime;
if (GetKey(olc::Key::W).bHeld)
{
vecShapes[1].pos.x += cosf(vecShapes[1].angle) * 60.0f * fElapsedTime;
vecShapes[1].pos.y += sinf(vecShapes[1].angle) * 60.0f * fElapsedTime;
}
if (GetKey(olc::Key::S).bHeld)
{
vecShapes[1].pos.x -= cosf(vecShapes[1].angle) * 60.0f * fElapsedTime;
vecShapes[1].pos.y -= sinf(vecShapes[1].angle) * 60.0f * fElapsedTime;
}
// Update Shapes and reset flags
for (auto &r : vecShapes)
{
for (int i = 0; i < r.o.size(); i++)
r.p[i] =
{ // 2D Rotation Transform + 2D Translation
(r.o[i].x * cosf(r.angle)) - (r.o[i].y * sinf(r.angle)) + r.pos.x,
(r.o[i].x * sinf(r.angle)) + (r.o[i].y * cosf(r.angle)) + r.pos.y,
};
r.overlap = false;
}
// Check for overlap
for (int m = 0; m < vecShapes.size(); m++)
for (int n = m + 1; n < vecShapes.size(); n++)
{
switch (nMode)
{
case 0: vecShapes[m].overlap |= ShapeOverlap_SAT(vecShapes[m], vecShapes[n]); break;
case 1: vecShapes[m].overlap |= ShapeOverlap_SAT_STATIC(vecShapes[m], vecShapes[n]); break;
case 2: vecShapes[m].overlap |= ShapeOverlap_DIAGS(vecShapes[m], vecShapes[n]); break;
case 3: vecShapes[m].overlap |= ShapeOverlap_DIAGS_STATIC(vecShapes[m], vecShapes[n]); break;
}
}
// === Render Display ===
Clear(olc::BLUE);
// Draw Shapes
for (auto &r : vecShapes)
{
// Draw Boundary
for (int i = 0; i < r.p.size(); i++)
DrawLine(r.p[i].x, r.p[i].y, r.p[(i + 1) % r.p.size()].x, r.p[(i + 1) % r.p.size()].y, (r.overlap ? olc::RED : olc::WHITE));
// Draw Direction
DrawLine(r.p[0].x, r.p[0].y, r.pos.x, r.pos.y, (r.overlap ? olc::RED : olc::WHITE));
}
// Draw HUD
DrawString(8, 10, "F1: SAT", (nMode == 0 ? olc::RED : olc::YELLOW));
DrawString(8, 20, "F2: SAT/STATIC", (nMode == 1 ? olc::RED : olc::YELLOW));
DrawString(8, 30, "F3: DIAG", (nMode == 2 ? olc::RED : olc::YELLOW));
DrawString(8, 40, "F4: DIAG/STATIC", (nMode == 3 ? olc::RED : olc::YELLOW));
return true;
}
};
int main()
{
PolygonCollisions demo;
if (demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
}

Binary file not shown.

View File

@@ -1,272 +0,0 @@
/*
Simple example code for olcPGEX_Sound.h - Mind your speakers!
You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo.
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
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_SOUND
#include "olcPGEX_Sound.h"
#include <list>
class SoundTest : public olc::PixelGameEngine
{
public:
SoundTest()
{
sAppName = "Sound Test";
}
private:
int sndSampleA;
int sndSampleB;
int sndSampleC;
bool bToggle = false;
static bool bSynthPlaying;
static float fSynthFrequency;
static float fFilterVolume;
const olc::Key keys[12] = { olc::Key::Z, olc::Key::S, olc::Key::X, olc::Key::D, olc::Key::C,
olc::Key::V, olc::Key::G, olc::Key::B, olc::Key::H, olc::Key::N, olc::Key::J, olc::Key::M};
static float fPreviousSamples[128];
static int nSamplePos;
private:
// This is an optional function that allows the user to generate or synthesize sounds
// in a custom way, it is fed into the output mixer bu the extension
static float MyCustomSynthFunction(int nChannel, float fGlobalTime, float fTimeStep)
{
// Just generate a sine wave of the appropriate frequency
if (bSynthPlaying)
return sin(fSynthFrequency * 2.0f * 3.14159f * fGlobalTime);
else
return 0.0f;
}
// This is an optional function that allows the user to filter the output from
// the internal mixer of the extension. Here you could add effects or just
// control volume. I also like to use it to extract information about
// the currently playing output waveform
static float MyCustomFilterFunction(int nChannel, float fGlobalTime, float fSample)
{
// Fundamentally just control volume
float fOutput = fSample * fFilterVolume;
// But also add sample to list of previous samples for visualisation
fPreviousSamples[nSamplePos] = fOutput;
nSamplePos++;
nSamplePos %= 128;
return fOutput;
}
bool OnUserCreate()
{
olc::SOUND::InitialiseAudio(44100, 1, 8, 512);
sndSampleA = olc::SOUND::LoadAudioSample("SampleA.wav");
sndSampleB = olc::SOUND::LoadAudioSample("SampleB.wav");
sndSampleC = olc::SOUND::LoadAudioSample("SampleC.wav");
// Give the sound engine a hook to a custom generation function
olc::SOUND::SetUserSynthFunction(MyCustomSynthFunction);
// Give the sound engine a hook to a custom filtering function
olc::SOUND::SetUserFilterFunction(MyCustomFilterFunction);
return true;
}
bool OnUserUpdate(float fElapsedTime)
{
//olc::SOUND::PlaySample(sndTest);
auto PointInRect = [&](int x, int y, int rx, int ry, int rw, int rh)
{
return x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh);
};
int nMouseX = GetMouseX();
int nMouseY = GetMouseY();
if(GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 16, 128, 24))
olc::SOUND::PlaySample(sndSampleA); // Plays the sample once
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 48, 128, 24))
olc::SOUND::PlaySample(sndSampleB);
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 80, 128, 24))
{
bToggle = !bToggle;
if (bToggle)
{
olc::SOUND::PlaySample(sndSampleC, true); // Plays the sample in looping mode
}
else
{
olc::SOUND::StopSample(sndSampleC);
}
}
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 16, 90, 24))
fFilterVolume += 2.0f * fElapsedTime;
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 48, 90, 24))
fFilterVolume -= 2.0f * fElapsedTime;
if (fFilterVolume < 0.0f) fFilterVolume = 0.0f;
if (fFilterVolume > 1.0f) fFilterVolume = 1.0f;
// Detect keyboard - very simple synthesizer
if (IsFocused())
{
bool bKeyIsPressed = false;
float fFrequency = 0.0f;
for (int i = 0; i < 12; i++)
{
if (GetKey(keys[i]).bHeld)
{
bKeyIsPressed = true;
float fOctaveBaseFrequency = 220.0f;
float f12thRootOf2 = pow(2.0f, 1.0f / 12.0f);
fFrequency = fOctaveBaseFrequency * powf(f12thRootOf2, (float)i);
}
}
fSynthFrequency = fFrequency;
bSynthPlaying = bKeyIsPressed;
}
// Draw Buttons
Clear(olc::BLUE);
DrawRect(16, 16, 128, 24);
DrawString(20, 20, "Play Sample A");
DrawRect(16, 48, 128, 24);
DrawString(20, 52, "Play Sample B");
DrawRect(16, 80, 128, 24);
DrawString(20, 84, (bToggle ? "Stop Sample C" : "Loop Sample C"));
DrawRect(160, 16, 90, 24);
DrawString(164, 20, "Volume +");
DrawRect(160, 48, 90, 24);
DrawString(164, 52, "Volume -");
DrawString(164, 80, "Volume: " + std::to_string((int)(fFilterVolume * 10.0f)));
// Draw Keyboard
// White Keys
for (int i = 0; i < 7; i++)
{
FillRect(i * 16 + 8, 160, 16, 64);
DrawRect(i * 16 + 8, 160, 16, 64, olc::BLACK);
DrawString(i * 16 + 12, 212, std::string(1, "ZXCVBNM"[i]), olc::BLACK);
}
// Black Keys
for (int i = 0; i < 6; i++)
{
if (i != 2)
{
FillRect(i * 16 + 18, 160, 12, 32, olc::BLACK);
DrawString(i * 16 + 20, 180, std::string(1, "SDFGHJ"[i]), olc::WHITE);
}
}
// Draw visualisation
int nStartPos = (nSamplePos + 127) % 128;
for (int i = 127; i >= 0; i--)
{
float fSample = fPreviousSamples[(nSamplePos + i) % 128];
DrawLine(124 + i, 210, 124 + i, 210 + (int)(fSample * 20.0f), olc::RED);
}
return true;
}
// Note we must shut down the sound system too!!
bool OnUserDestroy()
{
olc::SOUND::DestroyAudio();
return true;
}
};
bool SoundTest::bSynthPlaying = false;
float SoundTest::fSynthFrequency = 0.0f;
float SoundTest::fFilterVolume = 1.0f;
int SoundTest::nSamplePos = 0;
float SoundTest::fPreviousSamples[128];
int main()
{
SoundTest demo;
if(demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
}

View File

@@ -1,257 +0,0 @@
/*
OneLoneCoder.com - 2D Sprite Affine Transformations
"No more 90 degree movements" - @Javidx9
Background
~~~~~~~~~~
The sophistication of 2D engines is enhanced when the programmer is
able to rotate and scale sprites in a convenient manner. This program
shows the basics of how affine transformations accomplish this.
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
Patreon: https://www.patreon.com/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
https://youtu.be/zxwLN2blwbQ
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <algorithm>
#undef min
#undef max
class SpriteTransforms : public olc::PixelGameEngine
{
public:
SpriteTransforms()
{
sAppName = "Sprite Transforms";
}
private:
olc::Sprite *sprCar;
struct matrix3x3
{
float m[3][3];
};
void Identity(matrix3x3 &mat)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Translate(matrix3x3 &mat, float ox, float oy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = ox;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = oy;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Rotate(matrix3x3 &mat, float fTheta)
{
mat.m[0][0] = cosf(fTheta); mat.m[1][0] = sinf(fTheta); mat.m[2][0] = 0.0f;
mat.m[0][1] = -sinf(fTheta); mat.m[1][1] = cosf(fTheta); mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Scale(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = sx; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = sy; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Shear(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = sx; mat.m[2][0] = 0.0f;
mat.m[0][1] = sy; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void MatrixMultiply(matrix3x3 &matResult, matrix3x3 &matA, matrix3x3 &matB)
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matResult.m[c][r] = matA.m[0][r] * matB.m[c][0] +
matA.m[1][r] * matB.m[c][1] +
matA.m[2][r] * matB.m[c][2];
}
}
}
void Forward(matrix3x3 &mat, float in_x, float in_y, float &out_x, float &out_y)
{
out_x = in_x * mat.m[0][0] + in_y * mat.m[1][0] + mat.m[2][0];
out_y = in_x * mat.m[0][1] + in_y * mat.m[1][1] + mat.m[2][1];
}
void Invert(matrix3x3 &matIn, matrix3x3 &matOut)
{
float det = matIn.m[0][0] * (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) -
matIn.m[1][0] * (matIn.m[0][1] * matIn.m[2][2] - matIn.m[2][1] * matIn.m[0][2]) +
matIn.m[2][0] * (matIn.m[0][1] * matIn.m[1][2] - matIn.m[1][1] * matIn.m[0][2]);
float idet = 1.0f / det;
matOut.m[0][0] = (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) * idet;
matOut.m[1][0] = (matIn.m[2][0] * matIn.m[1][2] - matIn.m[1][0] * matIn.m[2][2]) * idet;
matOut.m[2][0] = (matIn.m[1][0] * matIn.m[2][1] - matIn.m[2][0] * matIn.m[1][1]) * idet;
matOut.m[0][1] = (matIn.m[2][1] * matIn.m[0][2] - matIn.m[0][1] * matIn.m[2][2]) * idet;
matOut.m[1][1] = (matIn.m[0][0] * matIn.m[2][2] - matIn.m[2][0] * matIn.m[0][2]) * idet;
matOut.m[2][1] = (matIn.m[0][1] * matIn.m[2][0] - matIn.m[0][0] * matIn.m[2][1]) * idet;
matOut.m[0][2] = (matIn.m[0][1] * matIn.m[1][2] - matIn.m[0][2] * matIn.m[1][1]) * idet;
matOut.m[1][2] = (matIn.m[0][2] * matIn.m[1][0] - matIn.m[0][0] * matIn.m[1][2]) * idet;
matOut.m[2][2] = (matIn.m[0][0] * matIn.m[1][1] - matIn.m[0][1] * matIn.m[1][0]) * idet;
}
float fRotate = 0.0f;
public:
bool OnUserCreate() override
{
sprCar = new olc::Sprite("car_top1.png");
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
if (GetKey(olc::Key::Z).bHeld) fRotate -= 2.0f * fElapsedTime;
if (GetKey(olc::Key::X).bHeld) fRotate += 2.0f * fElapsedTime;
Clear(olc::DARK_CYAN);
SetPixelMode(olc::Pixel::ALPHA);
//DrawSprite(0, 0, sprCar, 3);
matrix3x3 matFinal, matA, matB, matC, matFinalInv;
Translate(matA, -100, -50);
Rotate(matB, fRotate);
MatrixMultiply(matC, matB, matA);
Translate(matA, (float)ScreenWidth()/2, (float)ScreenHeight()/2);
MatrixMultiply(matFinal, matA, matC);
Invert(matFinal, matFinalInv);
// Draws the dumb way, but leaves gaps
/*for (int x = 0; x < sprCar->width; x++)
{
for (int y = 0; y < sprCar->height; y++)
{
olc::Pixel p = sprCar->GetPixel(x, y);
float nx, ny;
Forward(matFinal, (float)x, (float)y, nx, ny);
Draw(nx, ny, p);
}
}*/
// Work out bounding box of sprite post-transformation
// by passing through sprite corner locations into
// transformation matrix
float ex, ey;
float sx, sy;
float px, py;
Forward(matFinal, 0.0f, 0.0f, px, py);
sx = px; sy = py;
ex = px; ey = py;
Forward(matFinal, (float)sprCar->width, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, 0.0f, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, (float)sprCar->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Use transformed corner locations in screen space to establish
// region of pixels to fill, using inverse transform to sample
// sprite at suitable locations.
for (int x = sx; x < ex; x++)
{
for (int y = sy; y < ey; y++)
{
float nx, ny;
Forward(matFinalInv, (float)x, (float)y, nx, ny);
olc::Pixel p = sprCar->GetPixel((int32_t)(nx + 0.5f), (int32_t)(ny + 0.5f));
Draw(x, y, p);
}
}
SetPixelMode(olc::Pixel::NORMAL);
return true;
}
};
int main()
{
SpriteTransforms demo;
if (demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,37 +0,0 @@
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
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
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;
}