BIN
CarCrimeCity/Part1/City_Roads1_mip0.png
Normal file
BIN
CarCrimeCity/Part1/City_Roads1_mip0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
670
CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp
Normal file
670
CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp
Normal 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;
|
||||
}
|
||||
BIN
CarCrimeCity/Part1/car_top.png
Normal file
BIN
CarCrimeCity/Part1/car_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
BIN
CarCrimeCity/Part1/car_top1.png
Normal file
BIN
CarCrimeCity/Part1/car_top1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
CarCrimeCity/Part1/example1.city
Normal file
BIN
CarCrimeCity/Part1/example1.city
Normal file
Binary file not shown.
1174
CarCrimeCity/Part1/olcPGEX_Graphics3D.h
Normal file
1174
CarCrimeCity/Part1/olcPGEX_Graphics3D.h
Normal file
File diff suppressed because it is too large
Load Diff
2067
CarCrimeCity/Part1/olcPixelGameEngine.h
Normal file
2067
CarCrimeCity/Part1/olcPixelGameEngine.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
434
OneLoneCoder_PGE_PolygonCollisions1.cpp
Normal file
434
OneLoneCoder_PGE_PolygonCollisions1.cpp
Normal 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;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
1174
olcPGEX_Graphics3D.h
Normal file
File diff suppressed because it is too large
Load Diff
687
olcPGEX_Sound.h
687
olcPGEX_Sound.h
@@ -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(¶ms);
|
||||
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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user