Merge pull request #1 from OneLoneCoder/master

Update personal fork
This commit is contained in:
Benjamin Kyd
2019-02-09 22:09:21 +00:00
committed by GitHub
16 changed files with 6172 additions and 199 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1,670 @@
/*
BIG PROJECT - Top Down City Based Car Crime Game Part #1
"Probably gonna regret starting this one..." - javidx9
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018-2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Instructions:
~~~~~~~~~~~~~
This is the source that accompanies part 1 of the video series which
can be viewed via the link below. Here you can create and edit a city
from a top down perspective ad navigate it with the car.
Using the mouse left click you can select cells. Using right click clears
all the selected cells.
"E" - Lowers building height
"T" - Raises building height
"R" - Places road
"Z, X" - Zoom
"Up, Left, Right" - Control car
"F5" - Save current city
"F8" - Load existing city
A default city is provided for you - "example1.city", please ensure
you have this file also.
Relevant Video: https://youtu.be/mD6b_hP17WI
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Patreon: https://www.patreon.com/javidx9
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include <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.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# License (OLC-3)
Copyright 2018 OneLoneCoder.com
Copyright 2018-2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions

View File

@@ -46,7 +46,7 @@
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
// Include the olcPixelGameEngine
@@ -54,6 +54,7 @@
#include "olcPixelGameEngine.h"
// To use an extension, just include it
#define OLC_PGE_GRAPHICS2D
#include "olcPGEX_Graphics2D.h"
class TestExtension : public olc::PixelGameEngine
@@ -70,7 +71,7 @@ public:
for (int i = 0; i < 16; i++)
listEvents.push_back("");
spr = new olc::Sprite("logo_long.png");
spr = new olc::Sprite("new_piskel.png");
return true;
}
@@ -90,8 +91,8 @@ public:
DrawCircle(96, 32, 30); // Circle
float mx = GetMouseX();
float my = GetMouseY();
float mx = (float)GetMouseX();
float my = (float)GetMouseY();
float px1 = mx - 32, px2 = mx - 96;
float py1 = my - 32, py2 = my - 32;
@@ -101,8 +102,8 @@ public:
py1 = 22.0f * (py1 * pr1) + 32.0f;
px2 = 22.0f * (px2 * pr2) + 96.0f;
py2 = 22.0f * (py2 * pr2) + 32.0f;
FillCircle(px1, py1, 8, olc::CYAN);
FillCircle(px2, py2, 8, olc::CYAN);
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);
@@ -136,6 +137,8 @@ public:
nLog++;
}
std::string notes = "CDEFGAB";
// Test Text scaling and colours
DrawString(0, 360, "Text Scale = 1", olc::WHITE, 1);
@@ -168,6 +171,8 @@ public:
// Use extension to draw sprite with transform applied
olc::GFX2D::DrawSprite(spr, t1);
DrawSprite((int32_t)mx, (int32_t)my, spr, 4);
return true;
}
@@ -181,4 +186,4 @@ int main()
demo.Start();
return 0;
}
}

View File

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

View File

@@ -53,6 +53,8 @@
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_SOUND
#include "olcPGEX_Sound.h"
#include <list>
@@ -114,7 +116,8 @@ private:
bool OnUserCreate()
{
olc::SOUND::InitialiseAudio();
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");

View File

@@ -6,7 +6,7 @@ Please see https://github.com/OneLoneCoder/olcPixelGameEngine/wiki
# License (OLC-3)
Copyright 2018 OneLoneCoder.com
Copyright 2018, 2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions

View File

@@ -3,7 +3,7 @@
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.3 |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
@@ -15,7 +15,7 @@
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Copyright 2018 - 2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@@ -56,7 +56,7 @@
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
@@ -99,6 +99,8 @@ namespace olc
inline void Scale(float sx, float sy);
// Append a shear operation (sx, sy) to this transform
inline void Shear(float sx, float sy);
inline void Perspective(float ox, float oy);
// Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
inline void Forward(float in_x, float in_y, float &out_x, float &out_y);
// Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
@@ -121,7 +123,8 @@ namespace olc
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
@@ -250,16 +253,37 @@ namespace olc
Multiply();
}
void olc::GFX2D::Transform2D::Perspective(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y)
{
out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0];
out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1];
float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2];
if (out_z != 0)
{
out_x /= out_z;
out_y /= out_z;
}
}
void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y)
{
out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0];
out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1];
float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2];
if (out_z != 0)
{
out_x /= out_z;
out_y /= out_z;
}
}
void olc::GFX2D::Transform2D::Invert()
@@ -285,4 +309,5 @@ namespace olc
}
}
#endif
#endif

1174
olcPGEX_Graphics3D.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Sound - v0.2 |
| Sound - v0.3 |
+-------------------------------------------------------------+
What is this?
@@ -11,10 +11,18 @@
This is an extension to the olcPixelGameEngine, which provides
sound generation and wave playing routines.
Special Thanks:
~~~~~~~~~~~~~~~
Slavka - For entire non-windows system back end!
Gorbit99 - Testing, Bug Fixes
Cyberdroid - Testing, Bug Fixes
Dragoneye - Testing
Puol - Testing
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Copyright 2018 - 2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@@ -56,19 +64,60 @@
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
#ifndef OLC_PGEX_SOUND
#define OLC_PGEX_SOUND
#ifndef OLC_PGEX_SOUND_H
#define OLC_PGEX_SOUND_H
#include <istream>
#include <cstring>
#include <climits>
#include <algorithm>
#undef min
#undef max
// Choose a default sound backend
#if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS)
#ifdef __linux__
#define USE_ALSA
#endif
#ifdef __EMSCRIPTEN__
#define USE_OPENAL
#endif
#ifdef _WIN32
#define USE_WINDOWS
#endif
#endif
#ifdef USE_ALSA
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#endif
#ifdef USE_OPENAL
#include <AL/al.h>
#include <AL/alc.h>
#include <queue>
#endif
#pragma pack(push, 1)
typedef struct {
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
uint16_t cbSize;
} OLC_WAVEFORMATEX;
#pragma pack(pop)
namespace olc
{
// Container class for Advanced 2D Drawing functions
@@ -77,18 +126,18 @@ namespace olc
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class AudioSample
{
{
public:
AudioSample();
AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr);
public:
WAVEFORMATEX wavHeader;
OLC_WAVEFORMATEX wavHeader;
float *fSample = nullptr;
long nSamples = 0;
int nChannels = 0;
bool bSampleValid = false;
bool bSampleValid = false;
};
struct sCurrentlyPlayingSample
@@ -109,16 +158,16 @@ namespace olc
static void SetUserFilterFunction(std::function<float(int, float, float)> func);
public:
static unsigned int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
static void PlaySample(int id, bool bLoop = false);
static void StopSample(int id);
static void StopAll();
static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep);
#ifdef WIN32
private:
#ifdef USE_WINDOWS // Windows specific sound management
static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2);
static void AudioThread();
static unsigned int m_nSampleRate;
static unsigned int m_nChannels;
static unsigned int m_nBlockCount;
@@ -127,30 +176,51 @@ namespace olc
static short* m_pBlockMemory;
static WAVEHDR *m_pWaveHeaders;
static HWAVEOUT m_hwDevice;
static std::thread m_AudioThread;
static std::atomic<bool> m_bAudioThreadActive;
static std::atomic<unsigned int> m_nBlockFree;
static std::condition_variable m_cvBlockNotZero;
static std::mutex m_muxBlockNotZero;
#endif
#ifdef USE_ALSA
static snd_pcm_t *m_pPCM;
static unsigned int m_nSampleRate;
static unsigned int m_nChannels;
static unsigned int m_nBlockSamples;
static short* m_pBlockMemory;
#endif
#ifdef USE_OPENAL
static std::queue<ALuint> m_qAvailableBuffers;
static ALuint *m_pBuffers;
static ALuint m_nSource;
static ALCdevice *m_pDevice;
static ALCcontext *m_pContext;
static unsigned int m_nSampleRate;
static unsigned int m_nChannels;
static unsigned int m_nBlockCount;
static unsigned int m_nBlockSamples;
static short* m_pBlockMemory;
#endif
static void AudioThread();
static std::thread m_AudioThread;
static std::atomic<bool> m_bAudioThreadActive;
static std::atomic<float> m_fGlobalTime;
static std::function<float(int, float, float)> funcUserSynth;
static std::function<float(int, float, float)> funcUserFilter;
#endif
};
}
#ifdef WIN32
#pragma comment(lib, "winmm.lib")
// Implementation, platform-independent
#ifdef OLC_PGEX_SOUND
#undef OLC_PGEX_SOUND
namespace olc
{
SOUND::AudioSample::AudioSample()
{
}
{ }
SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack)
{
@@ -177,20 +247,20 @@ namespace olc
// which are not in the wav file
// Just check if wave format is compatible with olcPGE
if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100)
if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100)
return olc::FAIL;
// Search for audio data chunk
long nChunksize = 0;
uint32_t nChunksize = 0;
is.read(dump, sizeof(char) * 4); // Read chunk header
is.read((char*)&nChunksize, sizeof(long)); // Read chunk size
is.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size
while (strncmp(dump, "data", 4) != 0)
{
// Not audio data, so just skip it
//std::fseek(f, nChunksize, SEEK_CUR);
is.seekg(nChunksize, std::istream::cur);
is.read(dump, sizeof(char) * 4);
is.read((char*)&nChunksize, sizeof(long));
is.read((char*)&nChunksize, sizeof(uint32_t));
}
// Finally got to data, so read it all in and convert to float samples
@@ -211,20 +281,21 @@ namespace olc
{
is.read((char*)&s, sizeof(short));
*pSample = (float)s / (float)(MAXSHORT);
*pSample = (float)s / (float)(SHRT_MAX);
pSample++;
}
}
}
// All done, flag sound as valid
// All done, flag sound as valid
bSampleValid = true;
return olc::OK;
};
if (pack != nullptr)
{
std::istream is(&(pack->GetStreamBuffer(sWavFile)));
olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile);
std::istream is(&entry);
return ReadWave(is);
}
else
@@ -240,6 +311,131 @@ namespace olc
}
}
// This vector holds all loaded sound samples in memory
std::vector<olc::SOUND::AudioSample> vecAudioSamples;
// This structure represents a sound that is currently playing. It only
// holds the sound ID and where this instance of it is up to for its
// current playback
void SOUND::SetUserSynthFunction(std::function<float(int, float, float)> func)
{
funcUserSynth = func;
}
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func)
{
funcUserFilter = func;
}
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
// number is returned if successful, otherwise -1
int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack)
{
olc::SOUND::AudioSample a(sWavFile, pack);
if (a.bSampleValid)
{
vecAudioSamples.push_back(a);
return (unsigned int)vecAudioSamples.size();
}
else
return -1;
}
// Add sample 'id' to the mixers sounds to play list
void SOUND::PlaySample(int id, bool bLoop)
{
olc::SOUND::sCurrentlyPlayingSample a;
a.nAudioSampleID = id;
a.nSamplePosition = 0;
a.bFinished = false;
a.bFlagForStop = false;
a.bLoop = bLoop;
SOUND::listActiveSamples.push_back(a);
}
void SOUND::StopSample(int id)
{
// Find first occurence of sample id
auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; });
if (s != listActiveSamples.end())
s->bFlagForStop = true;
}
void SOUND::StopAll()
{
for (auto &s : listActiveSamples)
{
s.bFlagForStop = true;
}
}
float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep)
{
// Accumulate sample for this channel
float fMixerSample = 0.0f;
for (auto &s : listActiveSamples)
{
if (m_bAudioThreadActive)
{
if (s.bFlagForStop)
{
s.bLoop = false;
s.bFinished = true;
}
else
{
// Calculate sample position
s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep);
// If sample position is valid add to the mix
if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples)
fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel];
else
{
if (s.bLoop)
{
s.nSamplePosition = 0;
}
else
s.bFinished = true; // Else sound has completed
}
}
}
else
return 0.0f;
}
// If sounds have completed then remove them
listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; });
// The users application might be generating sound, so grab that if it exists
if (funcUserSynth != nullptr)
fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep);
// Return the sample via an optional user override to filter the sound
if (funcUserFilter != nullptr)
return funcUserFilter(nChannel, fGlobalTime, fMixerSample);
else
return fMixerSample;
}
std::thread SOUND::m_AudioThread;
std::atomic<bool> SOUND::m_bAudioThreadActive{ false };
std::atomic<float> SOUND::m_fGlobalTime{ 0.0f };
std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples;
std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr;
std::function<float(int, float, float)> SOUND::funcUserFilter = nullptr;
}
// Implementation, Windows-specific
#ifdef USE_WINDOWS
#pragma comment(lib, "winmm.lib")
namespace olc
{
bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{
// Initialise Sound Engine
@@ -376,118 +572,6 @@ namespace olc
}
}
// This vector holds all loaded sound samples in memory
std::vector<olc::SOUND::AudioSample> vecAudioSamples;
// This structure represents a sound that is currently playing. It only
// holds the sound ID and where this instance of it is up to for its
// current playback
void SOUND::SetUserSynthFunction(std::function<float(int, float, float)> func)
{
funcUserSynth = func;
}
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func)
{
funcUserFilter = func;
}
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
// number is returned if successful, otherwise -1
unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack)
{
olc::SOUND::AudioSample a(sWavFile, pack);
if (a.bSampleValid)
{
vecAudioSamples.push_back(a);
return vecAudioSamples.size();
}
else
return -1;
}
// Add sample 'id' to the mixers sounds to play list
void SOUND::PlaySample(int id, bool bLoop)
{
olc::SOUND::sCurrentlyPlayingSample a;
a.nAudioSampleID = id;
a.nSamplePosition = 0;
a.bFinished = false;
a.bFlagForStop = false;
a.bLoop = bLoop;
SOUND::listActiveSamples.push_back(a);
}
void SOUND::StopSample(int id)
{
// Find first occurence of sample id
auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; });
if(s != listActiveSamples.end())
s->bFlagForStop = true;
}
void SOUND::StopAll()
{
for (auto &s : listActiveSamples)
{
s.bFlagForStop = true;
}
}
float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep)
{
// Accumulate sample for this channel
float fMixerSample = 0.0f;
for (auto &s : listActiveSamples)
{
if (m_bAudioThreadActive)
{
if (s.bFlagForStop)
{
s.bLoop = false;
s.bFinished = true;
}
else
{
// Calculate sample position
s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep);
// If sample position is valid add to the mix
if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples)
fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel];
else
{
if (s.bLoop)
{
s.nSamplePosition = 0;
}
else
s.bFinished = true; // Else sound has completed
}
}
}
else
return 0.0f;
}
// If sounds have completed then remove them
listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; });
// The users application might be generating sound, so grab that if it exists
if(funcUserSynth != nullptr)
fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep);
// Return the sample via an optional user override to filter the sound
if (funcUserFilter != nullptr)
return funcUserFilter(nChannel, fGlobalTime, fMixerSample);
else
return fMixerSample;
}
unsigned int SOUND::m_nSampleRate = 0;
unsigned int SOUND::m_nChannels = 0;
unsigned int SOUND::m_nBlockCount = 0;
@@ -496,18 +580,313 @@ namespace olc
short* SOUND::m_pBlockMemory = nullptr;
WAVEHDR *SOUND::m_pWaveHeaders = nullptr;
HWAVEOUT SOUND::m_hwDevice;
std::thread SOUND::m_AudioThread;
std::atomic<bool> SOUND::m_bAudioThreadActive = false;
std::atomic<unsigned int> SOUND::m_nBlockFree = 0;
std::condition_variable SOUND::m_cvBlockNotZero;
std::mutex SOUND::m_muxBlockNotZero;
std::atomic<float> SOUND::m_fGlobalTime = 0.0f;
std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples;
std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr;
std::function<float(int, float, float)> SOUND::funcUserFilter = nullptr;
}
#elif defined(USE_ALSA)
namespace olc
{
bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{
// Initialise Sound Engine
m_bAudioThreadActive = false;
m_nSampleRate = nSampleRate;
m_nChannels = nChannels;
m_nBlockSamples = nBlockSamples;
m_pBlockMemory = nullptr;
// Open PCM stream
int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
return DestroyAudio();
// Prepare the parameter structure and set default parameters
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(m_pPCM, params);
// Set other parameters
snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0);
snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels);
snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0);
snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0);
// Save these parameters
rc = snd_pcm_hw_params(m_pPCM, params);
if (rc < 0)
return DestroyAudio();
listActiveSamples.clear();
// Allocate Wave|Block Memory
m_pBlockMemory = new short[m_nBlockSamples];
if (m_pBlockMemory == nullptr)
return DestroyAudio();
std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0);
// Unsure if really needed, helped prevent underrun on my setup
snd_pcm_start(m_pPCM);
for (unsigned int i = 0; i < nBlocks; i++)
rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512);
snd_pcm_start(m_pPCM);
m_bAudioThreadActive = true;
m_AudioThread = std::thread(&SOUND::AudioThread);
return true;
}
// Stop and clean up audio system
bool SOUND::DestroyAudio()
{
m_bAudioThreadActive = false;
m_AudioThread.join();
snd_pcm_drain(m_pPCM);
snd_pcm_close(m_pPCM);
return false;
}
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
// with audio data. If no requests are available it goes dormant until the sound
// card is ready for more data. The block is fille by the "user" in some manner
// and then issued to the soundcard.
void SOUND::AudioThread()
{
m_fGlobalTime = 0.0f;
static float fTimeStep = 1.0f / (float)m_nSampleRate;
// Goofy hack to get maximum integer for a type at run-time
short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
float fMaxSample = (float)nMaxSample;
short nPreviousSample = 0;
while (m_bAudioThreadActive)
{
short nNewSample = 0;
auto clip = [](float fSample, float fMax)
{
if (fSample >= 0.0)
return fmin(fSample, fMax);
else
return fmax(fSample, -fMax);
};
for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
{
// User Process
for (unsigned int c = 0; c < m_nChannels; c++)
{
nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample);
m_pBlockMemory[n + c] = nNewSample;
nPreviousSample = nNewSample;
}
m_fGlobalTime = m_fGlobalTime + fTimeStep;
}
// Send block to sound device
snd_pcm_uframes_t nLeft = m_nBlockSamples;
short *pBlockPos = m_pBlockMemory;
while (nLeft > 0)
{
int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft);
if (rc > 0)
{
pBlockPos += rc * m_nChannels;
nLeft -= rc;
}
if (rc == -EAGAIN) continue;
if (rc == -EPIPE) // an underrun occured, prepare the device for more data
snd_pcm_prepare(m_pPCM);
}
}
}
snd_pcm_t* SOUND::m_pPCM = nullptr;
unsigned int SOUND::m_nSampleRate = 0;
unsigned int SOUND::m_nChannels = 0;
unsigned int SOUND::m_nBlockSamples = 0;
short* SOUND::m_pBlockMemory = nullptr;
}
#elif defined(USE_OPENAL)
namespace olc
{
bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{
// Initialise Sound Engine
m_bAudioThreadActive = false;
m_nSampleRate = nSampleRate;
m_nChannels = nChannels;
m_nBlockCount = nBlocks;
m_nBlockSamples = nBlockSamples;
m_pBlockMemory = nullptr;
// Open the device and create the context
m_pDevice = alcOpenDevice(NULL);
if (m_pDevice)
{
m_pContext = alcCreateContext(m_pDevice, NULL);
alcMakeContextCurrent(m_pContext);
}
else
return DestroyAudio();
// Allocate memory for sound data
alGetError();
m_pBuffers = new ALuint[m_nBlockCount];
alGenBuffers(m_nBlockCount, m_pBuffers);
alGenSources(1, &m_nSource);
for (unsigned int i = 0; i < m_nBlockCount; i++)
m_qAvailableBuffers.push(m_pBuffers[i]);
listActiveSamples.clear();
// Allocate Wave|Block Memory
m_pBlockMemory = new short[m_nBlockSamples];
if (m_pBlockMemory == nullptr)
return DestroyAudio();
std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0);
m_bAudioThreadActive = true;
m_AudioThread = std::thread(&SOUND::AudioThread);
return true;
}
// Stop and clean up audio system
bool SOUND::DestroyAudio()
{
m_bAudioThreadActive = false;
m_AudioThread.join();
alDeleteBuffers(m_nBlockCount, m_pBuffers);
delete[] m_pBuffers;
alDeleteSources(1, &m_nSource);
alcMakeContextCurrent(NULL);
alcDestroyContext(m_pContext);
alcCloseDevice(m_pDevice);
return false;
}
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
// with audio data. If no requests are available it goes dormant until the sound
// card is ready for more data. The block is fille by the "user" in some manner
// and then issued to the soundcard.
void SOUND::AudioThread()
{
m_fGlobalTime = 0.0f;
static float fTimeStep = 1.0f / (float)m_nSampleRate;
// Goofy hack to get maximum integer for a type at run-time
short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
float fMaxSample = (float)nMaxSample;
short nPreviousSample = 0;
std::vector<ALuint> vProcessed;
while (m_bAudioThreadActive)
{
ALint nState, nProcessed;
alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState);
alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed);
// Add processed buffers to our queue
vProcessed.resize(nProcessed);
alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data());
for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf);
// Wait until there is a free buffer (ewww)
if (m_qAvailableBuffers.empty()) continue;
short nNewSample = 0;
auto clip = [](float fSample, float fMax)
{
if (fSample >= 0.0)
return fmin(fSample, fMax);
else
return fmax(fSample, -fMax);
};
for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
{
// User Process
for (unsigned int c = 0; c < m_nChannels; c++)
{
nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample);
m_pBlockMemory[n + c] = nNewSample;
nPreviousSample = nNewSample;
}
m_fGlobalTime = m_fGlobalTime + fTimeStep;
}
// Fill OpenAL data buffer
alBufferData(
m_qAvailableBuffers.front(),
m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
m_pBlockMemory,
2 * m_nBlockSamples,
m_nSampleRate
);
// Add it to the OpenAL queue
alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front());
// Remove it from ours
m_qAvailableBuffers.pop();
// If it's not playing for some reason, change that
if (nState != AL_PLAYING)
alSourcePlay(m_nSource);
}
}
std::queue<ALuint> SOUND::m_qAvailableBuffers;
ALuint *SOUND::m_pBuffers = nullptr;
ALuint SOUND::m_nSource = 0;
ALCdevice *SOUND::m_pDevice = nullptr;
ALCcontext *SOUND::m_pContext = nullptr;
unsigned int SOUND::m_nSampleRate = 0;
unsigned int SOUND::m_nChannels = 0;
unsigned int SOUND::m_nBlockCount = 0;
unsigned int SOUND::m_nBlockSamples = 0;
short* SOUND::m_pBlockMemory = nullptr;
}
#else // Some other platform
namespace olc
{
bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{
return true;
}
// Stop and clean up audio system
bool SOUND::DestroyAudio()
{
return false;
}
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
// with audio data. If no requests are available it goes dormant until the sound
// card is ready for more data. The block is fille by the "user" in some manner
// and then issued to the soundcard.
void SOUND::AudioThread()
{ }
}
#endif
// Currently no Linux implementation so just go blank :(
#endif
#endif
#endif // OLC_PGEX_SOUND

View File

@@ -2,11 +2,9 @@
olcPixelGameEngine.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine v1.11 |
| OneLoneCoder Pixel Game Engine v1.13 |
| "Like the command prompt console one, but not..." - javidx9 |
+-------------------------------------------------------------+
The Original & Best... :P
What is this?
~~~~~~~~~~~~~
@@ -52,7 +50,7 @@
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Copyright 2018 - 2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@@ -90,6 +88,7 @@
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9
Relevant Videos
~~~~~~~~~~~~~~~
@@ -121,17 +120,20 @@
~~~~~~
I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim,
JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice
& MagetzUb for advice, ideas and testing, and I'd like to extend
my appreciation to the 14K YouTube followers and 1K Discord server
Ralakus, Gorbit99, raoul & MagetzUb for advice, ideas and testing, and I'd like
to extend my appreciation to the 23K YouTube followers and 1.5K Discord server
members who give me the motivation to keep going with all this :D
Special thanks to those who bring gifts!
GnarGnarHead.......Domina
Gorbit99...........Bastion
Special thanks to my Patreons too - I wont name you on here, but I've
certainly enjoyed my tea and flapjacks :D
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
David Barr, aka javidx9, ©OneLoneCoder 2018, 2019
*/
//////////////////////////////////////////////////////////////////////////////////////////
@@ -279,7 +281,8 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
ResourcePack();
~ResourcePack();
struct sEntry : public std::streambuf {
uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); }};
uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); }
};
public:
olc::rcode AddToPack(std::string sFile);
@@ -323,7 +326,9 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL);
Pixel GetPixel(int32_t x, int32_t y);
void SetPixel(int32_t x, int32_t y, Pixel p);
Pixel Sample(float x, float y);
Pixel SampleBL(float u, float v);
Pixel* GetData();
private:
@@ -346,7 +351,9 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
UP, DOWN, LEFT, RIGHT,
SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN,
BACK, ESCAPE, ENTER, PAUSE, SCROLL,
BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL,
NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9,
NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL,
};
@@ -402,6 +409,7 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
// olc::Pixel::MASK = Transparent if alpha is < 255
// olc::Pixel::ALPHA = Full transparency
void SetPixelMode(Pixel::Mode m);
Pixel::Mode GetPixelMode();
// Use a custom blend function
void SetPixelMode(std::function<olc::Pixel(const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest)> pixelMode);
// Change the blend factor form between 0.0f to 1.0f;
@@ -541,9 +549,12 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
Class2.cpp - #define OLC_PGE_APPLICATION #include "Class2.h"
main.cpp - Includes Class1.h and Class2.h
If all of this is a bit too confusing, you can split this file in two!
Everything below this comment block can go into olcPixelGameEngineOOP.cpp
and everything above it can go into olcPixelGameEngineOOP.h
If all else fails, create a file called "olcPixelGameEngine.cpp" with the following
two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying
about defining things. Dont forget to include that cpp file as part of your build!
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
*/
@@ -578,14 +589,9 @@ namespace olc
std::wstring w(buffer);
delete[] buffer;
return w;
#else
return L"SVN FTW!";
#endif
//#ifdef __MINGW32__
// wchar_t *buffer = new wchar_t[sImageFile.length() + 1];
// mbstowcs(buffer, sImageFile.c_str(), sImageFile.length());
// buffer[sImageFile.length()] = L'\0';
// wsImageFile = buffer;
// delete[] buffer;
//#else
}
Sprite::Sprite()
@@ -647,7 +653,8 @@ namespace olc
}
else
{
std::istream is(&(pack->GetStreamBuffer(sImageFile)));
auto streamBuffer = pack->GetStreamBuffer(sImageFile);
std::istream is(&streamBuffer);
ReadData(is);
}
@@ -818,11 +825,33 @@ namespace olc
Pixel Sprite::Sample(float x, float y)
{
int32_t sx = (int32_t)(x * (float)width);
int32_t sy = (int32_t)(y * (float)height);
int32_t sx = (int32_t)((x * (float)width) - 0.5f);
int32_t sy = (int32_t)((y * (float)height) - 0.5f);
return GetPixel(sx, sy);
}
Pixel Sprite::SampleBL(float u, float v)
{
u = u * width - 0.5f;
v = v * height - 0.5f;
int x = (int)u;
int y = (int)v;
float u_ratio = u - x;
float v_ratio = v - y;
float u_opposite = 1 - u_ratio;
float v_opposite = 1 - v_ratio;
olc::Pixel p1 = GetPixel(x, y);
olc::Pixel p2 = GetPixel(x+1, y);
olc::Pixel p3 = GetPixel(x, y+1);
olc::Pixel p4 = GetPixel(x+1, y+1);
return olc::Pixel(
(uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio),
(uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio),
(uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio));
}
Pixel* Sprite::GetData() { return pColData; }
//==========================================================
@@ -852,10 +881,10 @@ namespace olc
// Create entry
sEntry e;
e.data = nullptr;
e.nFileSize = p;
e.nFileSize = (uint32_t)p;
// Read file into memory
e.data = new uint8_t[e.nFileSize];
e.data = new uint8_t[(uint32_t)e.nFileSize];
ifs.read((char*)e.data, e.nFileSize);
ifs.close();
@@ -886,7 +915,7 @@ namespace olc
std::streampos offset = ofs.tellp();
for (auto &e : mapFiles)
{
e.second.nFileOffset = offset;
e.second.nFileOffset = (uint32_t)offset;
ofs.write((char*)e.second.data, e.second.nFileSize);
offset += e.second.nFileSize;
}
@@ -936,12 +965,10 @@ namespace olc
// 2) Read Data
for (auto &e : mapFiles)
{
e.second.data = new uint8_t[e.second.nFileSize];
e.second.data = new uint8_t[(uint32_t)e.second.nFileSize];
ifs.seekg(e.second.nFileOffset);
ifs.read((char*)e.second.data, e.second.nFileSize);
//e.second.setg
e.second._config();
//e.second.pubsetbuf((char*)e.second.data, e.second.nFileSize);
}
ifs.close();
@@ -1542,6 +1569,11 @@ namespace olc
nPixelMode = m;
}
Pixel::Mode PixelGameEngine::GetPixelMode()
{
return nPixelMode;
}
void PixelGameEngine::SetPixelMode(std::function<olc::Pixel(const int x, const int y, const olc::Pixel&, const olc::Pixel&)> pixelMode)
{
funcPixelMode = pixelMode;
@@ -1884,6 +1916,7 @@ namespace olc
mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12;
mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP;
mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN;
mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE;
mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME;
@@ -1894,6 +1927,10 @@ namespace olc
mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4;
mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9;
mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4;
mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9;
mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL;
return olc_hWnd;
}
@@ -1941,8 +1978,8 @@ namespace olc
case WM_MOUSELEAVE: sge->bHasMouseFocus = false;
case WM_SETFOCUS: sge->bHasInputFocus = true; return 0;
case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0;
case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0;
case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0;
case WM_KEYDOWN: sge->pKeyNewState[mapKeys[(uint16_t)wParam]] = true; return 0;
case WM_KEYUP: sge->pKeyNewState[mapKeys[(uint16_t)wParam]] = false; return 0;
case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0;
case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0;
case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0;
@@ -1995,6 +2032,7 @@ namespace olc
mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12;
mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP;
mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER;
mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE;
mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME;
@@ -2005,6 +2043,10 @@ namespace olc
mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4;
mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9;
mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4;
mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9;
mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL;
return olc_Display;
}
@@ -2044,4 +2086,4 @@ namespace olc
//=============================================================
}
#endif
#endif