Removed stuff i dont care about
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
BIN
SampleA.wav
BIN
SampleA.wav
Binary file not shown.
BIN
SampleB.wav
BIN
SampleB.wav
Binary file not shown.
BIN
SampleC.wav
BIN
SampleC.wav
Binary file not shown.
BIN
car_top1.png
BIN
car_top1.png
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
BIN
light_cast.png
BIN
light_cast.png
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
BIN
logo_long.png
BIN
logo_long.png
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user