PixeGameEngine v1.20

+ Foundations for more system implementations
+ More compiler friendly on Linux
+ Tidied up public repo
pull/113/head
javidx9 5 years ago
parent 1cf1544316
commit 396a40eb5c
  1. 624
      Extensions/olcPGEX_Graphics2D.h
  2. 2346
      Extensions/olcPGEX_Graphics3D.h
  3. 1782
      Extensions/olcPGEX_Sound.h
  4. 0
      Videos/CarCrimeCity/Part1/City_Roads1_mip0.png
  5. 1338
      Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp
  6. 0
      Videos/CarCrimeCity/Part1/car_top.png
  7. 0
      Videos/CarCrimeCity/Part1/car_top1.png
  8. 0
      Videos/CarCrimeCity/Part1/example1.city
  9. 2346
      Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h
  10. 4132
      Videos/CarCrimeCity/Part1/olcPixelGameEngine.h
  11. 0
      Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h
  12. 0
      Videos/CarCrimeCity/Part2/Lua533/include/lua.h
  13. 0
      Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp
  14. 0
      Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h
  15. 0
      Videos/CarCrimeCity/Part2/Lua533/include/lualib.h
  16. 0
      Videos/CarCrimeCity/Part2/Lua533/liblua53.a
  17. 0
      Videos/CarCrimeCity/Part2/Lua533/lua53.dll
  18. 0
      Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj
  19. 0
      Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png
  20. 0
      Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend
  21. 0
      Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1
  22. 0
      Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj
  23. 0
      Videos/CarCrimeCity/Part2/assets/cities/example1.city
  24. 100
      Videos/CarCrimeCity/Part2/assets/config.lua
  25. 0
      Videos/CarCrimeCity/Part2/assets/system/car_top.png
  26. 0
      Videos/CarCrimeCity/Part2/assets/system/car_top3.png
  27. 0
      Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png
  28. 0
      Videos/CarCrimeCity/Part2/assets/system/clouds1.png
  29. 0
      Videos/CarCrimeCity/Part2/assets/system/clouds2.png
  30. 0
      Videos/CarCrimeCity/Part2/assets/system/grass1.png
  31. 0
      Videos/CarCrimeCity/Part2/assets/system/roads1.png
  32. 0
      Videos/CarCrimeCity/Part2/assets/system/roads2.png
  33. 0
      Videos/CarCrimeCity/Part2/assets/system/roads3.png
  34. 0
      Videos/CarCrimeCity/Part2/assets/system/roads4.png
  35. 0
      Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png
  36. 0
      Videos/CarCrimeCity/Part2/assets/system/water1.png
  37. 0
      Videos/CarCrimeCity/Part2/assets/system/waterside1.png
  38. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj
  39. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj
  40. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj
  41. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj
  42. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj
  43. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj
  44. 0
      Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png
  45. 412
      Videos/CarCrimeCity/Part2/cAutomata.cpp
  46. 214
      Videos/CarCrimeCity/Part2/cAutomata.h
  47. 1418
      Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp
  48. 578
      Videos/CarCrimeCity/Part2/cCarCrimeCity.h
  49. 242
      Videos/CarCrimeCity/Part2/cCell.cpp
  50. 120
      Videos/CarCrimeCity/Part2/cCell.h
  51. 106
      Videos/CarCrimeCity/Part2/cCell_Building.cpp
  52. 50
      Videos/CarCrimeCity/Part2/cCell_Building.h
  53. 98
      Videos/CarCrimeCity/Part2/cCell_Plane.cpp
  54. 68
      Videos/CarCrimeCity/Part2/cCell_Plane.h
  55. 1624
      Videos/CarCrimeCity/Part2/cCell_Road.cpp
  56. 108
      Videos/CarCrimeCity/Part2/cCell_Road.h
  57. 180
      Videos/CarCrimeCity/Part2/cCell_Water.cpp
  58. 50
      Videos/CarCrimeCity/Part2/cCell_Water.h
  59. 402
      Videos/CarCrimeCity/Part2/cCityMap.cpp
  60. 126
      Videos/CarCrimeCity/Part2/cCityMap.h
  61. 310
      Videos/CarCrimeCity/Part2/cGameSettings.cpp
  62. 130
      Videos/CarCrimeCity/Part2/cGameSettings.h
  63. 0
      Videos/CarCrimeCity/Part2/lua53.dll
  64. 732
      Videos/CarCrimeCity/Part2/main.cpp
  65. 3448
      Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h
  66. 10
      Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp
  67. 4634
      Videos/CarCrimeCity/Part2/olcPixelGameEngine.h
  68. 0
      Videos/DemoBinaries/OLC_8BitsImProc.zip
  69. 1008
      Videos/OneLoneCoder_PGE_8BitsImProc.cpp
  70. 1000
      Videos/OneLoneCoder_PGE_Balls2.cpp
  71. 376
      Videos/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp
  72. 850
      Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp
  73. 866
      Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp
  74. 1072
      Videos/OneLoneCoder_PGE_Polymorphism.cpp
  75. 542
      Videos/OneLoneCoder_PGE_RobotArm1.cpp
  76. 0
      Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp
  77. 542
      Videos/OneLoneCoder_PGE_SoundTest.cpp
  78. 512
      Videos/OneLoneCoder_PGE_SpriteTransforms.cpp
  79. 0
      Videos/OneLoneCoder_PGE_olcEngine3D.cpp
  80. 0
      Videos/SampleA.wav
  81. 0
      Videos/SampleB.wav
  82. 0
      Videos/SampleC.wav
  83. 3018
      Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp
  84. 446
      Videos/SavingSedit/Zix_PGE_Controller.h
  85. 84
      Videos/SavingSedit/licence.txt
  86. 624
      Videos/SavingSedit/olcPGEX_Graphics2D.h
  87. 1810
      Videos/SavingSedit/olcPGEX_Sound.h
  88. 762
      Videos/SavingSedit/olcPGEX_TileMaps_new.h
  89. 4634
      Videos/SavingSedit/olcPixelGameEngine.h
  90. 0
      Videos/car_top1.png
  91. 0
      Videos/light_cast.png
  92. 0
      Videos/logo_long.png
  93. 0
      Videos/mountains.obj
  94. 313
      Videos/olcPGEX_Graphics2D.h
  95. 1174
      Videos/olcPGEX_Graphics3D.h
  96. 892
      Videos/olcPGEX_Sound.h
  97. 2353
      Videos/olcPixelGameEngine.h
  98. 0
      Videos/zombie.png
  99. 149
      olcPixelGameEngine.h

@ -1,313 +1,313 @@
/*
olcPGEX_Graphics2D.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
This is an extension to the olcPixelGameEngine, which provides
advanced olc::Sprite manipulation and drawing routines. To use
it, simply include this header file.
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
Matrices stored as [Column][Row] (i.e. x, y)
|C0R0 C1R0 C2R0| | x | | x'|
|C0R1 C1R1 C2R1| * | y | = | y'|
|C0R2 C1R2 C2R2| |1.0| | - |
*/
#ifndef OLC_PGEX_GFX2D
#define OLC_PGEX_GFX2D
#include <algorithm>
#undef min
#undef max
namespace olc
{
// Container class for Advanced 2D Drawing functions
class GFX2D : public olc::PGEX
{
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class Transform2D
{
public:
inline Transform2D();
public:
// Set this transformation to unity
inline void Reset();
// Append a rotation of fTheta radians to this transform
inline void Rotate(float fTheta);
// Append a translation (ox, oy) to this transform
inline void Translate(float ox, float oy);
// Append a scaling operation (sx, sy) to this transform
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)
inline void Backward(float in_x, float in_y, float &out_x, float &out_y);
// Regenerate the Inverse Transformation
inline void Invert();
private:
inline void Multiply();
float matrix[4][3][3];
int nTargetMatrix;
int nSourceMatrix;
bool bDirty;
};
public:
// Draws a sprite with the transform applied
inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
};
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
{
if (sprite == nullptr)
return;
// Work out bounding rectangle of sprite
float ex, ey;
float sx, sy;
float px, py;
transform.Forward(0.0f, 0.0f, sx, sy);
px = sx; py = sy;
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward(0.0f, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Perform inversion of transform if required
transform.Invert();
if (ex < sx)
std::swap(ex, sx);
if (ey < sy)
std::swap(ey, sy);
// Iterate through render space, and sample Sprite from suitable texel location
for (float i = sx; i < ex; i++)
{
for (float j = sy; j < ey; j++)
{
float ox, oy;
transform.Backward(i, j, ox, oy);
pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
}
}
}
olc::GFX2D::Transform2D::Transform2D()
{
Reset();
}
void olc::GFX2D::Transform2D::Reset()
{
nTargetMatrix = 0;
nSourceMatrix = 1;
bDirty = true;
// Columns Then Rows
// Matrices 0 & 1 are used as swaps in Transform accumulation
matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
// Matrix 2 is a cache matrix to hold the immediate transform operation
// Matrix 3 is a cache matrix to hold the inverted transform
}
void olc::GFX2D::Transform2D::Multiply()
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
matrix[2][2][r] * matrix[nSourceMatrix][c][2];
}
}
std::swap(nTargetMatrix, nSourceMatrix);
bDirty = true; // Any transform multiply dirties the inversion
}
void olc::GFX2D::Transform2D::Rotate(float fTheta)
{
// Construct Rotation Matrix
matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Scale(float sx, float sy)
{
// Construct Scale Matrix
matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Shear(float sx, float sy)
{
// Construct Shear Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Translate(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
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()
{
if (bDirty) // Obviously costly so only do if needed
{
float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
float idet = 1.0f / det;
matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
bDirty = false;
}
}
}
#endif
/*
olcPGEX_Graphics2D.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
This is an extension to the olcPixelGameEngine, which provides
advanced olc::Sprite manipulation and drawing routines. To use
it, simply include this header file.
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
Matrices stored as [Column][Row] (i.e. x, y)
|C0R0 C1R0 C2R0| | x | | x'|
|C0R1 C1R1 C2R1| * | y | = | y'|
|C0R2 C1R2 C2R2| |1.0| | - |
*/
#ifndef OLC_PGEX_GFX2D
#define OLC_PGEX_GFX2D
#include <algorithm>
#undef min
#undef max
namespace olc
{
// Container class for Advanced 2D Drawing functions
class GFX2D : public olc::PGEX
{
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class Transform2D
{
public:
inline Transform2D();
public:
// Set this transformation to unity
inline void Reset();
// Append a rotation of fTheta radians to this transform
inline void Rotate(float fTheta);
// Append a translation (ox, oy) to this transform
inline void Translate(float ox, float oy);
// Append a scaling operation (sx, sy) to this transform
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)
inline void Backward(float in_x, float in_y, float &out_x, float &out_y);
// Regenerate the Inverse Transformation
inline void Invert();
private:
inline void Multiply();
float matrix[4][3][3];
int nTargetMatrix;
int nSourceMatrix;
bool bDirty;
};
public:
// Draws a sprite with the transform applied
inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
};
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
{
if (sprite == nullptr)
return;
// Work out bounding rectangle of sprite
float ex, ey;
float sx, sy;
float px, py;
transform.Forward(0.0f, 0.0f, sx, sy);
px = sx; py = sy;
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward(0.0f, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Perform inversion of transform if required
transform.Invert();
if (ex < sx)
std::swap(ex, sx);
if (ey < sy)
std::swap(ey, sy);
// Iterate through render space, and sample Sprite from suitable texel location
for (float i = sx; i < ex; i++)
{
for (float j = sy; j < ey; j++)
{
float ox, oy;
transform.Backward(i, j, ox, oy);
pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
}
}
}
olc::GFX2D::Transform2D::Transform2D()
{
Reset();
}
void olc::GFX2D::Transform2D::Reset()
{
nTargetMatrix = 0;
nSourceMatrix = 1;
bDirty = true;
// Columns Then Rows
// Matrices 0 & 1 are used as swaps in Transform accumulation
matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
// Matrix 2 is a cache matrix to hold the immediate transform operation
// Matrix 3 is a cache matrix to hold the inverted transform
}
void olc::GFX2D::Transform2D::Multiply()
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
matrix[2][2][r] * matrix[nSourceMatrix][c][2];
}
}
std::swap(nTargetMatrix, nSourceMatrix);
bDirty = true; // Any transform multiply dirties the inversion
}
void olc::GFX2D::Transform2D::Rotate(float fTheta)
{
// Construct Rotation Matrix
matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Scale(float sx, float sy)
{
// Construct Scale Matrix
matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Shear(float sx, float sy)
{
// Construct Shear Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Translate(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
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()
{
if (bDirty) // Obviously costly so only do if needed
{
float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
float idet = 1.0f / det;
matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
bDirty = false;
}
}
}
#endif
#endif

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@ -1,50 +1,50 @@
-- Size of pixel
PixelWidth = 2
PixelHeight = 2
-- Size of display window in pixels
ScreenWidth = 768
ScreenHeight = 480
--ScreenWidth = 384
--ScreenHeight = 240
FullScreen = false
-- Default city parameters
DefaultMapWidth = 64
DefaultMapHeight = 32
--DefaultCityFile = "assets/cities/example1.city"
-- Textures used by various game systems
Textures = {}
Textures[1] = {"Grass", "assets/system/grass1.png"}
Textures[2] = {"AllRoads", "assets/system/roads4.png"}
Textures[3] = {"Water", "assets/system/water1.png"}
Textures[4] = {"Clouds", "assets/system/clouds2.png"}
Textures[5] = {"WaterSide", "assets/system/waterside1.png"}
Textures[6] = {"Smoke", "assets/system/skidsmoke1.png"}
-- Buildings
Buildings = {}
Buildings[1] = {"javidx9", "UnitBuilding_1", "assets/buildings/unit_building.obj", "",
0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0}
Buildings[2] = {"UDXS", "Apartments_1", "assets/buildings/udxs_building1.obj", "assets/buildings/udxs_building1.png",
0.0, 0.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, 0.0}
Vehicles = {}
Vehicles[1] = {"JustinRM", "Sedan", "assets/vehicles/CarCrime_Sedan.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 1.5708, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[2] = {"JustinRM", "SUV", "assets/vehicles/CarCrime_SUV.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[3] = {"JustinRM", "TruckCab", "assets/vehicles/CarCrime_Truck_Cab.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[4] = {"JustinRM", "TruckTrailer", "assets/vehicles/CarCrime_Truck_Trailer.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[5] = {"JustinRM", "UTE", "assets/vehicles/CarCrime_Ute.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[6] = {"JustinRM", "Wagon", "assets/vehicles/CarCrime_Wahon.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
-- Size of pixel
PixelWidth = 2
PixelHeight = 2
-- Size of display window in pixels
ScreenWidth = 768
ScreenHeight = 480
--ScreenWidth = 384
--ScreenHeight = 240
FullScreen = false
-- Default city parameters
DefaultMapWidth = 64
DefaultMapHeight = 32
--DefaultCityFile = "assets/cities/example1.city"
-- Textures used by various game systems
Textures = {}
Textures[1] = {"Grass", "assets/system/grass1.png"}
Textures[2] = {"AllRoads", "assets/system/roads4.png"}
Textures[3] = {"Water", "assets/system/water1.png"}
Textures[4] = {"Clouds", "assets/system/clouds2.png"}
Textures[5] = {"WaterSide", "assets/system/waterside1.png"}
Textures[6] = {"Smoke", "assets/system/skidsmoke1.png"}
-- Buildings
Buildings = {}
Buildings[1] = {"javidx9", "UnitBuilding_1", "assets/buildings/unit_building.obj", "",
0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0}
Buildings[2] = {"UDXS", "Apartments_1", "assets/buildings/udxs_building1.obj", "assets/buildings/udxs_building1.png",
0.0, 0.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, 0.0}
Vehicles = {}
Vehicles[1] = {"JustinRM", "Sedan", "assets/vehicles/CarCrime_Sedan.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 1.5708, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[2] = {"JustinRM", "SUV", "assets/vehicles/CarCrime_SUV.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[3] = {"JustinRM", "TruckCab", "assets/vehicles/CarCrime_Truck_Cab.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[4] = {"JustinRM", "TruckTrailer", "assets/vehicles/CarCrime_Truck_Trailer.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[5] = {"JustinRM", "UTE", "assets/vehicles/CarCrime_Ute.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}
Vehicles[6] = {"JustinRM", "Wagon", "assets/vehicles/CarCrime_Wahon.obj", "assets/vehicles/CarTex_256.png",
0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0}

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before

Width:  |  Height:  |  Size: 482 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 383 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 691 KiB

After

Width:  |  Height:  |  Size: 691 KiB

Before

Width:  |  Height:  |  Size: 732 KiB

After

Width:  |  Height:  |  Size: 732 KiB

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -1,206 +1,206 @@
#include "cAutomata.h"
cAuto_Node::cAuto_Node()
{
pos = { 0,0 };
}
cAuto_Node::cAuto_Node(const olc::vf2d &worldpos)
{
pos = worldpos;
}
olc::vf2d cAuto_Track::GetPostion(float t, cAuto_Node *pStart)
{
// pStart indicates the node the automata first encounted this track
if (node[0] == pStart)
{
return node[0]->pos + (node[1]->pos - node[0]->pos) * (t / fTrackLength);
}
else
{
return node[1]->pos + (node[0]->pos - node[1]->pos) * (t / fTrackLength);
}
}
cAuto_Body::cAuto_Body()
{
}
cAuto_Body::~cAuto_Body()
{
}
void cAuto_Body::UpdateAuto(float fElapsedTime)
{
// Work out which node is the target destination
cAuto_Node *pExitNode = pCurrentTrack->node[0];
if (pExitNode == pTrackOriginNode)
pExitNode = pCurrentTrack->node[1];
bool bAutomataCanMove = true;
float fDistanceToAutoInFront = 1.0f;
// First check if the vehicle overlaps with the one in front of it
// Get an iterator for this automata
auto itThisAutomata = std::find(pCurrentTrack->listAutos.begin(), pCurrentTrack->listAutos.end(), this);
// If this automata is at the front of this track segment
if (*itThisAutomata == pCurrentTrack->listAutos.front())
{
// Then check all the following track segments. Take the position of
// each vehicle at the back of the track segments auto list
for (auto &track : pExitNode->listTracks)
{
if (track != pCurrentTrack && !track->listAutos.empty())
{
// Get Auto at back
float fDistanceFromTrackStartToAutoRear = track->listAutos.back()->fAutoPos - track->listAutos.back()->fAutoLength;
if ((*itThisAutomata)->fAutoPos < (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - fAutoLength))
{
// Move Automata along track, as there is space
//bAutomataCanMove = true;
fDistanceToAutoInFront = (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - 0.1f) - (*itThisAutomata)->fAutoPos;
}
else
{
// No space, so do not move automata
bAutomataCanMove = false;
}
}
else
{
// Track in front was empty, node is clear to pass through so
//bAutomataCanMove = true;
}
}
}
else
{
// Get the automata in front
auto itAutomataInFront = itThisAutomata;
itAutomataInFront--;
// If the distance between the front of the automata in front and the fornt of this automata
// is greater than the length of the automata in front, then there is space for this automata
// to enter
if (fabs((*itAutomataInFront)->fAutoPos - (*itThisAutomata)->fAutoPos) > ((*itAutomataInFront)->fAutoLength + 0.1f))
{
// Move Automata along track
//bAutomataCanMove = true;
fDistanceToAutoInFront = ((*itAutomataInFront)->fAutoPos - (*itAutomataInFront)->fAutoLength - 0.1f) - (*itThisAutomata)->fAutoPos;
}
else
{
// No space, so do not move automata
bAutomataCanMove = false;
}
}
if (bAutomataCanMove)
{
if (fDistanceToAutoInFront > pCurrentTrack->fTrackLength) fDistanceToAutoInFront = pCurrentTrack->fTrackLength;
fAutoPos += fElapsedTime * std::max(fDistanceToAutoInFront, 1.0f) * (fAutoLength < 0.1f ? 0.3f : 0.5f);
}
if (fAutoPos >= pCurrentTrack->fTrackLength)
{
// Automata has reached end of current track
// Check if it can transition beyond node
if (!pExitNode->bBlock)
{
// It can, so reset position along track back to start
fAutoPos -= pCurrentTrack->fTrackLength;
// Choose a track from the node not equal to this one, that has an unblocked exit node
// For now choose at random
cAuto_Track *pNewTrack = nullptr;
if (pExitNode->listTracks.size() == 2)
{
// Automata is travelling along straight joined sections, one of the
// tracks is the track its just come in on, the other is the exit, so
// choose the exit.
auto it = pExitNode->listTracks.begin();
pNewTrack = (*it);
if (pCurrentTrack == pNewTrack)
{
++it;
pNewTrack = (*it);
}
}
else
{
// Automata has reached a junction with several exits
while (pNewTrack == nullptr)
{
int i = rand() % pExitNode->listTracks.size();
int j = 0;
for (auto it = pExitNode->listTracks.begin(); it != pExitNode->listTracks.end(); ++it)
{
cAuto_Track* track = (*it);
// Work out which node is the target destination
cAuto_Node *pNewExitNode = track->node[0];
if (pNewExitNode == pExitNode)
pNewExitNode = track->node[1];
if (j == i && track != pCurrentTrack && !pNewExitNode->bBlock /*((*it)->cell != pCurrentTrack->cell)*/)
{
pNewTrack = track;
break;
}
j++;
}
}
}
// Change to new track, the origin node of the next
// track is the same as the exit node to the current track
pTrackOriginNode = pExitNode;
// Remove the automata from the front of the queue
// on the current track
pCurrentTrack->listAutos.pop_front();
// Switch the automatas track link to the new track
pCurrentTrack = pNewTrack;
// Push the automata onto the back of the new track queue
pCurrentTrack->listAutos.push_back(this);
}
else
{
// It cant pass the node, so clamp automata at this location
fAutoPos = pCurrentTrack->fTrackLength;
}
}
else
{
// Automata is travelling
vAutoPos = pCurrentTrack->GetPostion(fAutoPos, pTrackOriginNode);
}
}
#include "cAutomata.h"
cAuto_Node::cAuto_Node()
{
pos = { 0,0 };
}
cAuto_Node::cAuto_Node(const olc::vf2d &worldpos)
{
pos = worldpos;
}
olc::vf2d cAuto_Track::GetPostion(float t, cAuto_Node *pStart)
{
// pStart indicates the node the automata first encounted this track
if (node[0] == pStart)
{
return node[0]->pos + (node[1]->pos - node[0]->pos) * (t / fTrackLength);
}
else
{
return node[1]->pos + (node[0]->pos - node[1]->pos) * (t / fTrackLength);
}
}
cAuto_Body::cAuto_Body()
{
}
cAuto_Body::~cAuto_Body()
{
}
void cAuto_Body::UpdateAuto(float fElapsedTime)
{
// Work out which node is the target destination
cAuto_Node *pExitNode = pCurrentTrack->node[0];
if (pExitNode == pTrackOriginNode)
pExitNode = pCurrentTrack->node[1];
bool bAutomataCanMove = true;
float fDistanceToAutoInFront = 1.0f;
// First check if the vehicle overlaps with the one in front of it
// Get an iterator for this automata
auto itThisAutomata = std::find(pCurrentTrack->listAutos.begin(), pCurrentTrack->listAutos.end(), this);
// If this automata is at the front of this track segment
if (*itThisAutomata == pCurrentTrack->listAutos.front())
{
// Then check all the following track segments. Take the position of
// each vehicle at the back of the track segments auto list
for (auto &track : pExitNode->listTracks)
{
if (track != pCurrentTrack && !track->listAutos.empty())
{
// Get Auto at back
float fDistanceFromTrackStartToAutoRear = track->listAutos.back()->fAutoPos - track->listAutos.back()->fAutoLength;
if ((*itThisAutomata)->fAutoPos < (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - fAutoLength))
{
// Move Automata along track, as there is space
//bAutomataCanMove = true;
fDistanceToAutoInFront = (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - 0.1f) - (*itThisAutomata)->fAutoPos;
}
else
{
// No space, so do not move automata
bAutomataCanMove = false;
}
}
else
{
// Track in front was empty, node is clear to pass through so
//bAutomataCanMove = true;
}
}
}
else
{
// Get the automata in front
auto itAutomataInFront = itThisAutomata;
itAutomataInFront--;
// If the distance between the front of the automata in front and the fornt of this automata
// is greater than the length of the automata in front, then there is space for this automata
// to enter
if (fabs((*itAutomataInFront)->fAutoPos - (*itThisAutomata)->fAutoPos) > ((*itAutomataInFront)->fAutoLength + 0.1f))
{
// Move Automata along track
//bAutomataCanMove = true;
fDistanceToAutoInFront = ((*itAutomataInFront)->fAutoPos - (*itAutomataInFront)->fAutoLength - 0.1f) - (*itThisAutomata)->fAutoPos;
}
else
{
// No space, so do not move automata
bAutomataCanMove = false;
}
}
if (bAutomataCanMove)
{
if (fDistanceToAutoInFront > pCurrentTrack->fTrackLength) fDistanceToAutoInFront = pCurrentTrack->fTrackLength;
fAutoPos += fElapsedTime * std::max(fDistanceToAutoInFront, 1.0f) * (fAutoLength < 0.1f ? 0.3f : 0.5f);
}
if (fAutoPos >= pCurrentTrack->fTrackLength)
{
// Automata has reached end of current track
// Check if it can transition beyond node
if (!pExitNode->bBlock)
{
// It can, so reset position along track back to start
fAutoPos -= pCurrentTrack->fTrackLength;
// Choose a track from the node not equal to this one, that has an unblocked exit node
// For now choose at random
cAuto_Track *pNewTrack = nullptr;
if (pExitNode->listTracks.size() == 2)
{
// Automata is travelling along straight joined sections, one of the
// tracks is the track its just come in on, the other is the exit, so
// choose the exit.
auto it = pExitNode->listTracks.begin();
pNewTrack = (*it);
if (pCurrentTrack == pNewTrack)
{
++it;
pNewTrack = (*it);
}
}
else
{
// Automata has reached a junction with several exits
while (pNewTrack == nullptr)
{
int i = rand() % pExitNode->listTracks.size();
int j = 0;
for (auto it = pExitNode->listTracks.begin(); it != pExitNode->listTracks.end(); ++it)
{
cAuto_Track* track = (*it);
// Work out which node is the target destination
cAuto_Node *pNewExitNode = track->node[0];
if (pNewExitNode == pExitNode)
pNewExitNode = track->node[1];
if (j == i && track != pCurrentTrack && !pNewExitNode->bBlock /*((*it)->cell != pCurrentTrack->cell)*/)
{
pNewTrack = track;
break;
}
j++;
}
}
}
// Change to new track, the origin node of the next
// track is the same as the exit node to the current track
pTrackOriginNode = pExitNode;
// Remove the automata from the front of the queue
// on the current track
pCurrentTrack->listAutos.pop_front();
// Switch the automatas track link to the new track
pCurrentTrack = pNewTrack;
// Push the automata onto the back of the new track queue
pCurrentTrack->listAutos.push_back(this);
}
else
{
// It cant pass the node, so clamp automata at this location
fAutoPos = pCurrentTrack->fTrackLength;
}
}
else
{
// Automata is travelling
vAutoPos = pCurrentTrack->GetPostion(fAutoPos, pTrackOriginNode);
}
}

@ -1,107 +1,107 @@
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#pragma once
#include "olcPixelGameEngine.h"
class cAuto_Track;
class cAuto_Node;
class cAuto_Body;
class cCell;
class cAuto_Node
{
public:
cAuto_Node();
cAuto_Node(const olc::vf2d &worldpos);
olc::vf2d pos;
bool bBlock = false;
std::list<cAuto_Track*> listTracks;
};
class cAuto_Track
{
public:
cAuto_Node* node[2]; // Two end nodes
cCell* cell; // Pointer to host cell
olc::vf2d GetPostion(float t, cAuto_Node *pstart);
std::list<cAuto_Body*> listAutos;
float fTrackLength = 1.0f;
};
class cAuto_Body
{
public:
cAuto_Body();
~cAuto_Body();
public:
void UpdateAuto(float fElapsedTime);
public:
olc::vf2d vAutoPos = { 0.0f, 0.0f };
float fAutoPos = 0.0f; // Location of automata along track
float fAutoLength = 0.0f; // Physical length of automata
cAuto_Track *pCurrentTrack = nullptr;
cAuto_Node *pTrackOriginNode = nullptr;
};
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#pragma once
#include "olcPixelGameEngine.h"
class cAuto_Track;
class cAuto_Node;
class cAuto_Body;
class cCell;
class cAuto_Node
{
public:
cAuto_Node();
cAuto_Node(const olc::vf2d &worldpos);
olc::vf2d pos;
bool bBlock = false;
std::list<cAuto_Track*> listTracks;
};
class cAuto_Track
{
public:
cAuto_Node* node[2]; // Two end nodes
cCell* cell; // Pointer to host cell
olc::vf2d GetPostion(float t, cAuto_Node *pstart);
std::list<cAuto_Body*> listAutos;
float fTrackLength = 1.0f;
};
class cAuto_Body
{
public:
cAuto_Body();
~cAuto_Body();
public:
void UpdateAuto(float fElapsedTime);
public:
olc::vf2d vAutoPos = { 0.0f, 0.0f };
float fAutoPos = 0.0f; // Location of automata along track
float fAutoLength = 0.0f; // Physical length of automata
cAuto_Track *pCurrentTrack = nullptr;
cAuto_Node *pTrackOriginNode = nullptr;
};

@ -1,289 +1,289 @@
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#pragma once
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cGameSettings.h"
#include "cCityMap.h"
#include <vector>
#include <unordered_set>
struct sSmokeDecal
{
float fLifetime = 0.1f;
olc::GFX3D::vec3d pos;
};
class cCarCrimeCity : public olc::PixelGameEngine
{
public:
cCarCrimeCity();
~cCarCrimeCity();
private:
bool OnUserCreate() override;
bool OnUserUpdate(float fElapsedTime) override;
bool OnUserDestroy() override;
private:
class cGameObjectQuad
{
public:
cGameObjectQuad(float w, float h)
{
fWidth = w;
fHeight = h;
fAngle = 0.0f;
// Construct Model Quad Geometry
vecPointsModel = { {-fWidth / 2.0f, -fHeight / 2.0f, -0.01f, 1.0f},
{-fWidth / 2.0f, +fHeight / 2.0f, -0.01f, 1.0f},
{+fWidth / 2.0f, +fHeight / 2.0f, -0.01f, 1.0f},
{+fWidth / 2.0f, -fHeight / 2.0f, -0.01f, 1.0f} };
vecPointsWorld.resize(vecPointsModel.size());
TransformModelToWorld();
}
void TransformModelToWorld()
{
for (size_t i = 0; i < vecPointsModel.size(); ++i)
{
vecPointsWorld[i] = {
(vecPointsModel[i].x * cosf(fAngle)) - (vecPointsModel[i].y * sinf(fAngle)) + pos.x,
(vecPointsModel[i].x * sinf(fAngle)) + (vecPointsModel[i].y * cosf(fAngle)) + pos.y,
vecPointsModel[i].z,
vecPointsModel[i].w
};
}
}
std::vector<olc::GFX3D::triangle> GetTriangles()
{
// Return triangles based upon this quad
return
{
{vecPointsWorld[0], vecPointsWorld[1], vecPointsWorld[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::RED},
{vecPointsWorld[0], vecPointsWorld[2], vecPointsWorld[3], 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::RED},
};
}
// Use rectangle edge intersections.
bool StaticCollisionWith(cGameObjectQuad &r2, bool bResolveStatic = false)
{
struct vec2d { float x; float y; };
bool bCollision = false;
// Check diagonals of R1 against edges of R2
for (size_t p = 0; p < vecPointsWorld.size(); p++)
{
vec2d line_r1s = { pos.x, pos.y };
vec2d line_r1e = { vecPointsWorld[p].x, vecPointsWorld[p].y };
vec2d displacement = { 0,0 };
for (size_t q = 0; q < r2.vecPointsWorld.size(); q++)
{
vec2d line_r2s = { r2.vecPointsWorld[q].x, r2.vecPointsWorld[q].y };
vec2d line_r2e = { r2.vecPointsWorld[(q + 1) % r2.vecPointsWorld.size()].x, r2.vecPointsWorld[(q + 1) % r2.vecPointsWorld.size()].y };
// 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)
{
if (bResolveStatic)
{
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
bCollision = true;
}
else
return true;
}
}
pos.x -= displacement.x;
pos.y -= displacement.y;
}
// Check diagonals of R2 against edges of R1
for (size_t p = 0; p < r2.vecPointsWorld.size(); p++)
{
vec2d line_r1s = { r2.pos.x, r2.pos.y };
vec2d line_r1e = { r2.vecPointsWorld[p].x, r2.vecPointsWorld[p].y };
vec2d displacement = { 0,0 };
for (size_t q = 0; q < vecPointsWorld.size(); q++)
{
vec2d line_r2s = { vecPointsWorld[q].x, vecPointsWorld[q].y };
vec2d line_r2e = { vecPointsWorld[(q + 1) % vecPointsWorld.size()].x, vecPointsWorld[(q + 1) % vecPointsWorld.size()].y };
// 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)
{
if (bResolveStatic)
{
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
bCollision = true;
}
else
return true;
}
}
pos.x += displacement.x;
pos.y += displacement.y;
}
return bCollision;
}
std::vector<olc::GFX3D::triangle> meshTris;
std::vector<olc::GFX3D::vec3d> vecPointsModel;
std::vector<olc::GFX3D::vec3d> vecPointsWorld;
olc::GFX3D::vec3d pos;
float fWidth;
float fHeight;
float fOriginX;
float fOriginY;
float fAngle;
};
bool LoadAssets();
std::map<std::string, olc::Sprite*> mapAssetTextures;
std::map<std::string, olc::GFX3D::mesh*> mapAssetMeshes;
std::map<std::string, olc::GFX3D::mat4x4> mapAssetTransform;
// Camera variables
olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -3.0f };
olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f };
olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -3.0f };
olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f };
// Ray Casting Parameters
olc::vf2d viewWorldTopLeft;
olc::vf2d viewWorldBottomRight;
// Cloud movement variables
float fCloudOffsetX = 0.0f;
float fCloudOffsetY = 0.0f;
// Mouse Control
olc::vf2d vOffset = { 0.0f, 0.0f };
olc::vf2d vStartPan = { 0.0f, 0.0f };
olc::vf2d vMouseOnGround = { 0.0f, 0.0f };
float fScale = 1.0f;
olc::vf2d GetMouseOnGround(const olc::vf2d &vMouseScreen);
//cVehicle car;
olc::vf2d carvel;
olc::vf2d carpos;
float fSpeed = 0.0f;
float fAngle = 0.0f;
std::list<cAuto_Body*> listAutomata; // Holds all automata, note its a pointer because we use polymorphism
void SpawnPedestrian(int x, int y);
void SpawnVehicle(int x, int y);
//cGameObjectQuad *goCar = nullptr;
//cGameObjectQuad *goObstacle = nullptr;
//std::vector<cGameObjectQuad> vecObstacles;
cCityMap *pCity = nullptr;
float fGlobalTime = 0.0f;
// Editing Utilities
bool bEditMode = true;
int nMouseX = 0;
int nMouseY = 0;
struct sCellLoc { int x, y; };
std::unordered_set<int> setSelectedCells;
//std::list<sSmokeDecal> listDecalSmoke;
//int nTrafficState = 0;
void DoEditMode(float fElapsedTime);
};
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#pragma once
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cGameSettings.h"
#include "cCityMap.h"
#include <vector>
#include <unordered_set>
struct sSmokeDecal
{
float fLifetime = 0.1f;
olc::GFX3D::vec3d pos;
};
class cCarCrimeCity : public olc::PixelGameEngine
{
public:
cCarCrimeCity();
~cCarCrimeCity();
private:
bool OnUserCreate() override;
bool OnUserUpdate(float fElapsedTime) override;
bool OnUserDestroy() override;
private:
class cGameObjectQuad
{
public:
cGameObjectQuad(float w, float h)
{
fWidth = w;
fHeight = h;
fAngle = 0.0f;
// Construct Model Quad Geometry
vecPointsModel = { {-fWidth / 2.0f, -fHeight / 2.0f, -0.01f, 1.0f},
{-fWidth / 2.0f, +fHeight / 2.0f, -0.01f, 1.0f},
{+fWidth / 2.0f, +fHeight / 2.0f, -0.01f, 1.0f},
{+fWidth / 2.0f, -fHeight / 2.0f, -0.01f, 1.0f} };
vecPointsWorld.resize(vecPointsModel.size());
TransformModelToWorld();
}
void TransformModelToWorld()
{
for (size_t i = 0; i < vecPointsModel.size(); ++i)
{
vecPointsWorld[i] = {
(vecPointsModel[i].x * cosf(fAngle)) - (vecPointsModel[i].y * sinf(fAngle)) + pos.x,
(vecPointsModel[i].x * sinf(fAngle)) + (vecPointsModel[i].y * cosf(fAngle)) + pos.y,
vecPointsModel[i].z,
vecPointsModel[i].w
};
}
}
std::vector<olc::GFX3D::triangle> GetTriangles()
{
// Return triangles based upon this quad
return
{
{vecPointsWorld[0], vecPointsWorld[1], vecPointsWorld[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::RED},
{vecPointsWorld[0], vecPointsWorld[2], vecPointsWorld[3], 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::RED},
};
}
// Use rectangle edge intersections.
bool StaticCollisionWith(cGameObjectQuad &r2, bool bResolveStatic = false)
{
struct vec2d { float x; float y; };
bool bCollision = false;
// Check diagonals of R1 against edges of R2
for (size_t p = 0; p < vecPointsWorld.size(); p++)
{
vec2d line_r1s = { pos.x, pos.y };
vec2d line_r1e = { vecPointsWorld[p].x, vecPointsWorld[p].y };
vec2d displacement = { 0,0 };
for (size_t q = 0; q < r2.vecPointsWorld.size(); q++)
{
vec2d line_r2s = { r2.vecPointsWorld[q].x, r2.vecPointsWorld[q].y };
vec2d line_r2e = { r2.vecPointsWorld[(q + 1) % r2.vecPointsWorld.size()].x, r2.vecPointsWorld[(q + 1) % r2.vecPointsWorld.size()].y };
// 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)
{
if (bResolveStatic)
{
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
bCollision = true;
}
else
return true;
}
}
pos.x -= displacement.x;
pos.y -= displacement.y;
}
// Check diagonals of R2 against edges of R1
for (size_t p = 0; p < r2.vecPointsWorld.size(); p++)
{
vec2d line_r1s = { r2.pos.x, r2.pos.y };
vec2d line_r1e = { r2.vecPointsWorld[p].x, r2.vecPointsWorld[p].y };
vec2d displacement = { 0,0 };
for (size_t q = 0; q < vecPointsWorld.size(); q++)
{
vec2d line_r2s = { vecPointsWorld[q].x, vecPointsWorld[q].y };
vec2d line_r2e = { vecPointsWorld[(q + 1) % vecPointsWorld.size()].x, vecPointsWorld[(q + 1) % vecPointsWorld.size()].y };
// 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)
{
if (bResolveStatic)
{
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
bCollision = true;
}
else
return true;
}
}
pos.x += displacement.x;
pos.y += displacement.y;
}
return bCollision;
}
std::vector<olc::GFX3D::triangle> meshTris;
std::vector<olc::GFX3D::vec3d> vecPointsModel;
std::vector<olc::GFX3D::vec3d> vecPointsWorld;
olc::GFX3D::vec3d pos;
float fWidth;
float fHeight;
float fOriginX;
float fOriginY;
float fAngle;
};
bool LoadAssets();
std::map<std::string, olc::Sprite*> mapAssetTextures;
std::map<std::string, olc::GFX3D::mesh*> mapAssetMeshes;
std::map<std::string, olc::GFX3D::mat4x4> mapAssetTransform;
// Camera variables
olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -3.0f };
olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f };
olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -3.0f };
olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f };
// Ray Casting Parameters
olc::vf2d viewWorldTopLeft;
olc::vf2d viewWorldBottomRight;
// Cloud movement variables
float fCloudOffsetX = 0.0f;
float fCloudOffsetY = 0.0f;
// Mouse Control
olc::vf2d vOffset = { 0.0f, 0.0f };
olc::vf2d vStartPan = { 0.0f, 0.0f };
olc::vf2d vMouseOnGround = { 0.0f, 0.0f };
float fScale = 1.0f;
olc::vf2d GetMouseOnGround(const olc::vf2d &vMouseScreen);
//cVehicle car;
olc::vf2d carvel;
olc::vf2d carpos;
float fSpeed = 0.0f;
float fAngle = 0.0f;
std::list<cAuto_Body*> listAutomata; // Holds all automata, note its a pointer because we use polymorphism
void SpawnPedestrian(int x, int y);
void SpawnVehicle(int x, int y);
//cGameObjectQuad *goCar = nullptr;
//cGameObjectQuad *goObstacle = nullptr;
//std::vector<cGameObjectQuad> vecObstacles;
cCityMap *pCity = nullptr;
float fGlobalTime = 0.0f;
// Editing Utilities
bool bEditMode = true;
int nMouseX = 0;
int nMouseY = 0;
struct sCellLoc { int x, y; };
std::unordered_set<int> setSelectedCells;
//std::list<sSmokeDecal> listDecalSmoke;
//int nTrafficState = 0;
void DoEditMode(float fElapsedTime);
};

@ -1,121 +1,121 @@
#include "cCell.h"
#include "cCityMap.h"
#include "olcPixelGameEngine.h"
#include <map>
cCell::cCell()
{
}
cCell::~cCell()
{
// Cells own a list of automata navigation tracks
// but this will be destroyed when the cell is deleted
}
cCell::cCell(cCityMap* map, int x, int y)
{
pMap = map;
nWorldX = x;
nWorldY = y;
nCellType = CELL_BLANK;
// Connect internal nodes
for (int i = 0; i < 49; i++)
pNaviNodes[i] = pMap->GetAutoNodeBase(x, y) + i;
// Link cell into maps node pool
if (y > 0)
{
for (int i = 0; i < 7; i++)
pNaviNodes[i] = pMap->GetAutoNodeBase(x, y - 1) + 42 + i;
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[i] = nullptr;
}
if (x > 0)
{
// Link West side
for (int i = 0; i < 7; i++)
pNaviNodes[i * 7] = pMap->GetAutoNodeBase(x - 1, y) + 6 + i * 7;
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[i * 7] = nullptr;
}
// South Side
if (y < pMap->GetHeight() - 1)
{
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[42 + i] = nullptr;
}
// East Side
if (x < pMap->GetWidth() - 1)
{
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[6 + i * 7] = nullptr;
}
// Unused Nodes
pNaviNodes[9] = nullptr;
pNaviNodes[11] = nullptr;
pNaviNodes[15] = nullptr;
pNaviNodes[19] = nullptr;
pNaviNodes[29] = nullptr;
pNaviNodes[33] = nullptr;
pNaviNodes[37] = nullptr;
pNaviNodes[39] = nullptr;
pNaviNodes[0] = nullptr;
pNaviNodes[6] = nullptr;
pNaviNodes[42] = nullptr;
pNaviNodes[48] = nullptr;
}
bool cCell::LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
return false;
}
bool cCell::Update(float fElapsedTime)
{
return false;
}
bool cCell::DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
bool cCell::DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
bool cCell::DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
void cCell::CalculateAdjacency()
{
}
#include "cCell.h"
#include "cCityMap.h"
#include "olcPixelGameEngine.h"
#include <map>
cCell::cCell()
{
}
cCell::~cCell()
{
// Cells own a list of automata navigation tracks
// but this will be destroyed when the cell is deleted
}
cCell::cCell(cCityMap* map, int x, int y)
{
pMap = map;
nWorldX = x;
nWorldY = y;
nCellType = CELL_BLANK;
// Connect internal nodes
for (int i = 0; i < 49; i++)
pNaviNodes[i] = pMap->GetAutoNodeBase(x, y) + i;
// Link cell into maps node pool
if (y > 0)
{
for (int i = 0; i < 7; i++)
pNaviNodes[i] = pMap->GetAutoNodeBase(x, y - 1) + 42 + i;
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[i] = nullptr;
}
if (x > 0)
{
// Link West side
for (int i = 0; i < 7; i++)
pNaviNodes[i * 7] = pMap->GetAutoNodeBase(x - 1, y) + 6 + i * 7;
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[i * 7] = nullptr;
}
// South Side
if (y < pMap->GetHeight() - 1)
{
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[42 + i] = nullptr;
}
// East Side
if (x < pMap->GetWidth() - 1)
{
}
else
{
for (int i = 0; i < 7; i++)
pNaviNodes[6 + i * 7] = nullptr;
}
// Unused Nodes
pNaviNodes[9] = nullptr;
pNaviNodes[11] = nullptr;
pNaviNodes[15] = nullptr;
pNaviNodes[19] = nullptr;
pNaviNodes[29] = nullptr;
pNaviNodes[33] = nullptr;
pNaviNodes[37] = nullptr;
pNaviNodes[39] = nullptr;
pNaviNodes[0] = nullptr;
pNaviNodes[6] = nullptr;
pNaviNodes[42] = nullptr;
pNaviNodes[48] = nullptr;
}
bool cCell::LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
return false;
}
bool cCell::Update(float fElapsedTime)
{
return false;
}
bool cCell::DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
bool cCell::DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
bool cCell::DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
void cCell::CalculateAdjacency()
{
}

@ -1,60 +1,60 @@
#pragma once
#include <map>
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cAutomata.h"
class cCityMap;
enum CellType
{
CELL_BLANK,
CELL_GRASS,
CELL_CONCRETE,
CELL_WATER,
CELL_BUILDING,
CELL_ROAD,
CELL_PAVEMENT,
};
class cCell
{
public:
cCell();
cCell(cCityMap* map, int x, int y);
~cCell();
protected:
cCityMap* pMap = nullptr;
public:
int nWorldX = 0;
int nWorldY = 0;
bool bSolid = false;
CellType nCellType = CELL_BLANK;
// This cell may actuall be occupied by a multi-cell body
// so this pointer points to the host cell that contains
// that body
cCell* pHostCell = nullptr;
// Each cell links to 20 automata transport nodes, 5 on each side
cAuto_Node* pNaviNodes[49];
// Each cell can have a number of automata transport tracks, it owns them
// These connect nodes together as determined by the cell
std::list<cAuto_Track> listTracks;
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};
#pragma once
#include <map>
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cAutomata.h"
class cCityMap;
enum CellType
{
CELL_BLANK,
CELL_GRASS,
CELL_CONCRETE,
CELL_WATER,
CELL_BUILDING,
CELL_ROAD,
CELL_PAVEMENT,
};
class cCell
{
public:
cCell();
cCell(cCityMap* map, int x, int y);
~cCell();
protected:
cCityMap* pMap = nullptr;
public:
int nWorldX = 0;
int nWorldY = 0;
bool bSolid = false;
CellType nCellType = CELL_BLANK;
// This cell may actuall be occupied by a multi-cell body
// so this pointer points to the host cell that contains
// that body
cCell* pHostCell = nullptr;
// Each cell links to 20 automata transport nodes, 5 on each side
cAuto_Node* pNaviNodes[49];
// Each cell can have a number of automata transport tracks, it owns them
// These connect nodes together as determined by the cell
std::list<cAuto_Track> listTracks;
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};

@ -1,53 +1,53 @@
#include "cCell_Building.h"
cCell_Building::cCell_Building(const std::string &name, cCityMap* map, int x, int y) : cCell(map, x, y)
{
sName = name;
}
cCell_Building::~cCell_Building()
{
}
void cCell_Building::CalculateAdjacency()
{
}
bool cCell_Building::LinkAssets(std::map<std::string, olc::Sprite*>& mapTextures, std::map<std::string, olc::GFX3D::mesh*>& mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
texture = mapTextures[sName];
mesh = mapMesh[sName];
transform = mapTransforms[sName];
return false;
}
bool cCell_Building::Update(float fElapsedTime)
{
return false;
}
bool cCell_Building::DrawBase(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
olc::GFX3D::mat4x4 matTranslate = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MultiplyMatrix(transform, matTranslate);
pipe.SetTransform(matWorld);
if (texture != nullptr)
{
pipe.SetTexture(texture);
pipe.Render(mesh->tris,olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_LIGHTS);
}
else
{
pipe.Render(mesh->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_FLAT | olc::GFX3D::RENDER_LIGHTS);
}
return false;
}
bool cCell_Building::DrawAlpha(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
return false;
}
#include "cCell_Building.h"
cCell_Building::cCell_Building(const std::string &name, cCityMap* map, int x, int y) : cCell(map, x, y)
{
sName = name;
}
cCell_Building::~cCell_Building()
{
}
void cCell_Building::CalculateAdjacency()
{
}
bool cCell_Building::LinkAssets(std::map<std::string, olc::Sprite*>& mapTextures, std::map<std::string, olc::GFX3D::mesh*>& mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
texture = mapTextures[sName];
mesh = mapMesh[sName];
transform = mapTransforms[sName];
return false;
}
bool cCell_Building::Update(float fElapsedTime)
{
return false;
}
bool cCell_Building::DrawBase(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
olc::GFX3D::mat4x4 matTranslate = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MultiplyMatrix(transform, matTranslate);
pipe.SetTransform(matWorld);
if (texture != nullptr)
{
pipe.SetTexture(texture);
pipe.Render(mesh->tris,olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_LIGHTS);
}
else
{
pipe.Render(mesh->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_FLAT | olc::GFX3D::RENDER_LIGHTS);
}
return false;
}
bool cCell_Building::DrawAlpha(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
return false;
}

@ -1,25 +1,25 @@
#pragma once
#include "cCell.h"
#include "olcPGEX_Graphics3D.h"
class cCell_Building : public cCell
{
public:
cCell_Building(const std::string &name, cCityMap* map, int x, int y);
~cCell_Building();
private:
std::string sName;
olc::Sprite* texture = nullptr;
olc::GFX3D::mesh* mesh = nullptr;
olc::GFX3D::mat4x4 transform;
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};
#pragma once
#include "cCell.h"
#include "olcPGEX_Graphics3D.h"
class cCell_Building : public cCell
{
public:
cCell_Building(const std::string &name, cCityMap* map, int x, int y);
~cCell_Building();
private:
std::string sName;
olc::Sprite* texture = nullptr;
olc::GFX3D::mesh* mesh = nullptr;
olc::GFX3D::mat4x4 transform;
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};

@ -1,49 +1,49 @@
#include "cCell_Plane.h"
cCell_Plane::cCell_Plane(cCityMap* map, int x, int y, CELL_PLANE type) : cCell(map, x, y)
{
bSolid = false;
nType = type;
if (nType == PLANE_GRASS) nCellType = CELL_GRASS;
if (nType == PLANE_ASPHALT) nCellType = CELL_PAVEMENT;
}
cCell_Plane::~cCell_Plane()
{
}
bool cCell_Plane::LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
sprGrass = mapTextures["Grass"];
sprPavement = mapTextures["Pavement"];
meshUnitQuad = mapMesh["UnitQuad"];
return true;
}
bool cCell_Plane::Update(float fElapsedTime)
{
return false;
}
bool cCell_Plane::DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
olc::GFX3D::mat4x4 matWorld;
matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
pipe.SetTransform(matWorld);
if(nType == PLANE_GRASS)
pipe.SetTexture(sprGrass);
else
pipe.SetTexture(sprPavement);
pipe.Render(meshUnitQuad->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED);
return false;
}
bool cCell_Plane::DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}
#include "cCell_Plane.h"
cCell_Plane::cCell_Plane(cCityMap* map, int x, int y, CELL_PLANE type) : cCell(map, x, y)
{
bSolid = false;
nType = type;
if (nType == PLANE_GRASS) nCellType = CELL_GRASS;
if (nType == PLANE_ASPHALT) nCellType = CELL_PAVEMENT;
}
cCell_Plane::~cCell_Plane()
{
}
bool cCell_Plane::LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
sprGrass = mapTextures["Grass"];
sprPavement = mapTextures["Pavement"];
meshUnitQuad = mapMesh["UnitQuad"];
return true;
}
bool cCell_Plane::Update(float fElapsedTime)
{
return false;
}
bool cCell_Plane::DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
olc::GFX3D::mat4x4 matWorld;
matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
pipe.SetTransform(matWorld);
if(nType == PLANE_GRASS)
pipe.SetTexture(sprGrass);
else
pipe.SetTexture(sprPavement);
pipe.Render(meshUnitQuad->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED);
return false;
}
bool cCell_Plane::DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe)
{
return false;
}

@ -1,34 +1,34 @@
#pragma once
#include "cCell.h"
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include <map>
enum CELL_PLANE
{
PLANE_GRASS,
PLANE_ASPHALT
};
class cCell_Plane : public cCell
{
public:
cCell_Plane(cCityMap* map, int x, int y, CELL_PLANE type);
~cCell_Plane();
protected:
CELL_PLANE nType = PLANE_GRASS;
private:
olc::GFX3D::mesh* meshUnitQuad = nullptr;
olc::Sprite* sprGrass = nullptr;
olc::Sprite* sprPavement = nullptr;
public:
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};
#pragma once
#include "cCell.h"
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include <map>
enum CELL_PLANE
{
PLANE_GRASS,
PLANE_ASPHALT
};
class cCell_Plane : public cCell
{
public:
cCell_Plane(cCityMap* map, int x, int y, CELL_PLANE type);
~cCell_Plane();
protected:
CELL_PLANE nType = PLANE_GRASS;
private:
olc::GFX3D::mesh* meshUnitQuad = nullptr;
olc::Sprite* sprGrass = nullptr;
olc::Sprite* sprPavement = nullptr;
public:
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};

@ -1,54 +1,54 @@
#pragma once
#include "cCell.h"
enum RoadType
{
ROAD_H,
ROAD_V,
ROAD_C1,
ROAD_C2,
ROAD_C3,
ROAD_C4,
ROAD_T1,
ROAD_T2,
ROAD_T3,
ROAD_T4,
ROAD_X,
};
class cCell_Road : public cCell
{
public:
cCell_Road(cCityMap* map, int x, int y);
~cCell_Road();
private:
struct StopPattern
{
bool bStop[49];
};
private:
bool bNeighboursAreRoads[4];
olc::GFX3D::mesh *meshUnitQuad = nullptr;
olc::Sprite* sprRoadTex[11];
std::vector<StopPattern> vStopPattern;
int nCurrentStopPattern = 0;
float fStopPatternTimer = 0.0f;
public:
RoadType nRoadType = ROAD_X;
cAuto_Track* pSafeCarTrack = nullptr;
cAuto_Track* pSafePedestrianTrack = nullptr;
cAuto_Track* pSafeChaseTrack = nullptr;
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};
#pragma once
#include "cCell.h"
enum RoadType
{
ROAD_H,
ROAD_V,
ROAD_C1,
ROAD_C2,
ROAD_C3,
ROAD_C4,
ROAD_T1,
ROAD_T2,
ROAD_T3,
ROAD_T4,
ROAD_X,
};
class cCell_Road : public cCell
{
public:
cCell_Road(cCityMap* map, int x, int y);
~cCell_Road();
private:
struct StopPattern
{
bool bStop[49];
};
private:
bool bNeighboursAreRoads[4];
olc::GFX3D::mesh *meshUnitQuad = nullptr;
olc::Sprite* sprRoadTex[11];
std::vector<StopPattern> vStopPattern;
int nCurrentStopPattern = 0;
float fStopPatternTimer = 0.0f;
public:
RoadType nRoadType = ROAD_X;
cAuto_Track* pSafeCarTrack = nullptr;
cAuto_Track* pSafePedestrianTrack = nullptr;
cAuto_Track* pSafeChaseTrack = nullptr;
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawDebug(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};

@ -1,91 +1,91 @@
#include "cCell_Water.h"
#include "cCityMap.h"
cCell_Water::cCell_Water(cCityMap* map, int x, int y) : cCell(map, x, y)
{
nCellType = CELL_WATER;
bNeighboursAreWater[0] = false;
bNeighboursAreWater[1] = false;
bNeighboursAreWater[2] = false;
bNeighboursAreWater[3] = false;
}
cCell_Water::~cCell_Water()
{
}
bool cCell_Water::LinkAssets(std::map<std::string, olc::Sprite*>& mapTextures, std::map<std::string, olc::GFX3D::mesh*>& mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
meshUnitQuad = mapMesh["UnitQuad"];
meshWalls = mapMesh["WallsOut"];
sprWater = mapTextures["Water"];
sprSides = mapTextures["WaterSide"];
sprClouds = mapTextures["Clouds"];
return false;
}
bool cCell_Water::Update(float fElapsedTime)
{
return false;
}
bool cCell_Water::DrawBase(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
olc::GFX3D::mat4x4 matWorld;
matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
pipe.SetTransform(matWorld);
pipe.SetTexture(sprSides);
if (!bNeighboursAreWater[1]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 0, 2);
if (!bNeighboursAreWater[3]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 2, 2);
if (!bNeighboursAreWater[2]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 4, 2);
if (!bNeighboursAreWater[0]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 6, 2);
return false;
}
bool cCell_Water::DrawAlpha(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
auto renderWater = [&](const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest)
{
float a = (float)(pSource.a / 255.0f) * 0.6f;
float c = 1.0f - a;
float r = a * (float)pSource.r + c * (float)pDest.r;
float g = a * (float)pSource.g + c * (float)pDest.g;
float b = a * (float)pSource.b + c * (float)pDest.b;
a = 0.4f;
c = 1.0f - a;
olc::Pixel sky = sprClouds->GetPixel(x, y);
float sr = a * (float)sky.r + c * r;
float sg = a * (float)sky.g + c * g;
float sb = a * (float)sky.b + c * b;
return olc::Pixel((uint8_t)sr, (uint8_t)sg, (uint8_t)sb);
};
pge->SetPixelMode(renderWater);
olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.07f);
pipe.SetTransform(matWorld);
pipe.SetTexture(sprWater);
pipe.Render(meshUnitQuad->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED);
pge->SetPixelMode(olc::Pixel::NORMAL);
return false;
}
void cCell_Water::CalculateAdjacency()
{
auto r = [&](int i, int j)
{
if (pMap->Cell(nWorldX + i, nWorldY + j) != nullptr)
return pMap->Cell(nWorldX + i, nWorldY + j)->nCellType == CELL_WATER;
else
return false;
};
bNeighboursAreWater[0] = r(0, -1);
bNeighboursAreWater[1] = r(+1, 0);
bNeighboursAreWater[2] = r(0, +1);
bNeighboursAreWater[3] = r(-1, 0);
#include "cCell_Water.h"
#include "cCityMap.h"
cCell_Water::cCell_Water(cCityMap* map, int x, int y) : cCell(map, x, y)
{
nCellType = CELL_WATER;
bNeighboursAreWater[0] = false;
bNeighboursAreWater[1] = false;
bNeighboursAreWater[2] = false;
bNeighboursAreWater[3] = false;
}
cCell_Water::~cCell_Water()
{
}
bool cCell_Water::LinkAssets(std::map<std::string, olc::Sprite*>& mapTextures, std::map<std::string, olc::GFX3D::mesh*>& mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
meshUnitQuad = mapMesh["UnitQuad"];
meshWalls = mapMesh["WallsOut"];
sprWater = mapTextures["Water"];
sprSides = mapTextures["WaterSide"];
sprClouds = mapTextures["Clouds"];
return false;
}
bool cCell_Water::Update(float fElapsedTime)
{
return false;
}
bool cCell_Water::DrawBase(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
olc::GFX3D::mat4x4 matWorld;
matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.0f);
pipe.SetTransform(matWorld);
pipe.SetTexture(sprSides);
if (!bNeighboursAreWater[1]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 0, 2);
if (!bNeighboursAreWater[3]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 2, 2);
if (!bNeighboursAreWater[2]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 4, 2);
if (!bNeighboursAreWater[0]) pipe.Render(meshWalls->tris, olc::GFX3D::RENDER_LIGHTS | olc::GFX3D::RENDER_CULL_CCW | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_DEPTH, 6, 2);
return false;
}
bool cCell_Water::DrawAlpha(olc::PixelGameEngine * pge, olc::GFX3D::PipeLine & pipe)
{
auto renderWater = [&](const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest)
{
float a = (float)(pSource.a / 255.0f) * 0.6f;
float c = 1.0f - a;
float r = a * (float)pSource.r + c * (float)pDest.r;
float g = a * (float)pSource.g + c * (float)pDest.g;
float b = a * (float)pSource.b + c * (float)pDest.b;
a = 0.4f;
c = 1.0f - a;
olc::Pixel sky = sprClouds->GetPixel(x, y);
float sr = a * (float)sky.r + c * r;
float sg = a * (float)sky.g + c * g;
float sb = a * (float)sky.b + c * b;
return olc::Pixel((uint8_t)sr, (uint8_t)sg, (uint8_t)sb);
};
pge->SetPixelMode(renderWater);
olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)nWorldX, (float)nWorldY, 0.07f);
pipe.SetTransform(matWorld);
pipe.SetTexture(sprWater);
pipe.Render(meshUnitQuad->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED);
pge->SetPixelMode(olc::Pixel::NORMAL);
return false;
}
void cCell_Water::CalculateAdjacency()
{
auto r = [&](int i, int j)
{
if (pMap->Cell(nWorldX + i, nWorldY + j) != nullptr)
return pMap->Cell(nWorldX + i, nWorldY + j)->nCellType == CELL_WATER;
else
return false;
};
bNeighboursAreWater[0] = r(0, -1);
bNeighboursAreWater[1] = r(+1, 0);
bNeighboursAreWater[2] = r(0, +1);
bNeighboursAreWater[3] = r(-1, 0);
}

@ -1,25 +1,25 @@
#pragma once
#include "cCell.h"
class cCell_Water : public cCell
{
public:
cCell_Water(cCityMap* map, int x, int y);
~cCell_Water();
private:
olc::GFX3D::mesh* meshUnitQuad = nullptr;
olc::GFX3D::mesh* meshWalls = nullptr;
olc::Sprite* sprWater = nullptr;
olc::Sprite* sprSides = nullptr;
olc::Sprite* sprClouds = nullptr;
bool bNeighboursAreWater[4];
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};
#pragma once
#include "cCell.h"
class cCell_Water : public cCell
{
public:
cCell_Water(cCityMap* map, int x, int y);
~cCell_Water();
private:
olc::GFX3D::mesh* meshUnitQuad = nullptr;
olc::GFX3D::mesh* meshWalls = nullptr;
olc::Sprite* sprWater = nullptr;
olc::Sprite* sprSides = nullptr;
olc::Sprite* sprClouds = nullptr;
bool bNeighboursAreWater[4];
public:
virtual void CalculateAdjacency();
virtual bool LinkAssets(std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
virtual bool Update(float fElapsedTime);
virtual bool DrawBase(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
virtual bool DrawAlpha(olc::PixelGameEngine *pge, olc::GFX3D::PipeLine &pipe);
};

@ -1,202 +1,202 @@
#include "cCityMap.h"
#include <fstream>
cCityMap::cCityMap(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
CreateCity(w, h, mapTextures, mapMesh, mapTransforms);
}
cCityMap::~cCityMap()
{
ReleaseCity();
}
int cCityMap::GetWidth()
{
return nWidth;
}
int cCityMap::GetHeight()
{
return nHeight;
}
cCell* cCityMap::Cell(int x, int y)
{
if (x >= 0 && x < nWidth && y >= 0 && y < nHeight)
return pCells[y*nWidth + x];
else
return nullptr;
}
cCell* cCityMap::Replace(int x, int y, cCell* cell)
{
if (cell == nullptr)
return nullptr;
if (pCells[y * nWidth + x] != nullptr)
delete pCells[y * nWidth + x];
pCells[y * nWidth + x] = cell;
return cell;
}
void cCityMap::CreateCity(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
ReleaseCity();
nWidth = w;
nHeight = h;
pCells = new cCell*[nHeight * nWidth];
// Create Navigation Node Pool, assumes 5 nodes on east and south
// side of each cell. The City owns these nodes, and cells in the
// city borrow them and link to them as required
pNodes = new cAuto_Node[nHeight * nWidth * 49];
// The cell has 49 nodes, though some are simply unused. This is less memory
// efficient certainly, but makes code more intuitive and easier to write
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Nodes sit between cells, therefore each create nodes along
// the east and southern sides of the cell. This assumes that
// navigation along the top and left boundaries of the map
// will not occur. And it shouldnt, as its water
int idx = (y * nWidth + x) * 49;
for (int dx = 0; dx < 7; dx++)
{
float off_x = 0.0f;
switch (dx)
{
case 0: off_x = 0.000f; break;
case 1: off_x = 0.083f; break;
case 2: off_x = 0.333f; break;
case 3: off_x = 0.500f; break;
case 4: off_x = 0.667f; break;
case 5: off_x = 0.917f; break;
case 6: off_x = 1.000f; break;
}
for (int dy = 0; dy < 7; dy++)
{
float off_y = 0.0f;
switch (dy)
{
case 0: off_y = 0.000f; break;
case 1: off_y = 0.083f; break;
case 2: off_y = 0.333f; break;
case 3: off_y = 0.500f; break;
case 4: off_y = 0.667f; break;
case 5: off_y = 0.917f; break;
case 6: off_y = 1.000f; break;
}
pNodes[idx + dy * 7 + dx].pos = { (float)x + off_x, (float)y + off_y };
pNodes[idx + dy * 7 + dx].bBlock = false;
}
}
}
}
// Now create default Cell
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Default city, everything is grass
pCells[y * nWidth + x] = new cCell_Plane(this, x, y, PLANE_GRASS);
// Give the cell the opportunity to locally reference the resources it needs
pCells[y * nWidth + x]->LinkAssets(mapTextures, mapMesh, mapTransforms);
}
}
}
cAuto_Node* cCityMap::GetAutoNodeBase(int x, int y)
{
return pNodes + (y * nWidth + x) * 49;
}
void cCityMap::RemoveAllTracks()
{
for (int i = 0; i < nWidth * nHeight * 49; i++)
{
pNodes[i].listTracks.clear();
}
}
void cCityMap::ReleaseCity()
{
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Erase any tracks attached to nodes
for(int i=0; i<49; i++)
Cell(x, y)->pNaviNodes[i]->listTracks.clear();
// Release individual cell objects
delete pCells[y * nWidth + x];
}
}
// Release array of cell pointers
if (pCells != nullptr) delete pCells;
// Release array of automata navigation nodes
if (pNodes != nullptr) delete pNodes;
nWidth = 0;
nHeight = 0;
}
bool cCityMap::SaveCity(std::string sFilename)
{
/*std::ofstream file(sFilename, std::ios::out | std::ios::binary);
if (!file.is_open()) return false;
file.write((char*)&m_nWidth, sizeof(int));
file.write((char*)&m_nHeight, sizeof(int));
for (int x = 0; x < m_nWidth; x++)
{
for (int y = 0; y < m_nHeight; y++)
{
file.write((char*)Cell(x, y), sizeof(cCityCell));
}
}*/
return true;
}
bool cCityMap::LoadCity(std::string sFilename)
{
/*std::ifstream file(sFilename, std::ios::in | std::ios::binary);
if (!file.is_open()) return false;
int w, h;
file.read((char*)&w, sizeof(int));
file.read((char*)&h, sizeof(int));
CreateCity(w, h);
for (int x = 0; x < m_nWidth; x++)
{
for (int y = 0; y < m_nHeight; y++)
{
file.read((char*)Cell(x, y), sizeof(cCityCell));
}
}*/
return true;
#include "cCityMap.h"
#include <fstream>
cCityMap::cCityMap(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
CreateCity(w, h, mapTextures, mapMesh, mapTransforms);
}
cCityMap::~cCityMap()
{
ReleaseCity();
}
int cCityMap::GetWidth()
{
return nWidth;
}
int cCityMap::GetHeight()
{
return nHeight;
}
cCell* cCityMap::Cell(int x, int y)
{
if (x >= 0 && x < nWidth && y >= 0 && y < nHeight)
return pCells[y*nWidth + x];
else
return nullptr;
}
cCell* cCityMap::Replace(int x, int y, cCell* cell)
{
if (cell == nullptr)
return nullptr;
if (pCells[y * nWidth + x] != nullptr)
delete pCells[y * nWidth + x];
pCells[y * nWidth + x] = cell;
return cell;
}
void cCityMap::CreateCity(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms)
{
ReleaseCity();
nWidth = w;
nHeight = h;
pCells = new cCell*[nHeight * nWidth];
// Create Navigation Node Pool, assumes 5 nodes on east and south
// side of each cell. The City owns these nodes, and cells in the
// city borrow them and link to them as required
pNodes = new cAuto_Node[nHeight * nWidth * 49];
// The cell has 49 nodes, though some are simply unused. This is less memory
// efficient certainly, but makes code more intuitive and easier to write
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Nodes sit between cells, therefore each create nodes along
// the east and southern sides of the cell. This assumes that
// navigation along the top and left boundaries of the map
// will not occur. And it shouldnt, as its water
int idx = (y * nWidth + x) * 49;
for (int dx = 0; dx < 7; dx++)
{
float off_x = 0.0f;
switch (dx)
{
case 0: off_x = 0.000f; break;
case 1: off_x = 0.083f; break;
case 2: off_x = 0.333f; break;
case 3: off_x = 0.500f; break;
case 4: off_x = 0.667f; break;
case 5: off_x = 0.917f; break;
case 6: off_x = 1.000f; break;
}
for (int dy = 0; dy < 7; dy++)
{
float off_y = 0.0f;
switch (dy)
{
case 0: off_y = 0.000f; break;
case 1: off_y = 0.083f; break;
case 2: off_y = 0.333f; break;
case 3: off_y = 0.500f; break;
case 4: off_y = 0.667f; break;
case 5: off_y = 0.917f; break;
case 6: off_y = 1.000f; break;
}
pNodes[idx + dy * 7 + dx].pos = { (float)x + off_x, (float)y + off_y };
pNodes[idx + dy * 7 + dx].bBlock = false;
}
}
}
}
// Now create default Cell
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Default city, everything is grass
pCells[y * nWidth + x] = new cCell_Plane(this, x, y, PLANE_GRASS);
// Give the cell the opportunity to locally reference the resources it needs
pCells[y * nWidth + x]->LinkAssets(mapTextures, mapMesh, mapTransforms);
}
}
}
cAuto_Node* cCityMap::GetAutoNodeBase(int x, int y)
{
return pNodes + (y * nWidth + x) * 49;
}
void cCityMap::RemoveAllTracks()
{
for (int i = 0; i < nWidth * nHeight * 49; i++)
{
pNodes[i].listTracks.clear();
}
}
void cCityMap::ReleaseCity()
{
for (int x = 0; x < nWidth; x++)
{
for (int y = 0; y < nHeight; y++)
{
// Erase any tracks attached to nodes
for(int i=0; i<49; i++)
Cell(x, y)->pNaviNodes[i]->listTracks.clear();
// Release individual cell objects
delete pCells[y * nWidth + x];
}
}
// Release array of cell pointers
if (pCells != nullptr) delete pCells;
// Release array of automata navigation nodes
if (pNodes != nullptr) delete pNodes;
nWidth = 0;
nHeight = 0;
}
bool cCityMap::SaveCity(std::string sFilename)
{
/*std::ofstream file(sFilename, std::ios::out | std::ios::binary);
if (!file.is_open()) return false;
file.write((char*)&m_nWidth, sizeof(int));
file.write((char*)&m_nHeight, sizeof(int));
for (int x = 0; x < m_nWidth; x++)
{
for (int y = 0; y < m_nHeight; y++)
{
file.write((char*)Cell(x, y), sizeof(cCityCell));
}
}*/
return true;
}
bool cCityMap::LoadCity(std::string sFilename)
{
/*std::ifstream file(sFilename, std::ios::in | std::ios::binary);
if (!file.is_open()) return false;
int w, h;
file.read((char*)&w, sizeof(int));
file.read((char*)&h, sizeof(int));
CreateCity(w, h);
for (int x = 0; x < m_nWidth; x++)
{
for (int y = 0; y < m_nHeight; y++)
{
file.read((char*)Cell(x, y), sizeof(cCityCell));
}
}*/
return true;
}

@ -1,63 +1,63 @@
#pragma once
#include <string>
#include <map>
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cCell.h"
#include "cCell_Plane.h"
#include "cCell_Water.h"
#include "cCell_Road.h"
#include "cCell_Building.h"
/*
This class holds the definition of a map. The map data is actually
stored within this clap, as well as accessors to access the individual
map cells
*/
class cCityMap
{
public:
// Construct a "blank" city w units wide by h units high
cCityMap(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
// Cleans up city, like Batman
~cCityMap();
public:
// Save the current city to a file, this will overwrite an existing
// city file without warning. Returns true if successful
bool SaveCity(std::string sFilename);
// Load a city from file and replace current city with it, retuns
// true if successful
bool LoadCity(std::string sFilename);
public:
// Return width of city in cells
int GetWidth();
// Return height of city in cells
int GetHeight();
// Return a specific cell reference if inside city limits, or nullptr
cCell* Cell(int x, int y);
// Replace a specific cell
cCell* Replace(int x, int y, cCell* cell);
cAuto_Node* GetAutoNodeBase(int x, int y);
void RemoveAllTracks();
private:
int nWidth = 0;
int nHeight = 0;
cCell **pCells = nullptr;
cAuto_Node *pNodes = nullptr;
private:
// Creates a "default" city of specified size
void CreateCity(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
// Destroy city
void ReleaseCity();
};
#pragma once
#include <string>
#include <map>
#include "olcPixelGameEngine.h"
#include "olcPGEX_Graphics3D.h"
#include "cCell.h"
#include "cCell_Plane.h"
#include "cCell_Water.h"
#include "cCell_Road.h"
#include "cCell_Building.h"
/*
This class holds the definition of a map. The map data is actually
stored within this clap, as well as accessors to access the individual
map cells
*/
class cCityMap
{
public:
// Construct a "blank" city w units wide by h units high
cCityMap(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
// Cleans up city, like Batman
~cCityMap();
public:
// Save the current city to a file, this will overwrite an existing
// city file without warning. Returns true if successful
bool SaveCity(std::string sFilename);
// Load a city from file and replace current city with it, retuns
// true if successful
bool LoadCity(std::string sFilename);
public:
// Return width of city in cells
int GetWidth();
// Return height of city in cells
int GetHeight();
// Return a specific cell reference if inside city limits, or nullptr
cCell* Cell(int x, int y);
// Replace a specific cell
cCell* Replace(int x, int y, cCell* cell);
cAuto_Node* GetAutoNodeBase(int x, int y);
void RemoveAllTracks();
private:
int nWidth = 0;
int nHeight = 0;
cCell **pCells = nullptr;
cAuto_Node *pNodes = nullptr;
private:
// Creates a "default" city of specified size
void CreateCity(int w, int h, std::map<std::string, olc::Sprite*> &mapTextures, std::map<std::string, olc::GFX3D::mesh*> &mapMesh, std::map<std::string, olc::GFX3D::mat4x4> &mapTransforms);
// Destroy city
void ReleaseCity();
};

@ -1,156 +1,156 @@
#include "cGameSettings.h"
cGameSettings::cGameSettings()
{
}
cGameSettings::~cGameSettings()
{
}
bool cGameSettings::LoadConfigFile(std::string sFile)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Load game settings file
int r = luaL_loadfile(L, sFile.c_str());
if (r != LUA_OK)
{
std::string errormsg = lua_tostring(L, -1);
std::cout << errormsg << std::endl;
return false;
}
// Execute it
int i = lua_pcall(L, 0, LUA_MULTRET, 0);
if (i != LUA_OK)
{
std::string errormsg = lua_tostring(L, -1);
std::cout << errormsg << std::endl;
return false;
}
lua_getglobal(L, "PixelWidth");
if (lua_isinteger(L, -1)) cGameSettings::nPixelWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "PixelHeight");
if (lua_isinteger(L, -1)) cGameSettings::nPixelHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "ScreenWidth");
if (lua_isinteger(L, -1)) cGameSettings::nScreenWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "ScreenHeight");
if (lua_isinteger(L, -1)) cGameSettings::nScreenHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultMapWidth");
if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultMapHeight");
if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultCityFile");
if (lua_isstring(L, -1)) cGameSettings::sDefaultCityFile = lua_tostring(L, -1);
lua_getglobal(L, "FullScreen");
if (lua_isboolean(L, -1)) cGameSettings::bFullScreen = lua_toboolean(L, -1);
//// Load System Texture files
// Load Texture Assets
lua_getglobal(L, "Textures"); // -1 Table "Teams"
if (lua_istable(L, -1))
{
lua_pushnil(L); // -2 Key Nil : -1 Table "Teams"
while (lua_next(L, -2) != 0) // -1 Table : -2 Key "TeamName" : -3 Table "Teams"
{
sAssetTexture texture;
int stage = 0;
if (lua_istable(L, -1))
{
lua_gettable(L, -1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams"
lua_pushnil(L); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams"
while (lua_next(L, -2) != 0) // -1 Value "BotFile" : -2 Key Nil : -3 Table : -4 Table Value : -5 Key "TeamName" : -6 Table "Teams"
{
if (stage == 0) texture.sName = lua_tostring(L, -1);
if (stage == 1) texture.sFile = lua_tostring(L, -1);
lua_pop(L, 1); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams"
stage++;
}
}
lua_pop(L, 1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams"
vecAssetTextures.push_back(texture);
}
}
auto GroupLoadAssets = [L](const std::string &group, std::vector<sAssetModel> &vec)
{
lua_getglobal(L, group.c_str());
if (lua_istable(L, -1))
{
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
sAssetModel model;
int stage = 0;
if (lua_istable(L, -1))
{
lua_gettable(L, -1);
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (stage == 0) model.sCreator = lua_tostring(L, -1);
if (stage == 1) model.sDescription = lua_tostring(L, -1);
if (stage == 2) model.sModelOBJ = lua_tostring(L, -1);
if (stage == 3) model.sModelPNG = lua_tostring(L, -1);
if (stage == 4) model.fRotate[0] = (float)lua_tonumber(L, -1);
if (stage == 5) model.fRotate[1] = (float)lua_tonumber(L, -1);
if (stage == 6) model.fRotate[2] = (float)lua_tonumber(L, -1);
if (stage == 7) model.fScale[0] = (float)lua_tonumber(L, -1);
if (stage == 8) model.fScale[1] = (float)lua_tonumber(L, -1);
if (stage == 9) model.fScale[2] = (float)lua_tonumber(L, -1);
if (stage == 10) model.fTranslate[0] = (float)lua_tonumber(L, -1);
if (stage == 11) model.fTranslate[1] = (float)lua_tonumber(L, -1);
if (stage == 12) model.fTranslate[2] = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
stage++;
}
}
lua_pop(L, 1);
vec.push_back(model);
}
}
};
// Load Building Assets
GroupLoadAssets("Buildings", vecAssetBuildings);
// Load Vehicle Assets
GroupLoadAssets("Vehicles", vecAssetVehicles);
lua_close(L);
return true;
}
int cGameSettings::nScreenWidth = 768;
int cGameSettings::nScreenHeight = 480;
int cGameSettings::nPixelWidth = 2;
int cGameSettings::nPixelHeight = 2;
bool cGameSettings::bFullScreen = false;
int cGameSettings::nDefaultMapWidth = 64;
int cGameSettings::nDefaultMapHeight = 32;
std::string cGameSettings::sDefaultCityFile = "";
std::vector<sAssetTexture> cGameSettings::vecAssetTextures;
std::vector<sAssetModel> cGameSettings::vecAssetBuildings;
#include "cGameSettings.h"
cGameSettings::cGameSettings()
{
}
cGameSettings::~cGameSettings()
{
}
bool cGameSettings::LoadConfigFile(std::string sFile)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Load game settings file
int r = luaL_loadfile(L, sFile.c_str());
if (r != LUA_OK)
{
std::string errormsg = lua_tostring(L, -1);
std::cout << errormsg << std::endl;
return false;
}
// Execute it
int i = lua_pcall(L, 0, LUA_MULTRET, 0);
if (i != LUA_OK)
{
std::string errormsg = lua_tostring(L, -1);
std::cout << errormsg << std::endl;
return false;
}
lua_getglobal(L, "PixelWidth");
if (lua_isinteger(L, -1)) cGameSettings::nPixelWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "PixelHeight");
if (lua_isinteger(L, -1)) cGameSettings::nPixelHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "ScreenWidth");
if (lua_isinteger(L, -1)) cGameSettings::nScreenWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "ScreenHeight");
if (lua_isinteger(L, -1)) cGameSettings::nScreenHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultMapWidth");
if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapWidth = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultMapHeight");
if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapHeight = (int)lua_tointeger(L, -1);
lua_getglobal(L, "DefaultCityFile");
if (lua_isstring(L, -1)) cGameSettings::sDefaultCityFile = lua_tostring(L, -1);
lua_getglobal(L, "FullScreen");
if (lua_isboolean(L, -1)) cGameSettings::bFullScreen = lua_toboolean(L, -1);
//// Load System Texture files
// Load Texture Assets
lua_getglobal(L, "Textures"); // -1 Table "Teams"
if (lua_istable(L, -1))
{
lua_pushnil(L); // -2 Key Nil : -1 Table "Teams"
while (lua_next(L, -2) != 0) // -1 Table : -2 Key "TeamName" : -3 Table "Teams"
{
sAssetTexture texture;
int stage = 0;
if (lua_istable(L, -1))
{
lua_gettable(L, -1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams"
lua_pushnil(L); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams"
while (lua_next(L, -2) != 0) // -1 Value "BotFile" : -2 Key Nil : -3 Table : -4 Table Value : -5 Key "TeamName" : -6 Table "Teams"
{
if (stage == 0) texture.sName = lua_tostring(L, -1);
if (stage == 1) texture.sFile = lua_tostring(L, -1);
lua_pop(L, 1); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams"
stage++;
}
}
lua_pop(L, 1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams"
vecAssetTextures.push_back(texture);
}
}
auto GroupLoadAssets = [L](const std::string &group, std::vector<sAssetModel> &vec)
{
lua_getglobal(L, group.c_str());
if (lua_istable(L, -1))
{
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
sAssetModel model;
int stage = 0;
if (lua_istable(L, -1))
{
lua_gettable(L, -1);
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (stage == 0) model.sCreator = lua_tostring(L, -1);
if (stage == 1) model.sDescription = lua_tostring(L, -1);
if (stage == 2) model.sModelOBJ = lua_tostring(L, -1);
if (stage == 3) model.sModelPNG = lua_tostring(L, -1);
if (stage == 4) model.fRotate[0] = (float)lua_tonumber(L, -1);
if (stage == 5) model.fRotate[1] = (float)lua_tonumber(L, -1);
if (stage == 6) model.fRotate[2] = (float)lua_tonumber(L, -1);
if (stage == 7) model.fScale[0] = (float)lua_tonumber(L, -1);
if (stage == 8) model.fScale[1] = (float)lua_tonumber(L, -1);
if (stage == 9) model.fScale[2] = (float)lua_tonumber(L, -1);
if (stage == 10) model.fTranslate[0] = (float)lua_tonumber(L, -1);
if (stage == 11) model.fTranslate[1] = (float)lua_tonumber(L, -1);
if (stage == 12) model.fTranslate[2] = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
stage++;
}
}
lua_pop(L, 1);
vec.push_back(model);
}
}
};
// Load Building Assets
GroupLoadAssets("Buildings", vecAssetBuildings);
// Load Vehicle Assets
GroupLoadAssets("Vehicles", vecAssetVehicles);
lua_close(L);
return true;
}
int cGameSettings::nScreenWidth = 768;
int cGameSettings::nScreenHeight = 480;
int cGameSettings::nPixelWidth = 2;
int cGameSettings::nPixelHeight = 2;
bool cGameSettings::bFullScreen = false;
int cGameSettings::nDefaultMapWidth = 64;
int cGameSettings::nDefaultMapHeight = 32;
std::string cGameSettings::sDefaultCityFile = "";
std::vector<sAssetTexture> cGameSettings::vecAssetTextures;
std::vector<sAssetModel> cGameSettings::vecAssetBuildings;
std::vector<sAssetModel> cGameSettings::vecAssetVehicles;

@ -1,65 +1,65 @@
#pragma once
#include <iostream>
#include <string>
#include <vector>
extern "C"
{
#include "lua533/include/lua.h"
#include "lua533/include/lauxlib.h"
#include "lua533/include/lualib.h"
}
#ifdef _WIN32
#pragma comment(lib, "lua533/liblua53.a")
#endif
/*
This is a singleton that stores all the games configuration settings.
These settings are loaded on game start up and are to be considered
read-only.
*/
struct sAssetModel
{
std::string sCreator;
std::string sDescription;
std::string sModelOBJ;
std::string sModelPNG;
float fRotate[3];
float fScale[3];
float fTranslate[3];
};
struct sAssetTexture
{
std::string sName;
std::string sFile;
};
class cGameSettings
{
public:
cGameSettings();
~cGameSettings();
public:
bool LoadConfigFile(std::string sFile);
public:
static int nScreenWidth;
static int nScreenHeight;
static int nPixelWidth;
static int nPixelHeight;
static bool bFullScreen;
static int nDefaultMapWidth;
static int nDefaultMapHeight;
static std::string sDefaultCityFile;
static std::vector<sAssetTexture> vecAssetTextures;
static std::vector<sAssetModel> vecAssetBuildings;
static std::vector<sAssetModel> vecAssetVehicles;
};
#pragma once
#include <iostream>
#include <string>
#include <vector>
extern "C"
{
#include "lua533/include/lua.h"
#include "lua533/include/lauxlib.h"
#include "lua533/include/lualib.h"
}
#ifdef _WIN32
#pragma comment(lib, "lua533/liblua53.a")
#endif
/*
This is a singleton that stores all the games configuration settings.
These settings are loaded on game start up and are to be considered
read-only.
*/
struct sAssetModel
{
std::string sCreator;
std::string sDescription;
std::string sModelOBJ;
std::string sModelPNG;
float fRotate[3];
float fScale[3];
float fTranslate[3];
};
struct sAssetTexture
{
std::string sName;
std::string sFile;
};
class cGameSettings
{
public:
cGameSettings();
~cGameSettings();
public:
bool LoadConfigFile(std::string sFile);
public:
static int nScreenWidth;
static int nScreenHeight;
static int nPixelWidth;
static int nPixelHeight;
static bool bFullScreen;
static int nDefaultMapWidth;
static int nDefaultMapHeight;
static std::string sDefaultCityFile;
static std::vector<sAssetTexture> vecAssetTextures;
static std::vector<sAssetModel> vecAssetBuildings;
static std::vector<sAssetModel> vecAssetVehicles;
};

@ -1,367 +1,367 @@
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#include "cGameSettings.h"
#include "cCarCrimeCity.h"
int main()
{
// Load the settings singleton
cGameSettings config;
if (!config.LoadConfigFile("assets/config.lua"))
{
std::cout << "Failed to load '/assets/config.lua'" << std::endl;
std::cout << " -> Using default configuration" << std::endl;
}
// Start the PixelGameEngine
cCarCrimeCity game;
if (game.Construct(config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight, config.bFullScreen))
game.Start();
// Exit!
return 0;
}
//#define OLC_PGE_APPLICATION
//#include "olcPixelGameEngine.h"
//
//#define OLC_PGEX_GRAPHICS3D
//#include "olcPGEX_Graphics3D.h"
//
//
//
//enum CELLTYPE
//{
// CELL_BLANK = 0,
// CELL_GRASS = 1,
// CELL_CONCRETE = 2,
// CELL_WATER = 3,
// CELL_BUILDING = 4,
// CELL_ROAD_H = 5,
// CELL_ROAD_V = 6,
// CELL_ROAD_C1 = 7,
// CELL_ROAD_C2 = 8,
// CELL_ROAD_C3 = 9,
// CELL_ROAD_C4 = 10,
// CELL_ROAD_T1 = 11,
// CELL_ROAD_T2 = 12,
// CELL_ROAD_T3 = 13,
// CELL_ROAD_T4 = 14,
// CELL_ROAD_X = 15,
//};
//
//struct cCityCell
//{
// int nType = 5;// CELL_GRASS;
//};
//
//class cCityMap
//{
//public:
// // Construct a "blank" city w units wide by h units high
// cCityMap(int w, int h);
//
// // Cleans up city, like Batman
// ~cCityMap();
//
//
//public:
// // Return width of city in cells
// int GetWidth();
// // Return height of city in cells
// int GetHeight();
// // Return a specific cell reference if inside city limits, or nullptr
// cCityCell* Cell(int x, int y);
//
//private:
// int m_nWidth = 0;
// int m_nHeight = 0;
// cCityCell *m_pCells = nullptr;
//
//private:
// // Creates a "default" city of specified size
// void CreateCity(int w, int h);
// // Destroy city
// void ReleaseCity();
//};
//
//cCityMap::cCityMap(int w, int h)
//{
// CreateCity(w, h);
//}
//
//cCityMap::~cCityMap()
//{
// //ReleaseCity();
//}
//
//int cCityMap::GetWidth()
//{
// return m_nWidth;
//}
//
//int cCityMap::GetHeight()
//{
// return m_nHeight;
//}
//
//cCityCell* cCityMap::Cell(int x, int y)
//{
// if (x >= 0 && x < m_nWidth && y >= 0 && y < m_nHeight)
// return &m_pCells[y*m_nWidth + x];
// else
// return nullptr;
//}
//
//void cCityMap::CreateCity(int w, int h)
//{
// //ReleaseCity();
// m_nWidth = w;
// m_nHeight = h;
// m_pCells = new cCityCell[m_nHeight * m_nWidth];
//
// for (int x = 0; x < m_nWidth; x++)
// {
// for (int y = 0; y < m_nHeight; y++)
// {
// //m_pCells[y*m_nWidth + x] = new cCityCell();
// //Cell(x, y)->bRoad = false;
// //Cell(x, y)->nHeight = 0;
// //Cell(x, y)->nWorldX = x;
// //Cell(x, y)->nWorldY = y;
// Cell(x, y)->nType = CELL_GRASS;
// //Cell(x, y)->bBuilding = false;
// }
// }
//}
//
//void cCityMap::ReleaseCity()
//{
// if (m_pCells != nullptr) delete m_pCells;
// m_nWidth = 0;
// m_nHeight = 0;
//}
//
//
//class cCarCrimeCity : public olc::PixelGameEngine
//{
//public:
// cCarCrimeCity()
// {
// sAppName = "Car Crime City";
// }
//
// ~cCarCrimeCity()
// {
// }
//
// bool OnUserCreate()
// {
// // Initialise PGEX 3D
// olc::GFX3D::ConfigureDisplay();
//
// // Create Default city
// pCity = new cCityMap(64, 32);// cGameSettings::nDefaultMapWidth, cGameSettings::nDefaultMapHeight);
//
//
// // A simple flat unit quad
// meshQuad.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, olc::RED },
// { 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, olc::RED},
// };
//
//
// sprOld = new olc::Sprite("assets/system/grass1.png");
//
//
//
// SetDrawTarget(nullptr);
// return true;
// }
//
//
// bool OnUserUpdate(float fElapsedTime)
// {
// // User Input
// if (GetKey(olc::Key::W).bHeld) vCamera.y -= 2.0f * fElapsedTime;
// if (GetKey(olc::Key::S).bHeld) vCamera.y += 2.0f * fElapsedTime;
// if (GetKey(olc::Key::A).bHeld) vCamera.x -= 2.0f * fElapsedTime;
// if (GetKey(olc::Key::D).bHeld) vCamera.x += 2.0f * fElapsedTime;
// if (GetKey(olc::Key::Z).bHeld) vCamera.z += 10.0f * fElapsedTime;
// if (GetKey(olc::Key::X).bHeld) vCamera.z -= 10.0f * fElapsedTime;
//
//
// vEye = vCamera;
//
// // Perform Ray casting to calculate visible world extents and mouse position
// olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir);
// olc::GFX3D::mat4x4 matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f);
// olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp);
//
//
//
// // Render Scene
// Clear(olc::BLUE);
// olc::GFX3D::ClearDepth();
//
// // Create rendering pipeline
// olc::GFX3D::PipeLine pipe;
// pipe.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f, 0.0f, 0.0f, (float)ScreenWidth(), (float)ScreenHeight());
// pipe.SetCamera(vEye, vLookTarget, vUp);
//
//
//
// int nStartX = 0;
// int nEndX = pCity->GetWidth();
// int nStartY = 0;
// int nEndY = pCity->GetHeight();
//
// // Render Ground, Roads, Walls & Buildings
// for (int x = nStartX; x < nEndX; x++)
// {
// if (x == 15)
// int k = 7;
//
// for (int y = nStartY; y < nEndY; y++)
// {
//
//
// switch (pCity->Cell(x, y)->nType)
// {
// case CELL_GRASS:
// {
// olc::GFX3D::mat4x4 matWorld;
// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f);
// pipe.SetTransform(matWorld);
// pipe.SetTexture(sprOld);
// //pipe.SetTexture(vecSpriteSystem[0]);
// //pipe.Render(vecMeshSystem[0].tris);
// pipe.Render(meshQuad.tris);
// //pipe.Render(vecMeshSystem[0].tris, olc::GFX3D::RENDER_FLAT);
// break;
// }
//
//
// default:
// {
// olc::GFX3D::mat4x4 matWorld;
// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f);
// pipe.SetTransform(matWorld);
// pipe.Render(meshQuad.tris, olc::GFX3D::RENDER_WIRE);
// break;
// }
// }
//
//
//
//
// }
// }
//
//
//
// return true;
// }
//
// bool OnUserDestroy()
// {
// return true;
// }
//
//
//private:
// olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -10.0f };
// olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f };
// olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -10.0f };
// olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f };
//
//
//
// olc::Sprite *sprOld = nullptr;
// olc::GFX3D::mesh meshQuad;
//
// cCityMap *pCity = nullptr;
//
//
//
//};
//
//int main()
//{
// // Load the settings singleton
// /*cGameSettings config;
// if (!config.LoadConfigFile("assets/config.lua"))
// {
// std::cout << "Failed to load '/assets/config.lua'" << std::endl;
// std::cout << " -> Using default configuration" << std::endl;
// }*/
//
// // Start the PixelGameEngine
// cCarCrimeCity game;
// if (game.Construct(256, 240, 4, 4))// config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight))
// game.Start();
//
// // Exit!
// return 0;
/*
Top Down City Based Car Crime Game - Part #2
"Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - 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:
~~~~~~~~~~~~~
Scroll with middle mouse wheel, TAB toggle edit mode, R to place road
P to place pavement, Q to place building, Arrow keys to drive car
Relevant Video: https://youtu.be/fIV6P1W-wuo
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
*/
#include "cGameSettings.h"
#include "cCarCrimeCity.h"
int main()
{
// Load the settings singleton
cGameSettings config;
if (!config.LoadConfigFile("assets/config.lua"))
{
std::cout << "Failed to load '/assets/config.lua'" << std::endl;
std::cout << " -> Using default configuration" << std::endl;
}
// Start the PixelGameEngine
cCarCrimeCity game;
if (game.Construct(config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight, config.bFullScreen))
game.Start();
// Exit!
return 0;
}
//#define OLC_PGE_APPLICATION
//#include "olcPixelGameEngine.h"
//
//#define OLC_PGEX_GRAPHICS3D
//#include "olcPGEX_Graphics3D.h"
//
//
//
//enum CELLTYPE
//{
// CELL_BLANK = 0,
// CELL_GRASS = 1,
// CELL_CONCRETE = 2,
// CELL_WATER = 3,
// CELL_BUILDING = 4,
// CELL_ROAD_H = 5,
// CELL_ROAD_V = 6,
// CELL_ROAD_C1 = 7,
// CELL_ROAD_C2 = 8,
// CELL_ROAD_C3 = 9,
// CELL_ROAD_C4 = 10,
// CELL_ROAD_T1 = 11,
// CELL_ROAD_T2 = 12,
// CELL_ROAD_T3 = 13,
// CELL_ROAD_T4 = 14,
// CELL_ROAD_X = 15,
//};
//
//struct cCityCell
//{
// int nType = 5;// CELL_GRASS;
//};
//
//class cCityMap
//{
//public:
// // Construct a "blank" city w units wide by h units high
// cCityMap(int w, int h);
//
// // Cleans up city, like Batman
// ~cCityMap();
//
//
//public:
// // Return width of city in cells
// int GetWidth();
// // Return height of city in cells
// int GetHeight();
// // Return a specific cell reference if inside city limits, or nullptr
// cCityCell* Cell(int x, int y);
//
//private:
// int m_nWidth = 0;
// int m_nHeight = 0;
// cCityCell *m_pCells = nullptr;
//
//private:
// // Creates a "default" city of specified size
// void CreateCity(int w, int h);
// // Destroy city
// void ReleaseCity();
//};
//
//cCityMap::cCityMap(int w, int h)
//{
// CreateCity(w, h);
//}
//
//cCityMap::~cCityMap()
//{
// //ReleaseCity();
//}
//
//int cCityMap::GetWidth()
//{
// return m_nWidth;
//}
//
//int cCityMap::GetHeight()
//{
// return m_nHeight;
//}
//
//cCityCell* cCityMap::Cell(int x, int y)
//{
// if (x >= 0 && x < m_nWidth && y >= 0 && y < m_nHeight)
// return &m_pCells[y*m_nWidth + x];
// else
// return nullptr;
//}
//
//void cCityMap::CreateCity(int w, int h)
//{
// //ReleaseCity();
// m_nWidth = w;
// m_nHeight = h;
// m_pCells = new cCityCell[m_nHeight * m_nWidth];
//
// for (int x = 0; x < m_nWidth; x++)
// {
// for (int y = 0; y < m_nHeight; y++)
// {
// //m_pCells[y*m_nWidth + x] = new cCityCell();
// //Cell(x, y)->bRoad = false;
// //Cell(x, y)->nHeight = 0;
// //Cell(x, y)->nWorldX = x;
// //Cell(x, y)->nWorldY = y;
// Cell(x, y)->nType = CELL_GRASS;
// //Cell(x, y)->bBuilding = false;
// }
// }
//}
//
//void cCityMap::ReleaseCity()
//{
// if (m_pCells != nullptr) delete m_pCells;
// m_nWidth = 0;
// m_nHeight = 0;
//}
//
//
//class cCarCrimeCity : public olc::PixelGameEngine
//{
//public:
// cCarCrimeCity()
// {
// sAppName = "Car Crime City";
// }
//
// ~cCarCrimeCity()
// {
// }
//
// bool OnUserCreate()
// {
// // Initialise PGEX 3D
// olc::GFX3D::ConfigureDisplay();
//
// // Create Default city
// pCity = new cCityMap(64, 32);// cGameSettings::nDefaultMapWidth, cGameSettings::nDefaultMapHeight);
//
//
// // A simple flat unit quad
// meshQuad.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, olc::RED },
// { 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, olc::RED},
// };
//
//
// sprOld = new olc::Sprite("assets/system/grass1.png");
//
//
//
// SetDrawTarget(nullptr);
// return true;
// }
//
//
// bool OnUserUpdate(float fElapsedTime)
// {
// // User Input
// if (GetKey(olc::Key::W).bHeld) vCamera.y -= 2.0f * fElapsedTime;
// if (GetKey(olc::Key::S).bHeld) vCamera.y += 2.0f * fElapsedTime;
// if (GetKey(olc::Key::A).bHeld) vCamera.x -= 2.0f * fElapsedTime;
// if (GetKey(olc::Key::D).bHeld) vCamera.x += 2.0f * fElapsedTime;
// if (GetKey(olc::Key::Z).bHeld) vCamera.z += 10.0f * fElapsedTime;
// if (GetKey(olc::Key::X).bHeld) vCamera.z -= 10.0f * fElapsedTime;
//
//
// vEye = vCamera;
//
// // Perform Ray casting to calculate visible world extents and mouse position
// olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir);
// olc::GFX3D::mat4x4 matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f);
// olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp);
//
//
//
// // Render Scene
// Clear(olc::BLUE);
// olc::GFX3D::ClearDepth();
//
// // Create rendering pipeline
// olc::GFX3D::PipeLine pipe;
// pipe.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f, 0.0f, 0.0f, (float)ScreenWidth(), (float)ScreenHeight());
// pipe.SetCamera(vEye, vLookTarget, vUp);
//
//
//
// int nStartX = 0;
// int nEndX = pCity->GetWidth();
// int nStartY = 0;
// int nEndY = pCity->GetHeight();
//
// // Render Ground, Roads, Walls & Buildings
// for (int x = nStartX; x < nEndX; x++)
// {
// if (x == 15)
// int k = 7;
//
// for (int y = nStartY; y < nEndY; y++)
// {
//
//
// switch (pCity->Cell(x, y)->nType)
// {
// case CELL_GRASS:
// {
// olc::GFX3D::mat4x4 matWorld;
// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f);
// pipe.SetTransform(matWorld);
// pipe.SetTexture(sprOld);
// //pipe.SetTexture(vecSpriteSystem[0]);
// //pipe.Render(vecMeshSystem[0].tris);
// pipe.Render(meshQuad.tris);
// //pipe.Render(vecMeshSystem[0].tris, olc::GFX3D::RENDER_FLAT);
// break;
// }
//
//
// default:
// {
// olc::GFX3D::mat4x4 matWorld;
// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f);
// pipe.SetTransform(matWorld);
// pipe.Render(meshQuad.tris, olc::GFX3D::RENDER_WIRE);
// break;
// }
// }
//
//
//
//
// }
// }
//
//
//
// return true;
// }
//
// bool OnUserDestroy()
// {
// return true;
// }
//
//
//private:
// olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -10.0f };
// olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f };
// olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -10.0f };
// olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f };
//
//
//
// olc::Sprite *sprOld = nullptr;
// olc::GFX3D::mesh meshQuad;
//
// cCityMap *pCity = nullptr;
//
//
//
//};
//
//int main()
//{
// // Load the settings singleton
// /*cGameSettings config;
// if (!config.LoadConfigFile("assets/config.lua"))
// {
// std::cout << "Failed to load '/assets/config.lua'" << std::endl;
// std::cout << " -> Using default configuration" << std::endl;
// }*/
//
// // Start the PixelGameEngine
// cCarCrimeCity game;
// if (game.Construct(256, 240, 4, 4))// config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight))
// game.Start();
//
// // Exit!
// return 0;
//}

@ -1,5 +1,5 @@
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_GRAPHICS3D
#include "olcPGEX_Graphics3D.h"
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_GRAPHICS3D
#include "olcPGEX_Graphics3D.h"

@ -1,189 +1,189 @@
/*
OneLoneCoder_PGE_ExtensionTestGFX2D.cpp
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
// Include the olcPixelGameEngine
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
// To use an extension, just include it
#define OLC_PGE_GRAPHICS2D
#include "olcPGEX_Graphics2D.h"
class TestExtension : public olc::PixelGameEngine
{
public:
TestExtension()
{
sAppName = "Testing Graphics2D";
}
public:
bool OnUserCreate() override
{
for (int i = 0; i < 16; i++)
listEvents.push_back("");
spr = new olc::Sprite("new_piskel.png");
return true;
}
std::list<std::string> listEvents;
float fTotalTime = 0.0f;
olc::Sprite *spr;
bool OnUserUpdate(float fElapsedTime) override
{
// Clear Screen
SetPixelMode(olc::Pixel::NORMAL);
Clear(olc::BLUE);
// Draw Primitives
DrawCircle(32, 32, 30); // Circle
DrawCircle(96, 32, 30); // Circle
float mx = (float)GetMouseX();
float my = (float)GetMouseY();
float px1 = mx - 32, px2 = mx - 96;
float py1 = my - 32, py2 = my - 32;
float pr1 = 1.0f / sqrtf(px1*px1 + py1*py1);
float pr2 = 1.0f / sqrtf(px2*px2 + py2*py2);
px1 = 22.0f * (px1 * pr1) + 32.0f;
py1 = 22.0f * (py1 * pr1) + 32.0f;
px2 = 22.0f * (px2 * pr2) + 96.0f;
py2 = 22.0f * (py2 * pr2) + 32.0f;
FillCircle((int32_t)px1, (int32_t)py1, 8, olc::CYAN);
FillCircle((int32_t)px2, (int32_t)py2, 8, olc::CYAN);
DrawLine(10, 70, 54, 70); // Lines
DrawLine(54, 70, 70, 54);
DrawRect(10, 80, 54, 30);
FillRect(10, 80, 54, 30);
// Multiline Text
std::string mpos = "Your Mouse Position is:\nX=" + std::to_string(mx) + "\nY=" + std::to_string(my);
DrawString(10, 130, mpos);
auto AddEvent = [&](std::string s)
{
listEvents.push_back(s);
listEvents.pop_front();
};
if (GetMouse(0).bPressed) AddEvent("Mouse Button 0 Down");
if (GetMouse(0).bReleased) AddEvent("Mouse Button 0 Up");
if (GetMouse(1).bPressed) AddEvent("Mouse Button 1 Down");
if (GetMouse(1).bReleased) AddEvent("Mouse Button 1 Up");
if (GetMouse(2).bPressed) AddEvent("Mouse Button 2 Down");
if (GetMouse(2).bReleased) AddEvent("Mouse Button 2 Up");
// Draw Event Log
int nLog = 0;
for (auto &s : listEvents)
{
DrawString(200, nLog * 8 + 20, s, olc::Pixel(nLog * 16, nLog * 16, nLog * 16));
nLog++;
}
std::string notes = "CDEFGAB";
// Test Text scaling and colours
DrawString(0, 360, "Text Scale = 1", olc::WHITE, 1);
DrawString(0, 368, "Text Scale = 2", olc::BLUE, 2);
DrawString(0, 384, "Text Scale = 3", olc::RED, 3);
DrawString(0, 408, "Text Scale = 4", olc::YELLOW, 4);
DrawString(0, 440, "Text Scale = 5", olc::GREEN, 5);
fTotalTime += fElapsedTime;
float fAngle = fTotalTime;
// Draw Sprite using extension, first create a transformation stack
olc::GFX2D::Transform2D t1;
// Traslate sprite so center of image is at 0,0
t1.Translate(-250, -35);
// Scale the sprite
t1.Scale(1 * sinf(fAngle) + 1, 1 * sinf(fAngle) + 1);
// Rotate it
t1.Rotate(fAngle*2.0f);
// Translate to 0,100
t1.Translate(0, 100);
// Rotate different speed
t1.Rotate(fAngle / 3);
// Translate to centre of screen
t1.Translate(320, 240);
SetPixelMode(olc::Pixel::ALPHA);
// Use extension to draw sprite with transform applied
olc::GFX2D::DrawSprite(spr, t1);
DrawSprite((int32_t)mx, (int32_t)my, spr, 4);
return true;
}
};
int main()
{
TestExtension demo;
if (demo.Construct(640, 480, 2, 2))
demo.Start();
return 0;
/*
OneLoneCoder_PGE_ExtensionTestGFX2D.cpp
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
// Include the olcPixelGameEngine
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
// To use an extension, just include it
#define OLC_PGE_GRAPHICS2D
#include "olcPGEX_Graphics2D.h"
class TestExtension : public olc::PixelGameEngine
{
public:
TestExtension()
{
sAppName = "Testing Graphics2D";
}
public:
bool OnUserCreate() override
{
for (int i = 0; i < 16; i++)
listEvents.push_back("");
spr = new olc::Sprite("new_piskel.png");
return true;
}
std::list<std::string> listEvents;
float fTotalTime = 0.0f;
olc::Sprite *spr;
bool OnUserUpdate(float fElapsedTime) override
{
// Clear Screen
SetPixelMode(olc::Pixel::NORMAL);
Clear(olc::BLUE);
// Draw Primitives
DrawCircle(32, 32, 30); // Circle
DrawCircle(96, 32, 30); // Circle
float mx = (float)GetMouseX();
float my = (float)GetMouseY();
float px1 = mx - 32, px2 = mx - 96;
float py1 = my - 32, py2 = my - 32;
float pr1 = 1.0f / sqrtf(px1*px1 + py1*py1);
float pr2 = 1.0f / sqrtf(px2*px2 + py2*py2);
px1 = 22.0f * (px1 * pr1) + 32.0f;
py1 = 22.0f * (py1 * pr1) + 32.0f;
px2 = 22.0f * (px2 * pr2) + 96.0f;
py2 = 22.0f * (py2 * pr2) + 32.0f;
FillCircle((int32_t)px1, (int32_t)py1, 8, olc::CYAN);
FillCircle((int32_t)px2, (int32_t)py2, 8, olc::CYAN);
DrawLine(10, 70, 54, 70); // Lines
DrawLine(54, 70, 70, 54);
DrawRect(10, 80, 54, 30);
FillRect(10, 80, 54, 30);
// Multiline Text
std::string mpos = "Your Mouse Position is:\nX=" + std::to_string(mx) + "\nY=" + std::to_string(my);
DrawString(10, 130, mpos);
auto AddEvent = [&](std::string s)
{
listEvents.push_back(s);
listEvents.pop_front();
};
if (GetMouse(0).bPressed) AddEvent("Mouse Button 0 Down");
if (GetMouse(0).bReleased) AddEvent("Mouse Button 0 Up");
if (GetMouse(1).bPressed) AddEvent("Mouse Button 1 Down");
if (GetMouse(1).bReleased) AddEvent("Mouse Button 1 Up");
if (GetMouse(2).bPressed) AddEvent("Mouse Button 2 Down");
if (GetMouse(2).bReleased) AddEvent("Mouse Button 2 Up");
// Draw Event Log
int nLog = 0;
for (auto &s : listEvents)
{
DrawString(200, nLog * 8 + 20, s, olc::Pixel(nLog * 16, nLog * 16, nLog * 16));
nLog++;
}
std::string notes = "CDEFGAB";
// Test Text scaling and colours
DrawString(0, 360, "Text Scale = 1", olc::WHITE, 1);
DrawString(0, 368, "Text Scale = 2", olc::BLUE, 2);
DrawString(0, 384, "Text Scale = 3", olc::RED, 3);
DrawString(0, 408, "Text Scale = 4", olc::YELLOW, 4);
DrawString(0, 440, "Text Scale = 5", olc::GREEN, 5);
fTotalTime += fElapsedTime;
float fAngle = fTotalTime;
// Draw Sprite using extension, first create a transformation stack
olc::GFX2D::Transform2D t1;
// Traslate sprite so center of image is at 0,0
t1.Translate(-250, -35);
// Scale the sprite
t1.Scale(1 * sinf(fAngle) + 1, 1 * sinf(fAngle) + 1);
// Rotate it
t1.Rotate(fAngle*2.0f);
// Translate to 0,100
t1.Translate(0, 100);
// Rotate different speed
t1.Rotate(fAngle / 3);
// Translate to centre of screen
t1.Translate(320, 240);
SetPixelMode(olc::Pixel::ALPHA);
// Use extension to draw sprite with transform applied
olc::GFX2D::DrawSprite(spr, t1);
DrawSprite((int32_t)mx, (int32_t)my, spr, 4);
return true;
}
};
int main()
{
TestExtension demo;
if (demo.Construct(640, 480, 2, 2))
demo.Start();
return 0;
}

@ -1,426 +1,426 @@
/*
OneLoneCoder.com - Path Finding #2 - Wave Propagation & Potential Fields
"...never get lost again, so long as you know where you are" - @Javidx9
Background
~~~~~~~~~~
A nice follow up alternative to the A* Algorithm. Wave propagation is less
applicable to multiple objects with multiple destinations, but fantatsic
for multiple objects all reaching the same destination.
WARNING! This code is NOT OPTIMAL!! It is however very robust. There
are many ways to optimise this further.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Patreon: https://www.patreon/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
Part #1 https://youtu.be/icZj67PTFhc
Part #2 https://youtu.be/0ihciMKlcP8
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <vector>
#include <list>
#include <algorithm>
#include <utility>
// Override base class with your custom functionality
class PathFinding_FlowFields : public olc::PixelGameEngine
{
public:
PathFinding_FlowFields()
{
sAppName = "PathFinding - Flow Fields";
}
private:
int nMapWidth;
int nMapHeight;
int nCellSize;
int nBorderWidth;
bool *bObstacleMap;
int *nFlowFieldZ;
float *fFlowFieldY;
float *fFlowFieldX;
int nStartX;
int nStartY;
int nEndX;
int nEndY;
int nWave = 1;
public:
bool OnUserCreate() override
{
nBorderWidth = 4;
nCellSize = 32;
nMapWidth = ScreenWidth() / nCellSize;
nMapHeight = ScreenHeight() / nCellSize;
bObstacleMap = new bool[nMapWidth * nMapHeight]{ false };
nFlowFieldZ = new int[nMapWidth * nMapHeight]{ 0 };
fFlowFieldX = new float[nMapWidth * nMapHeight]{ 0 };
fFlowFieldY = new float[nMapWidth * nMapHeight]{ 0 };
nStartX = 3;
nStartY = 7;
nEndX = 12;
nEndY = 7;
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Little convenience lambda 2D -> 1D
auto p = [&](int x, int y) { return y * nMapWidth + x; };
// User Input
int nSelectedCellX = GetMouseX() / nCellSize;
int nSelectedCellY = GetMouseY() / nCellSize;
if (GetMouse(0).bReleased)
{
// Toggle Obstacle if mouse left clicked
bObstacleMap[p(nSelectedCellX, nSelectedCellY)] =
!bObstacleMap[p(nSelectedCellX, nSelectedCellY)];
}
if (GetMouse(1).bReleased)
{
nStartX = nSelectedCellX;
nStartY = nSelectedCellY;
}
if (GetKey(olc::Key::Q).bReleased)
{
nWave++;
}
if (GetKey(olc::Key::A).bReleased)
{
nWave--;
if (nWave == 0)
nWave = 1;
}
// 1) Prepare flow field, add a boundary, and add obstacles
// by setting the flow Field Height (Z) to -1
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
// Set border or obstacles
if (x == 0 || y == 0 || x == (nMapWidth - 1) || y == (nMapHeight - 1) || bObstacleMap[p(x, y)])
{
nFlowFieldZ[p(x, y)] = -1;
}
else
{
nFlowFieldZ[p(x, y)] = 0;
}
}
}
// 2) Propagate a wave (i.e. flood-fill) from target location. Here I use
// a tuple, of {x, y, distance} - though you could use a struct or
// similar.
std::list<std::tuple<int, int, int>> nodes;
// Add the first discovered node - the target location, with a distance of 1
nodes.push_back({ nEndX, nEndY, 1 });
while (!nodes.empty())
{
// Each iteration through the discovered nodes may create newly discovered
// nodes, so I maintain a second list. It's important not to contaminate
// the list being iterated through.
std::list<std::tuple<int, int, int>> new_nodes;
// Now iterate through each discovered node. If it has neighbouring nodes
// that are empty space and undiscovered, add those locations to the
// new nodes list
for (auto &n : nodes)
{
int x = std::get<0>(n); // Map X-Coordinate
int y = std::get<1>(n); // Map Y-Coordinate
int d = std::get<2>(n); // Distance From Target Location
// Set distance count for this node. NOte that when we add nodes we add 1
// to this distance. This emulates propagating a wave across the map, where
// the front of that wave increments each iteration. In this way, we can
// propagate distance information 'away from target location'
nFlowFieldZ[p(x, y)] = d;
// Add neigbour nodes if unmarked, i.e their "height" is 0. Any discovered
// node or obstacle will be non-zero
// Check East
if ((x + 1) < nMapWidth && nFlowFieldZ[p(x + 1, y)] == 0)
new_nodes.push_back({ x + 1, y, d + 1 });
// Check West
if ((x - 1) >= 0 && nFlowFieldZ[p(x - 1, y)] == 0)
new_nodes.push_back({ x - 1, y, d + 1 });
// Check South
if ((y + 1) < nMapHeight && nFlowFieldZ[p(x, y + 1)] == 0)
new_nodes.push_back({ x, y + 1, d + 1 });
// Check North
if ((y - 1) >= 0 && nFlowFieldZ[p(x, y - 1)] == 0)
new_nodes.push_back({ x, y - 1, d + 1 });
}
// We will now have potentially multiple nodes for a single location. This means our
// algorithm will never complete! So we must remove duplicates form our new node list.
// Im doing this with some clever code - but it is not performant(!) - it is merely
// convenient. I'd suggest doing away with overhead structures like linked lists and sorts
// if you are aiming for fastest path finding.
// Sort the nodes - This will stack up nodes that are similar: A, B, B, B, B, C, D, D, E, F, F
new_nodes.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
// In this instance I dont care how the values are sorted, so long as nodes that
// represent the same location are adjacent in the list. I can use the p() lambda
// to generate a unique 1D value for a 2D coordinate, so I'll sort by that.
return p(std::get<0>(n1), std::get<1>(n1)) < p(std::get<0>(n2), std::get<1>(n2));
});
// Use "unique" function to remove adjacent duplicates : A, B, -, -, -, C, D, -, E, F -
// and also erase them : A, B, C, D, E, F
new_nodes.unique([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return p(std::get<0>(n1), std::get<1>(n1)) == p(std::get<0>(n2), std::get<1>(n2));
});
// We've now processed all the discoverd nodes, so clear the list, and add the newly
// discovered nodes for processing on the next iteration
nodes.clear();
nodes.insert(nodes.begin(), new_nodes.begin(), new_nodes.end());
// When there are no more newly discovered nodes, we have "flood filled" the entire
// map. The propagation phase of the algorithm is complete
}
// 3) Create Path. Starting a start location, create a path of nodes until you reach target
// location. At each node find the neighbour with the lowest "distance" score.
std::list<std::pair<int, int>> path;
path.push_back({ nStartX, nStartY });
int nLocX = nStartX;
int nLocY = nStartY;
bool bNoPath = false;
while (!(nLocX == nEndX && nLocY == nEndY) && !bNoPath)
{
std::list<std::tuple<int, int, int>> listNeighbours;
// 4-Way Connectivity
if ((nLocY - 1) >= 0 && nFlowFieldZ[p(nLocX, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY - 1, nFlowFieldZ[p(nLocX, nLocY - 1)] });
if ((nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY, nFlowFieldZ[p(nLocX + 1, nLocY)] });
if ((nLocY + 1) < nMapHeight && nFlowFieldZ[p(nLocX, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY + 1, nFlowFieldZ[p(nLocX, nLocY + 1)] });
if ((nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY, nFlowFieldZ[p(nLocX - 1, nLocY)] });
// 8-Way Connectivity
if ((nLocY - 1) >= 0 && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY - 1, nFlowFieldZ[p(nLocX - 1, nLocY - 1)] });
if ((nLocY - 1) >= 0 && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY - 1, nFlowFieldZ[p(nLocX + 1, nLocY - 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY + 1, nFlowFieldZ[p(nLocX - 1, nLocY + 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY + 1, nFlowFieldZ[p(nLocX + 1, nLocY + 1)] });
// Sprt neigbours based on height, so lowest neighbour is at front
// of list
listNeighbours.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return std::get<2>(n1) < std::get<2>(n2); // Compare distances
});
if (listNeighbours.empty()) // Neighbour is invalid or no possible path
bNoPath = true;
else
{
nLocX = std::get<0>(listNeighbours.front());
nLocY = std::get<1>(listNeighbours.front());
path.push_back({ nLocX, nLocY });
}
}
// 4) Create Flow "Field"
for (int x = 1; x < nMapWidth - 1; x++)
{
for (int y = 1; y < nMapHeight - 1; y++)
{
float vx = 0.0f;
float vy = 0.0f;
vy -= (float)((nFlowFieldZ[p(x, y + 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y + 1)]) - nFlowFieldZ[p(x, y)]);
vx -= (float)((nFlowFieldZ[p(x + 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x + 1, y)]) - nFlowFieldZ[p(x, y)]);
vy += (float)((nFlowFieldZ[p(x, y - 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y - 1)]) - nFlowFieldZ[p(x, y)]);
vx += (float)((nFlowFieldZ[p(x - 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x - 1, y)]) - nFlowFieldZ[p(x, y)]);
float r = 1.0f / sqrtf(vx*vx + vy * vy);
fFlowFieldX[p(x, y)] = vx * r;
fFlowFieldY[p(x, y)] = vy * r;
}
}
// Draw Map
Clear(olc::BLACK);
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
olc::Pixel colour = olc::BLUE;
if (bObstacleMap[p(x, y)])
colour = olc::GREY;
if (nWave == nFlowFieldZ[p(x, y)])
colour = olc::DARK_CYAN;
if (x == nStartX && y == nStartY)
colour = olc::GREEN;
if (x == nEndX && y == nEndY)
colour = olc::RED;
// Draw Base
FillRect(x * nCellSize, y * nCellSize, nCellSize - nBorderWidth, nCellSize - nBorderWidth, colour);
// Draw "potential" or "distance" or "height" :D
//DrawString(x * nCellSize, y * nCellSize, std::to_string(nFlowFieldZ[p(x, y)]), olc::WHITE);
if (nFlowFieldZ[p(x, y)] > 0)
{
float ax[4], ay[4];
float fAngle = atan2f(fFlowFieldY[p(x, y)], fFlowFieldX[p(x, y)]);
float fRadius = (float)(nCellSize - nBorderWidth) / 2.0f;
int fOffsetX = x * nCellSize + ((nCellSize - nBorderWidth) / 2);
int fOffsetY = y * nCellSize + ((nCellSize - nBorderWidth) / 2);
ax[0] = cosf(fAngle) * fRadius + fOffsetX;
ay[0] = sinf(fAngle) * fRadius + fOffsetY;
ax[1] = cosf(fAngle) * -fRadius + fOffsetX;
ay[1] = sinf(fAngle) * -fRadius + fOffsetY;
ax[2] = cosf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetX;
ay[2] = sinf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetY;
ax[3] = cosf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetX;
ay[3] = sinf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetY;
DrawLine(ax[0], ay[0], ax[1], ay[1], olc::CYAN);
DrawLine(ax[0], ay[0], ax[2], ay[2], olc::CYAN);
DrawLine(ax[0], ay[0], ax[3], ay[3], olc::CYAN);
}
}
}
bool bFirstPoint = true;
int ox, oy;
for (auto &a : path)
{
if (bFirstPoint)
{
ox = a.first;
oy = a.second;
bFirstPoint = false;
}
else
{
DrawLine(
ox * nCellSize + ((nCellSize - nBorderWidth) / 2),
oy * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.first * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.second * nCellSize + ((nCellSize - nBorderWidth) / 2), olc::YELLOW);
ox = a.first;
oy = a.second;
FillCircle(ox * nCellSize + ((nCellSize - nBorderWidth) / 2), oy * nCellSize + ((nCellSize - nBorderWidth) / 2), 10, olc::YELLOW);
}
}
return true;
}
};
int main()
{
PathFinding_FlowFields demo;
if (demo.Construct(512, 480, 2, 2))
demo.Start();
return 0;
/*
OneLoneCoder.com - Path Finding #2 - Wave Propagation & Potential Fields
"...never get lost again, so long as you know where you are" - @Javidx9
Background
~~~~~~~~~~
A nice follow up alternative to the A* Algorithm. Wave propagation is less
applicable to multiple objects with multiple destinations, but fantatsic
for multiple objects all reaching the same destination.
WARNING! This code is NOT OPTIMAL!! It is however very robust. There
are many ways to optimise this further.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Patreon: https://www.patreon/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
Part #1 https://youtu.be/icZj67PTFhc
Part #2 https://youtu.be/0ihciMKlcP8
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <vector>
#include <list>
#include <algorithm>
#include <utility>
// Override base class with your custom functionality
class PathFinding_FlowFields : public olc::PixelGameEngine
{
public:
PathFinding_FlowFields()
{
sAppName = "PathFinding - Flow Fields";
}
private:
int nMapWidth;
int nMapHeight;
int nCellSize;
int nBorderWidth;
bool *bObstacleMap;
int *nFlowFieldZ;
float *fFlowFieldY;
float *fFlowFieldX;
int nStartX;
int nStartY;
int nEndX;
int nEndY;
int nWave = 1;
public:
bool OnUserCreate() override
{
nBorderWidth = 4;
nCellSize = 32;
nMapWidth = ScreenWidth() / nCellSize;
nMapHeight = ScreenHeight() / nCellSize;
bObstacleMap = new bool[nMapWidth * nMapHeight]{ false };
nFlowFieldZ = new int[nMapWidth * nMapHeight]{ 0 };
fFlowFieldX = new float[nMapWidth * nMapHeight]{ 0 };
fFlowFieldY = new float[nMapWidth * nMapHeight]{ 0 };
nStartX = 3;
nStartY = 7;
nEndX = 12;
nEndY = 7;
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Little convenience lambda 2D -> 1D
auto p = [&](int x, int y) { return y * nMapWidth + x; };
// User Input
int nSelectedCellX = GetMouseX() / nCellSize;
int nSelectedCellY = GetMouseY() / nCellSize;
if (GetMouse(0).bReleased)
{
// Toggle Obstacle if mouse left clicked
bObstacleMap[p(nSelectedCellX, nSelectedCellY)] =
!bObstacleMap[p(nSelectedCellX, nSelectedCellY)];
}
if (GetMouse(1).bReleased)
{
nStartX = nSelectedCellX;
nStartY = nSelectedCellY;
}
if (GetKey(olc::Key::Q).bReleased)
{
nWave++;
}
if (GetKey(olc::Key::A).bReleased)
{
nWave--;
if (nWave == 0)
nWave = 1;
}
// 1) Prepare flow field, add a boundary, and add obstacles
// by setting the flow Field Height (Z) to -1
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
// Set border or obstacles
if (x == 0 || y == 0 || x == (nMapWidth - 1) || y == (nMapHeight - 1) || bObstacleMap[p(x, y)])
{
nFlowFieldZ[p(x, y)] = -1;
}
else
{
nFlowFieldZ[p(x, y)] = 0;
}
}
}
// 2) Propagate a wave (i.e. flood-fill) from target location. Here I use
// a tuple, of {x, y, distance} - though you could use a struct or
// similar.
std::list<std::tuple<int, int, int>> nodes;
// Add the first discovered node - the target location, with a distance of 1
nodes.push_back({ nEndX, nEndY, 1 });
while (!nodes.empty())
{
// Each iteration through the discovered nodes may create newly discovered
// nodes, so I maintain a second list. It's important not to contaminate
// the list being iterated through.
std::list<std::tuple<int, int, int>> new_nodes;
// Now iterate through each discovered node. If it has neighbouring nodes
// that are empty space and undiscovered, add those locations to the
// new nodes list
for (auto &n : nodes)
{
int x = std::get<0>(n); // Map X-Coordinate
int y = std::get<1>(n); // Map Y-Coordinate
int d = std::get<2>(n); // Distance From Target Location
// Set distance count for this node. NOte that when we add nodes we add 1
// to this distance. This emulates propagating a wave across the map, where
// the front of that wave increments each iteration. In this way, we can
// propagate distance information 'away from target location'
nFlowFieldZ[p(x, y)] = d;
// Add neigbour nodes if unmarked, i.e their "height" is 0. Any discovered
// node or obstacle will be non-zero
// Check East
if ((x + 1) < nMapWidth && nFlowFieldZ[p(x + 1, y)] == 0)
new_nodes.push_back({ x + 1, y, d + 1 });
// Check West
if ((x - 1) >= 0 && nFlowFieldZ[p(x - 1, y)] == 0)
new_nodes.push_back({ x - 1, y, d + 1 });
// Check South
if ((y + 1) < nMapHeight && nFlowFieldZ[p(x, y + 1)] == 0)
new_nodes.push_back({ x, y + 1, d + 1 });
// Check North
if ((y - 1) >= 0 && nFlowFieldZ[p(x, y - 1)] == 0)
new_nodes.push_back({ x, y - 1, d + 1 });
}
// We will now have potentially multiple nodes for a single location. This means our
// algorithm will never complete! So we must remove duplicates form our new node list.
// Im doing this with some clever code - but it is not performant(!) - it is merely
// convenient. I'd suggest doing away with overhead structures like linked lists and sorts
// if you are aiming for fastest path finding.
// Sort the nodes - This will stack up nodes that are similar: A, B, B, B, B, C, D, D, E, F, F
new_nodes.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
// In this instance I dont care how the values are sorted, so long as nodes that
// represent the same location are adjacent in the list. I can use the p() lambda
// to generate a unique 1D value for a 2D coordinate, so I'll sort by that.
return p(std::get<0>(n1), std::get<1>(n1)) < p(std::get<0>(n2), std::get<1>(n2));
});
// Use "unique" function to remove adjacent duplicates : A, B, -, -, -, C, D, -, E, F -
// and also erase them : A, B, C, D, E, F
new_nodes.unique([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return p(std::get<0>(n1), std::get<1>(n1)) == p(std::get<0>(n2), std::get<1>(n2));
});
// We've now processed all the discoverd nodes, so clear the list, and add the newly
// discovered nodes for processing on the next iteration
nodes.clear();
nodes.insert(nodes.begin(), new_nodes.begin(), new_nodes.end());
// When there are no more newly discovered nodes, we have "flood filled" the entire
// map. The propagation phase of the algorithm is complete
}
// 3) Create Path. Starting a start location, create a path of nodes until you reach target
// location. At each node find the neighbour with the lowest "distance" score.
std::list<std::pair<int, int>> path;
path.push_back({ nStartX, nStartY });
int nLocX = nStartX;
int nLocY = nStartY;
bool bNoPath = false;
while (!(nLocX == nEndX && nLocY == nEndY) && !bNoPath)
{
std::list<std::tuple<int, int, int>> listNeighbours;
// 4-Way Connectivity
if ((nLocY - 1) >= 0 && nFlowFieldZ[p(nLocX, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY - 1, nFlowFieldZ[p(nLocX, nLocY - 1)] });
if ((nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY, nFlowFieldZ[p(nLocX + 1, nLocY)] });
if ((nLocY + 1) < nMapHeight && nFlowFieldZ[p(nLocX, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX, nLocY + 1, nFlowFieldZ[p(nLocX, nLocY + 1)] });
if ((nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY, nFlowFieldZ[p(nLocX - 1, nLocY)] });
// 8-Way Connectivity
if ((nLocY - 1) >= 0 && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY - 1, nFlowFieldZ[p(nLocX - 1, nLocY - 1)] });
if ((nLocY - 1) >= 0 && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY - 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY - 1, nFlowFieldZ[p(nLocX + 1, nLocY - 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX - 1) >= 0 && nFlowFieldZ[p(nLocX - 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX - 1, nLocY + 1, nFlowFieldZ[p(nLocX - 1, nLocY + 1)] });
if ((nLocY + 1) < nMapHeight && (nLocX + 1) < nMapWidth && nFlowFieldZ[p(nLocX + 1, nLocY + 1)] > 0)
listNeighbours.push_back({ nLocX + 1, nLocY + 1, nFlowFieldZ[p(nLocX + 1, nLocY + 1)] });
// Sprt neigbours based on height, so lowest neighbour is at front
// of list
listNeighbours.sort([&](const std::tuple<int, int, int> &n1, const std::tuple<int, int, int> &n2)
{
return std::get<2>(n1) < std::get<2>(n2); // Compare distances
});
if (listNeighbours.empty()) // Neighbour is invalid or no possible path
bNoPath = true;
else
{
nLocX = std::get<0>(listNeighbours.front());
nLocY = std::get<1>(listNeighbours.front());
path.push_back({ nLocX, nLocY });
}
}
// 4) Create Flow "Field"
for (int x = 1; x < nMapWidth - 1; x++)
{
for (int y = 1; y < nMapHeight - 1; y++)
{
float vx = 0.0f;
float vy = 0.0f;
vy -= (float)((nFlowFieldZ[p(x, y + 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y + 1)]) - nFlowFieldZ[p(x, y)]);
vx -= (float)((nFlowFieldZ[p(x + 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x + 1, y)]) - nFlowFieldZ[p(x, y)]);
vy += (float)((nFlowFieldZ[p(x, y - 1)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x, y - 1)]) - nFlowFieldZ[p(x, y)]);
vx += (float)((nFlowFieldZ[p(x - 1, y)] <= 0 ? nFlowFieldZ[p(x, y)] : nFlowFieldZ[p(x - 1, y)]) - nFlowFieldZ[p(x, y)]);
float r = 1.0f / sqrtf(vx*vx + vy * vy);
fFlowFieldX[p(x, y)] = vx * r;
fFlowFieldY[p(x, y)] = vy * r;
}
}
// Draw Map
Clear(olc::BLACK);
for (int x = 0; x < nMapWidth; x++)
{
for (int y = 0; y < nMapHeight; y++)
{
olc::Pixel colour = olc::BLUE;
if (bObstacleMap[p(x, y)])
colour = olc::GREY;
if (nWave == nFlowFieldZ[p(x, y)])
colour = olc::DARK_CYAN;
if (x == nStartX && y == nStartY)
colour = olc::GREEN;
if (x == nEndX && y == nEndY)
colour = olc::RED;
// Draw Base
FillRect(x * nCellSize, y * nCellSize, nCellSize - nBorderWidth, nCellSize - nBorderWidth, colour);
// Draw "potential" or "distance" or "height" :D
//DrawString(x * nCellSize, y * nCellSize, std::to_string(nFlowFieldZ[p(x, y)]), olc::WHITE);
if (nFlowFieldZ[p(x, y)] > 0)
{
float ax[4], ay[4];
float fAngle = atan2f(fFlowFieldY[p(x, y)], fFlowFieldX[p(x, y)]);
float fRadius = (float)(nCellSize - nBorderWidth) / 2.0f;
int fOffsetX = x * nCellSize + ((nCellSize - nBorderWidth) / 2);
int fOffsetY = y * nCellSize + ((nCellSize - nBorderWidth) / 2);
ax[0] = cosf(fAngle) * fRadius + fOffsetX;
ay[0] = sinf(fAngle) * fRadius + fOffsetY;
ax[1] = cosf(fAngle) * -fRadius + fOffsetX;
ay[1] = sinf(fAngle) * -fRadius + fOffsetY;
ax[2] = cosf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetX;
ay[2] = sinf(fAngle + 0.1f) * fRadius * 0.7f + fOffsetY;
ax[3] = cosf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetX;
ay[3] = sinf(fAngle - 0.1f) * fRadius * 0.7f + fOffsetY;
DrawLine(ax[0], ay[0], ax[1], ay[1], olc::CYAN);
DrawLine(ax[0], ay[0], ax[2], ay[2], olc::CYAN);
DrawLine(ax[0], ay[0], ax[3], ay[3], olc::CYAN);
}
}
}
bool bFirstPoint = true;
int ox, oy;
for (auto &a : path)
{
if (bFirstPoint)
{
ox = a.first;
oy = a.second;
bFirstPoint = false;
}
else
{
DrawLine(
ox * nCellSize + ((nCellSize - nBorderWidth) / 2),
oy * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.first * nCellSize + ((nCellSize - nBorderWidth) / 2),
a.second * nCellSize + ((nCellSize - nBorderWidth) / 2), olc::YELLOW);
ox = a.first;
oy = a.second;
FillCircle(ox * nCellSize + ((nCellSize - nBorderWidth) / 2), oy * nCellSize + ((nCellSize - nBorderWidth) / 2), 10, olc::YELLOW);
}
}
return true;
}
};
int main()
{
PathFinding_FlowFields demo;
if (demo.Construct(512, 480, 2, 2))
demo.Start();
return 0;
}

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

@ -1,272 +1,272 @@
/*
Programming a robotic arm
"I told you, put down the screwdriver..." - 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:
~~~~~~~~~~~~~
Without a robot arm and an mbed there is not much you can do!
Also requires a 3rd Party PGEX UI by ZleapingBear:
https://youtu.be/bfiSjC__MCI
Relevant Video: https://youtu.be/ekdQ-aAB36Y
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 "olcPGEX_UI.h"
class RobotArm1 : public olc::PixelGameEngine
{
public:
RobotArm1()
{
sAppName = "Robot Arm 1";
}
olc::UI_CONTAINER gui;
float fJointAngle[6];
float fAccumulatedTime = 0.0f;
HANDLE hCom = nullptr;
public:
bool OnUserCreate() override
{
gui.addSlider(10, 20, 180);
gui.addSlider(10, 60, 180);
gui.addSlider(10, 100, 180);
gui.addSlider(10, 140, 180);
gui.addSlider(10, 180, 180);
gui.addSlider(10, 220, 180);
gui.setValue(0, 50);
gui.setValue(1, 50);
gui.setValue(2, 50);
gui.setValue(3, 50);
gui.setValue(4, 50);
gui.setValue(5, 50);
// Open COM Port
hCom = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hCom == nullptr) return false;
// Configure Protocol: 9600bps, 8N1
DCB dcb = { 0 };
GetCommState(hCom, &dcb);
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
SetCommState(hCom, &dcb);
return true;
}
bool OnUserDestroy() override
{
if (hCom != nullptr) CloseHandle(hCom);
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
gui.Update(fElapsedTime);
Clear(olc::GREEN);
gui.drawUIObjects();
for (int i = 0; i < 6; i++)
fJointAngle[i] = (gui.getSliderFloat(i) / 100.0f) * 180.0f - 90.0f;
unsigned char command[12];
for (int i = 0; i < 6; i++)
{
command[i * 2 + 0] = i;
command[i * 2 + 1] = (int)(128 + fJointAngle[i]);
}
fAccumulatedTime += fElapsedTime;
if (fAccumulatedTime > 0.05f)
{
fAccumulatedTime -= 0.05f;
DWORD bw = 0;
WriteFile(hCom, command, 12, &bw, 0);
}
return true;
}
};
int main()
{
RobotArm1 demo;
if (demo.Construct(400, 400, 2, 2))
demo.Start();
return 0;
}
// Below here is the source code compiled on MBED LPC1768, using the BufferedSerial Library
/*
#include "mbed.h"
#include "BufferedSerial.h"
PwmOut pin26(p26);
PwmOut pin25(p25);
PwmOut pin24(p24);
PwmOut pin23(p23);
PwmOut pin22(p22);
PwmOut pin21(p21);
BufferedSerial uart(p9, p10);
class Joint
{
private:
static const float fDutyMin = 0.03f; // -90
static const float fDutyMax = 0.11f; // +90
static const float fDutyRange = fDutyMax - fDutyMin;
float fTarget;
float fPosition;
float fJointMax;
float fJointMin;
public:
Joint(float fMin = -90.0f, float fMax = 90.0f, float fDefaultPos = 0.0f)
{
fJointMin = fMin;
fJointMax = fMax;
fPosition = 0.0f;
SetTarget(fDefaultPos);
}
void SetTarget(float fAngle)
{
fTarget = fAngle;
if(fTarget < fJointMin) fTarget = fJointMin;
if(fTarget > fJointMax) fTarget = fJointMax;
}
void UpdatePosition()
{
fPosition = fTarget;
}
float GetTarget()
{
return fTarget;
}
float GetDutyCycle()
{
float fDutyCycle = fPosition / (fJointMax - fJointMin);
fDutyCycle = (fDutyCycle * fDutyRange) + fDutyMin + (fDutyRange * 0.5f);
return fDutyCycle;
}
};
int main()
{
// Servos (MG996R) operate on 20ms period, so set
// PWM period for each pin
pin26.period(0.02f);
pin25.period(0.02f);
pin24.period(0.02f);
pin23.period(0.02f);
pin22.period(0.02f);
pin21.period(0.02f);
Joint joint[6];
joint[0].SetTarget(0.0f);
joint[1].SetTarget(0.0f);
joint[2].SetTarget(0.0f);
joint[3].SetTarget(-25.0f);
joint[4].SetTarget(-20.0f);
joint[5].SetTarget(-15.0f);
int nTargetJoint = 0;
while(1)
{
// Read from UART
if(uart.readable())
{
unsigned char c = (unsigned char)uart.getc();
if(c < 10)
nTargetJoint = c;
else
joint[nTargetJoint].SetTarget((float)c - 128);
}
// Write Duty Cycles
// Update each joints position
for(int i=0; i<6; i++)
joint[i].UpdatePosition();
// Set PWM values for each joint
pin26.write(joint[0].GetDutyCycle());
pin25.write(joint[1].GetDutyCycle());
pin24.write(joint[2].GetDutyCycle());
pin23.write(joint[3].GetDutyCycle());
pin22.write(joint[4].GetDutyCycle());
pin21.write(joint[5].GetDutyCycle());
}
}
/*
Programming a robotic arm
"I told you, put down the screwdriver..." - 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:
~~~~~~~~~~~~~
Without a robot arm and an mbed there is not much you can do!
Also requires a 3rd Party PGEX UI by ZleapingBear:
https://youtu.be/bfiSjC__MCI
Relevant Video: https://youtu.be/ekdQ-aAB36Y
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 "olcPGEX_UI.h"
class RobotArm1 : public olc::PixelGameEngine
{
public:
RobotArm1()
{
sAppName = "Robot Arm 1";
}
olc::UI_CONTAINER gui;
float fJointAngle[6];
float fAccumulatedTime = 0.0f;
HANDLE hCom = nullptr;
public:
bool OnUserCreate() override
{
gui.addSlider(10, 20, 180);
gui.addSlider(10, 60, 180);
gui.addSlider(10, 100, 180);
gui.addSlider(10, 140, 180);
gui.addSlider(10, 180, 180);
gui.addSlider(10, 220, 180);
gui.setValue(0, 50);
gui.setValue(1, 50);
gui.setValue(2, 50);
gui.setValue(3, 50);
gui.setValue(4, 50);
gui.setValue(5, 50);
// Open COM Port
hCom = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hCom == nullptr) return false;
// Configure Protocol: 9600bps, 8N1
DCB dcb = { 0 };
GetCommState(hCom, &dcb);
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
SetCommState(hCom, &dcb);
return true;
}
bool OnUserDestroy() override
{
if (hCom != nullptr) CloseHandle(hCom);
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
gui.Update(fElapsedTime);
Clear(olc::GREEN);
gui.drawUIObjects();
for (int i = 0; i < 6; i++)
fJointAngle[i] = (gui.getSliderFloat(i) / 100.0f) * 180.0f - 90.0f;
unsigned char command[12];
for (int i = 0; i < 6; i++)
{
command[i * 2 + 0] = i;
command[i * 2 + 1] = (int)(128 + fJointAngle[i]);
}
fAccumulatedTime += fElapsedTime;
if (fAccumulatedTime > 0.05f)
{
fAccumulatedTime -= 0.05f;
DWORD bw = 0;
WriteFile(hCom, command, 12, &bw, 0);
}
return true;
}
};
int main()
{
RobotArm1 demo;
if (demo.Construct(400, 400, 2, 2))
demo.Start();
return 0;
}
// Below here is the source code compiled on MBED LPC1768, using the BufferedSerial Library
/*
#include "mbed.h"
#include "BufferedSerial.h"
PwmOut pin26(p26);
PwmOut pin25(p25);
PwmOut pin24(p24);
PwmOut pin23(p23);
PwmOut pin22(p22);
PwmOut pin21(p21);
BufferedSerial uart(p9, p10);
class Joint
{
private:
static const float fDutyMin = 0.03f; // -90
static const float fDutyMax = 0.11f; // +90
static const float fDutyRange = fDutyMax - fDutyMin;
float fTarget;
float fPosition;
float fJointMax;
float fJointMin;
public:
Joint(float fMin = -90.0f, float fMax = 90.0f, float fDefaultPos = 0.0f)
{
fJointMin = fMin;
fJointMax = fMax;
fPosition = 0.0f;
SetTarget(fDefaultPos);
}
void SetTarget(float fAngle)
{
fTarget = fAngle;
if(fTarget < fJointMin) fTarget = fJointMin;
if(fTarget > fJointMax) fTarget = fJointMax;
}
void UpdatePosition()
{
fPosition = fTarget;
}
float GetTarget()
{
return fTarget;
}
float GetDutyCycle()
{
float fDutyCycle = fPosition / (fJointMax - fJointMin);
fDutyCycle = (fDutyCycle * fDutyRange) + fDutyMin + (fDutyRange * 0.5f);
return fDutyCycle;
}
};
int main()
{
// Servos (MG996R) operate on 20ms period, so set
// PWM period for each pin
pin26.period(0.02f);
pin25.period(0.02f);
pin24.period(0.02f);
pin23.period(0.02f);
pin22.period(0.02f);
pin21.period(0.02f);
Joint joint[6];
joint[0].SetTarget(0.0f);
joint[1].SetTarget(0.0f);
joint[2].SetTarget(0.0f);
joint[3].SetTarget(-25.0f);
joint[4].SetTarget(-20.0f);
joint[5].SetTarget(-15.0f);
int nTargetJoint = 0;
while(1)
{
// Read from UART
if(uart.readable())
{
unsigned char c = (unsigned char)uart.getc();
if(c < 10)
nTargetJoint = c;
else
joint[nTargetJoint].SetTarget((float)c - 128);
}
// Write Duty Cycles
// Update each joints position
for(int i=0; i<6; i++)
joint[i].UpdatePosition();
// Set PWM values for each joint
pin26.write(joint[0].GetDutyCycle());
pin25.write(joint[1].GetDutyCycle());
pin24.write(joint[2].GetDutyCycle());
pin23.write(joint[3].GetDutyCycle());
pin22.write(joint[4].GetDutyCycle());
pin21.write(joint[5].GetDutyCycle());
}
}
*/

@ -1,272 +1,272 @@
/*
Simple example code for olcPGEX_Sound.h - Mind your speakers!
You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_SOUND
#include "olcPGEX_Sound.h"
#include <list>
class SoundTest : public olc::PixelGameEngine
{
public:
SoundTest()
{
sAppName = "Sound Test";
}
private:
int sndSampleA;
int sndSampleB;
int sndSampleC;
bool bToggle = false;
static bool bSynthPlaying;
static float fSynthFrequency;
static float fFilterVolume;
const olc::Key keys[12] = { olc::Key::Z, olc::Key::S, olc::Key::X, olc::Key::D, olc::Key::C,
olc::Key::V, olc::Key::G, olc::Key::B, olc::Key::H, olc::Key::N, olc::Key::J, olc::Key::M};
static float fPreviousSamples[128];
static int nSamplePos;
private:
// This is an optional function that allows the user to generate or synthesize sounds
// in a custom way, it is fed into the output mixer bu the extension
static float MyCustomSynthFunction(int nChannel, float fGlobalTime, float fTimeStep)
{
// Just generate a sine wave of the appropriate frequency
if (bSynthPlaying)
return sin(fSynthFrequency * 2.0f * 3.14159f * fGlobalTime);
else
return 0.0f;
}
// This is an optional function that allows the user to filter the output from
// the internal mixer of the extension. Here you could add effects or just
// control volume. I also like to use it to extract information about
// the currently playing output waveform
static float MyCustomFilterFunction(int nChannel, float fGlobalTime, float fSample)
{
// Fundamentally just control volume
float fOutput = fSample * fFilterVolume;
// But also add sample to list of previous samples for visualisation
fPreviousSamples[nSamplePos] = fOutput;
nSamplePos++;
nSamplePos %= 128;
return fOutput;
}
bool OnUserCreate()
{
olc::SOUND::InitialiseAudio(44100, 1, 8, 512);
sndSampleA = olc::SOUND::LoadAudioSample("SampleA.wav");
sndSampleB = olc::SOUND::LoadAudioSample("SampleB.wav");
sndSampleC = olc::SOUND::LoadAudioSample("SampleC.wav");
// Give the sound engine a hook to a custom generation function
olc::SOUND::SetUserSynthFunction(MyCustomSynthFunction);
// Give the sound engine a hook to a custom filtering function
olc::SOUND::SetUserFilterFunction(MyCustomFilterFunction);
return true;
}
bool OnUserUpdate(float fElapsedTime)
{
//olc::SOUND::PlaySample(sndTest);
auto PointInRect = [&](int x, int y, int rx, int ry, int rw, int rh)
{
return x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh);
};
int nMouseX = GetMouseX();
int nMouseY = GetMouseY();
if(GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 16, 128, 24))
olc::SOUND::PlaySample(sndSampleA); // Plays the sample once
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 48, 128, 24))
olc::SOUND::PlaySample(sndSampleB);
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 80, 128, 24))
{
bToggle = !bToggle;
if (bToggle)
{
olc::SOUND::PlaySample(sndSampleC, true); // Plays the sample in looping mode
}
else
{
olc::SOUND::StopSample(sndSampleC);
}
}
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 16, 90, 24))
fFilterVolume += 2.0f * fElapsedTime;
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 48, 90, 24))
fFilterVolume -= 2.0f * fElapsedTime;
if (fFilterVolume < 0.0f) fFilterVolume = 0.0f;
if (fFilterVolume > 1.0f) fFilterVolume = 1.0f;
// Detect keyboard - very simple synthesizer
if (IsFocused())
{
bool bKeyIsPressed = false;
float fFrequency = 0.0f;
for (int i = 0; i < 12; i++)
{
if (GetKey(keys[i]).bHeld)
{
bKeyIsPressed = true;
float fOctaveBaseFrequency = 220.0f;
float f12thRootOf2 = pow(2.0f, 1.0f / 12.0f);
fFrequency = fOctaveBaseFrequency * powf(f12thRootOf2, (float)i);
}
}
fSynthFrequency = fFrequency;
bSynthPlaying = bKeyIsPressed;
}
// Draw Buttons
Clear(olc::BLUE);
DrawRect(16, 16, 128, 24);
DrawString(20, 20, "Play Sample A");
DrawRect(16, 48, 128, 24);
DrawString(20, 52, "Play Sample B");
DrawRect(16, 80, 128, 24);
DrawString(20, 84, (bToggle ? "Stop Sample C" : "Loop Sample C"));
DrawRect(160, 16, 90, 24);
DrawString(164, 20, "Volume +");
DrawRect(160, 48, 90, 24);
DrawString(164, 52, "Volume -");
DrawString(164, 80, "Volume: " + std::to_string((int)(fFilterVolume * 10.0f)));
// Draw Keyboard
// White Keys
for (int i = 0; i < 7; i++)
{
FillRect(i * 16 + 8, 160, 16, 64);
DrawRect(i * 16 + 8, 160, 16, 64, olc::BLACK);
DrawString(i * 16 + 12, 212, std::string(1, "ZXCVBNM"[i]), olc::BLACK);
}
// Black Keys
for (int i = 0; i < 6; i++)
{
if (i != 2)
{
FillRect(i * 16 + 18, 160, 12, 32, olc::BLACK);
DrawString(i * 16 + 20, 180, std::string(1, "SDFGHJ"[i]), olc::WHITE);
}
}
// Draw visualisation
int nStartPos = (nSamplePos + 127) % 128;
for (int i = 127; i >= 0; i--)
{
float fSample = fPreviousSamples[(nSamplePos + i) % 128];
DrawLine(124 + i, 210, 124 + i, 210 + (int)(fSample * 20.0f), olc::RED);
}
return true;
}
// Note we must shut down the sound system too!!
bool OnUserDestroy()
{
olc::SOUND::DestroyAudio();
return true;
}
};
bool SoundTest::bSynthPlaying = false;
float SoundTest::fSynthFrequency = 0.0f;
float SoundTest::fFilterVolume = 1.0f;
int SoundTest::nSamplePos = 0;
float SoundTest::fPreviousSamples[128];
int main()
{
SoundTest demo;
if(demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
/*
Simple example code for olcPGEX_Sound.h - Mind your speakers!
You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_SOUND
#include "olcPGEX_Sound.h"
#include <list>
class SoundTest : public olc::PixelGameEngine
{
public:
SoundTest()
{
sAppName = "Sound Test";
}
private:
int sndSampleA;
int sndSampleB;
int sndSampleC;
bool bToggle = false;
static bool bSynthPlaying;
static float fSynthFrequency;
static float fFilterVolume;
const olc::Key keys[12] = { olc::Key::Z, olc::Key::S, olc::Key::X, olc::Key::D, olc::Key::C,
olc::Key::V, olc::Key::G, olc::Key::B, olc::Key::H, olc::Key::N, olc::Key::J, olc::Key::M};
static float fPreviousSamples[128];
static int nSamplePos;
private:
// This is an optional function that allows the user to generate or synthesize sounds
// in a custom way, it is fed into the output mixer bu the extension
static float MyCustomSynthFunction(int nChannel, float fGlobalTime, float fTimeStep)
{
// Just generate a sine wave of the appropriate frequency
if (bSynthPlaying)
return sin(fSynthFrequency * 2.0f * 3.14159f * fGlobalTime);
else
return 0.0f;
}
// This is an optional function that allows the user to filter the output from
// the internal mixer of the extension. Here you could add effects or just
// control volume. I also like to use it to extract information about
// the currently playing output waveform
static float MyCustomFilterFunction(int nChannel, float fGlobalTime, float fSample)
{
// Fundamentally just control volume
float fOutput = fSample * fFilterVolume;
// But also add sample to list of previous samples for visualisation
fPreviousSamples[nSamplePos] = fOutput;
nSamplePos++;
nSamplePos %= 128;
return fOutput;
}
bool OnUserCreate()
{
olc::SOUND::InitialiseAudio(44100, 1, 8, 512);
sndSampleA = olc::SOUND::LoadAudioSample("SampleA.wav");
sndSampleB = olc::SOUND::LoadAudioSample("SampleB.wav");
sndSampleC = olc::SOUND::LoadAudioSample("SampleC.wav");
// Give the sound engine a hook to a custom generation function
olc::SOUND::SetUserSynthFunction(MyCustomSynthFunction);
// Give the sound engine a hook to a custom filtering function
olc::SOUND::SetUserFilterFunction(MyCustomFilterFunction);
return true;
}
bool OnUserUpdate(float fElapsedTime)
{
//olc::SOUND::PlaySample(sndTest);
auto PointInRect = [&](int x, int y, int rx, int ry, int rw, int rh)
{
return x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh);
};
int nMouseX = GetMouseX();
int nMouseY = GetMouseY();
if(GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 16, 128, 24))
olc::SOUND::PlaySample(sndSampleA); // Plays the sample once
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 48, 128, 24))
olc::SOUND::PlaySample(sndSampleB);
if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 80, 128, 24))
{
bToggle = !bToggle;
if (bToggle)
{
olc::SOUND::PlaySample(sndSampleC, true); // Plays the sample in looping mode
}
else
{
olc::SOUND::StopSample(sndSampleC);
}
}
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 16, 90, 24))
fFilterVolume += 2.0f * fElapsedTime;
if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 48, 90, 24))
fFilterVolume -= 2.0f * fElapsedTime;
if (fFilterVolume < 0.0f) fFilterVolume = 0.0f;
if (fFilterVolume > 1.0f) fFilterVolume = 1.0f;
// Detect keyboard - very simple synthesizer
if (IsFocused())
{
bool bKeyIsPressed = false;
float fFrequency = 0.0f;
for (int i = 0; i < 12; i++)
{
if (GetKey(keys[i]).bHeld)
{
bKeyIsPressed = true;
float fOctaveBaseFrequency = 220.0f;
float f12thRootOf2 = pow(2.0f, 1.0f / 12.0f);
fFrequency = fOctaveBaseFrequency * powf(f12thRootOf2, (float)i);
}
}
fSynthFrequency = fFrequency;
bSynthPlaying = bKeyIsPressed;
}
// Draw Buttons
Clear(olc::BLUE);
DrawRect(16, 16, 128, 24);
DrawString(20, 20, "Play Sample A");
DrawRect(16, 48, 128, 24);
DrawString(20, 52, "Play Sample B");
DrawRect(16, 80, 128, 24);
DrawString(20, 84, (bToggle ? "Stop Sample C" : "Loop Sample C"));
DrawRect(160, 16, 90, 24);
DrawString(164, 20, "Volume +");
DrawRect(160, 48, 90, 24);
DrawString(164, 52, "Volume -");
DrawString(164, 80, "Volume: " + std::to_string((int)(fFilterVolume * 10.0f)));
// Draw Keyboard
// White Keys
for (int i = 0; i < 7; i++)
{
FillRect(i * 16 + 8, 160, 16, 64);
DrawRect(i * 16 + 8, 160, 16, 64, olc::BLACK);
DrawString(i * 16 + 12, 212, std::string(1, "ZXCVBNM"[i]), olc::BLACK);
}
// Black Keys
for (int i = 0; i < 6; i++)
{
if (i != 2)
{
FillRect(i * 16 + 18, 160, 12, 32, olc::BLACK);
DrawString(i * 16 + 20, 180, std::string(1, "SDFGHJ"[i]), olc::WHITE);
}
}
// Draw visualisation
int nStartPos = (nSamplePos + 127) % 128;
for (int i = 127; i >= 0; i--)
{
float fSample = fPreviousSamples[(nSamplePos + i) % 128];
DrawLine(124 + i, 210, 124 + i, 210 + (int)(fSample * 20.0f), olc::RED);
}
return true;
}
// Note we must shut down the sound system too!!
bool OnUserDestroy()
{
olc::SOUND::DestroyAudio();
return true;
}
};
bool SoundTest::bSynthPlaying = false;
float SoundTest::fSynthFrequency = 0.0f;
float SoundTest::fFilterVolume = 1.0f;
int SoundTest::nSamplePos = 0;
float SoundTest::fPreviousSamples[128];
int main()
{
SoundTest demo;
if(demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
}

@ -1,257 +1,257 @@
/*
OneLoneCoder.com - 2D Sprite Affine Transformations
"No more 90 degree movements" - @Javidx9
Background
~~~~~~~~~~
The sophistication of 2D engines is enhanced when the programmer is
able to rotate and scale sprites in a convenient manner. This program
shows the basics of how affine transformations accomplish this.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Patreon: https://www.patreon.com/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
https://youtu.be/zxwLN2blwbQ
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <algorithm>
#undef min
#undef max
class SpriteTransforms : public olc::PixelGameEngine
{
public:
SpriteTransforms()
{
sAppName = "Sprite Transforms";
}
private:
olc::Sprite *sprCar;
struct matrix3x3
{
float m[3][3];
};
void Identity(matrix3x3 &mat)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Translate(matrix3x3 &mat, float ox, float oy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = ox;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = oy;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Rotate(matrix3x3 &mat, float fTheta)
{
mat.m[0][0] = cosf(fTheta); mat.m[1][0] = sinf(fTheta); mat.m[2][0] = 0.0f;
mat.m[0][1] = -sinf(fTheta); mat.m[1][1] = cosf(fTheta); mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Scale(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = sx; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = sy; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Shear(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = sx; mat.m[2][0] = 0.0f;
mat.m[0][1] = sy; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void MatrixMultiply(matrix3x3 &matResult, matrix3x3 &matA, matrix3x3 &matB)
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matResult.m[c][r] = matA.m[0][r] * matB.m[c][0] +
matA.m[1][r] * matB.m[c][1] +
matA.m[2][r] * matB.m[c][2];
}
}
}
void Forward(matrix3x3 &mat, float in_x, float in_y, float &out_x, float &out_y)
{
out_x = in_x * mat.m[0][0] + in_y * mat.m[1][0] + mat.m[2][0];
out_y = in_x * mat.m[0][1] + in_y * mat.m[1][1] + mat.m[2][1];
}
void Invert(matrix3x3 &matIn, matrix3x3 &matOut)
{
float det = matIn.m[0][0] * (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) -
matIn.m[1][0] * (matIn.m[0][1] * matIn.m[2][2] - matIn.m[2][1] * matIn.m[0][2]) +
matIn.m[2][0] * (matIn.m[0][1] * matIn.m[1][2] - matIn.m[1][1] * matIn.m[0][2]);
float idet = 1.0f / det;
matOut.m[0][0] = (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) * idet;
matOut.m[1][0] = (matIn.m[2][0] * matIn.m[1][2] - matIn.m[1][0] * matIn.m[2][2]) * idet;
matOut.m[2][0] = (matIn.m[1][0] * matIn.m[2][1] - matIn.m[2][0] * matIn.m[1][1]) * idet;
matOut.m[0][1] = (matIn.m[2][1] * matIn.m[0][2] - matIn.m[0][1] * matIn.m[2][2]) * idet;
matOut.m[1][1] = (matIn.m[0][0] * matIn.m[2][2] - matIn.m[2][0] * matIn.m[0][2]) * idet;
matOut.m[2][1] = (matIn.m[0][1] * matIn.m[2][0] - matIn.m[0][0] * matIn.m[2][1]) * idet;
matOut.m[0][2] = (matIn.m[0][1] * matIn.m[1][2] - matIn.m[0][2] * matIn.m[1][1]) * idet;
matOut.m[1][2] = (matIn.m[0][2] * matIn.m[1][0] - matIn.m[0][0] * matIn.m[1][2]) * idet;
matOut.m[2][2] = (matIn.m[0][0] * matIn.m[1][1] - matIn.m[0][1] * matIn.m[1][0]) * idet;
}
float fRotate = 0.0f;
public:
bool OnUserCreate() override
{
sprCar = new olc::Sprite("car_top1.png");
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
if (GetKey(olc::Key::Z).bHeld) fRotate -= 2.0f * fElapsedTime;
if (GetKey(olc::Key::X).bHeld) fRotate += 2.0f * fElapsedTime;
Clear(olc::DARK_CYAN);
SetPixelMode(olc::Pixel::ALPHA);
//DrawSprite(0, 0, sprCar, 3);
matrix3x3 matFinal, matA, matB, matC, matFinalInv;
Translate(matA, -100, -50);
Rotate(matB, fRotate);
MatrixMultiply(matC, matB, matA);
Translate(matA, (float)ScreenWidth()/2, (float)ScreenHeight()/2);
MatrixMultiply(matFinal, matA, matC);
Invert(matFinal, matFinalInv);
// Draws the dumb way, but leaves gaps
/*for (int x = 0; x < sprCar->width; x++)
{
for (int y = 0; y < sprCar->height; y++)
{
olc::Pixel p = sprCar->GetPixel(x, y);
float nx, ny;
Forward(matFinal, (float)x, (float)y, nx, ny);
Draw(nx, ny, p);
}
}*/
// Work out bounding box of sprite post-transformation
// by passing through sprite corner locations into
// transformation matrix
float ex, ey;
float sx, sy;
float px, py;
Forward(matFinal, 0.0f, 0.0f, px, py);
sx = px; sy = py;
ex = px; ey = py;
Forward(matFinal, (float)sprCar->width, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, 0.0f, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, (float)sprCar->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Use transformed corner locations in screen space to establish
// region of pixels to fill, using inverse transform to sample
// sprite at suitable locations.
for (int x = sx; x < ex; x++)
{
for (int y = sy; y < ey; y++)
{
float nx, ny;
Forward(matFinalInv, (float)x, (float)y, nx, ny);
olc::Pixel p = sprCar->GetPixel((int32_t)(nx + 0.5f), (int32_t)(ny + 0.5f));
Draw(x, y, p);
}
}
SetPixelMode(olc::Pixel::NORMAL);
return true;
}
};
int main()
{
SpriteTransforms demo;
if (demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
/*
OneLoneCoder.com - 2D Sprite Affine Transformations
"No more 90 degree movements" - @Javidx9
Background
~~~~~~~~~~
The sophistication of 2D engines is enhanced when the programmer is
able to rotate and scale sprites in a convenient manner. This program
shows the basics of how affine transformations accomplish this.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Patreon: https://www.patreon.com/javidx9
Homepage: https://www.onelonecoder.com
Relevant Videos
~~~~~~~~~~~~~~~
https://youtu.be/zxwLN2blwbQ
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#include <algorithm>
#undef min
#undef max
class SpriteTransforms : public olc::PixelGameEngine
{
public:
SpriteTransforms()
{
sAppName = "Sprite Transforms";
}
private:
olc::Sprite *sprCar;
struct matrix3x3
{
float m[3][3];
};
void Identity(matrix3x3 &mat)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Translate(matrix3x3 &mat, float ox, float oy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = ox;
mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = oy;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Rotate(matrix3x3 &mat, float fTheta)
{
mat.m[0][0] = cosf(fTheta); mat.m[1][0] = sinf(fTheta); mat.m[2][0] = 0.0f;
mat.m[0][1] = -sinf(fTheta); mat.m[1][1] = cosf(fTheta); mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Scale(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = sx; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f;
mat.m[0][1] = 0.0f; mat.m[1][1] = sy; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void Shear(matrix3x3 &mat, float sx, float sy)
{
mat.m[0][0] = 1.0f; mat.m[1][0] = sx; mat.m[2][0] = 0.0f;
mat.m[0][1] = sy; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f;
mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f;
}
void MatrixMultiply(matrix3x3 &matResult, matrix3x3 &matA, matrix3x3 &matB)
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matResult.m[c][r] = matA.m[0][r] * matB.m[c][0] +
matA.m[1][r] * matB.m[c][1] +
matA.m[2][r] * matB.m[c][2];
}
}
}
void Forward(matrix3x3 &mat, float in_x, float in_y, float &out_x, float &out_y)
{
out_x = in_x * mat.m[0][0] + in_y * mat.m[1][0] + mat.m[2][0];
out_y = in_x * mat.m[0][1] + in_y * mat.m[1][1] + mat.m[2][1];
}
void Invert(matrix3x3 &matIn, matrix3x3 &matOut)
{
float det = matIn.m[0][0] * (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) -
matIn.m[1][0] * (matIn.m[0][1] * matIn.m[2][2] - matIn.m[2][1] * matIn.m[0][2]) +
matIn.m[2][0] * (matIn.m[0][1] * matIn.m[1][2] - matIn.m[1][1] * matIn.m[0][2]);
float idet = 1.0f / det;
matOut.m[0][0] = (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) * idet;
matOut.m[1][0] = (matIn.m[2][0] * matIn.m[1][2] - matIn.m[1][0] * matIn.m[2][2]) * idet;
matOut.m[2][0] = (matIn.m[1][0] * matIn.m[2][1] - matIn.m[2][0] * matIn.m[1][1]) * idet;
matOut.m[0][1] = (matIn.m[2][1] * matIn.m[0][2] - matIn.m[0][1] * matIn.m[2][2]) * idet;
matOut.m[1][1] = (matIn.m[0][0] * matIn.m[2][2] - matIn.m[2][0] * matIn.m[0][2]) * idet;
matOut.m[2][1] = (matIn.m[0][1] * matIn.m[2][0] - matIn.m[0][0] * matIn.m[2][1]) * idet;
matOut.m[0][2] = (matIn.m[0][1] * matIn.m[1][2] - matIn.m[0][2] * matIn.m[1][1]) * idet;
matOut.m[1][2] = (matIn.m[0][2] * matIn.m[1][0] - matIn.m[0][0] * matIn.m[1][2]) * idet;
matOut.m[2][2] = (matIn.m[0][0] * matIn.m[1][1] - matIn.m[0][1] * matIn.m[1][0]) * idet;
}
float fRotate = 0.0f;
public:
bool OnUserCreate() override
{
sprCar = new olc::Sprite("car_top1.png");
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
if (GetKey(olc::Key::Z).bHeld) fRotate -= 2.0f * fElapsedTime;
if (GetKey(olc::Key::X).bHeld) fRotate += 2.0f * fElapsedTime;
Clear(olc::DARK_CYAN);
SetPixelMode(olc::Pixel::ALPHA);
//DrawSprite(0, 0, sprCar, 3);
matrix3x3 matFinal, matA, matB, matC, matFinalInv;
Translate(matA, -100, -50);
Rotate(matB, fRotate);
MatrixMultiply(matC, matB, matA);
Translate(matA, (float)ScreenWidth()/2, (float)ScreenHeight()/2);
MatrixMultiply(matFinal, matA, matC);
Invert(matFinal, matFinalInv);
// Draws the dumb way, but leaves gaps
/*for (int x = 0; x < sprCar->width; x++)
{
for (int y = 0; y < sprCar->height; y++)
{
olc::Pixel p = sprCar->GetPixel(x, y);
float nx, ny;
Forward(matFinal, (float)x, (float)y, nx, ny);
Draw(nx, ny, p);
}
}*/
// Work out bounding box of sprite post-transformation
// by passing through sprite corner locations into
// transformation matrix
float ex, ey;
float sx, sy;
float px, py;
Forward(matFinal, 0.0f, 0.0f, px, py);
sx = px; sy = py;
ex = px; ey = py;
Forward(matFinal, (float)sprCar->width, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, 0.0f, (float)sprCar->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
Forward(matFinal, (float)sprCar->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Use transformed corner locations in screen space to establish
// region of pixels to fill, using inverse transform to sample
// sprite at suitable locations.
for (int x = sx; x < ex; x++)
{
for (int y = sy; y < ey; y++)
{
float nx, ny;
Forward(matFinalInv, (float)x, (float)y, nx, ny);
olc::Pixel p = sprCar->GetPixel((int32_t)(nx + 0.5f), (int32_t)(ny + 0.5f));
Draw(x, y, p);
}
}
SetPixelMode(olc::Pixel::NORMAL);
return true;
}
};
int main()
{
SpriteTransforms demo;
if (demo.Construct(256, 240, 4, 4))
demo.Start();
return 0;
}

@ -1,224 +1,224 @@
#pragma once
#ifdef WIN32
#include <windows.h>
#include <xinput.h>
typedef DWORD(WINAPI XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE* pState);
static XInputGetState_t* XInputStateGet;
typedef DWORD(WINAPI XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration);
static XInputSetState_t* XInputStateSet;
#endif
#define C_BUTTON_COUNT 14
enum CButton
{
UP,
DOWN,
LEFT,
RIGHT,
START,
BACK,
A,
B,
X,
Y,
LEFT_SHOULDER,
RIGHT_SHOULDER,
LEFT_THUMB,
RIGHT_THUMB
};
struct CBState
{
bool bPressed = false;
bool bReleased = false;
bool bHeld = false;
};
class ControllerManager
{
private:
bool buttonState[C_BUTTON_COUNT];
bool lastButtonState[C_BUTTON_COUNT];
// Trigger values are in the range of 0 to 1, where 0 is fully
// released and 1 is fully pressed.
float triggerLeft = 0;
float triggerRight = 0;
// Stick values are in the range of -1 to 1. For X values, -1 is
// all the way to the left while +1 is all the way to the right.
float leftStickX = 0;
float leftStickY = 0;
float rightStickX = 0;
float rightStickY = 0;
// Whether or not the controller is plugged in.
bool pluggedIn = true;
bool vibrating = false;
float vibrateTime = 0;
float vibrateCounter = 0;
public:
bool Initialize();
void Update(float dt);
void Vibrate(short amt, int timeMs);
CBState GetButton(CButton button);
float GetLeftTrigger() { return triggerLeft; }
float GetRightTrigger() { return triggerRight; }
float GetLeftStickX() { return leftStickX; }
float GetLeftStickY() { return leftStickY; }
float GetRightStickX() { return rightStickX; }
float GetRightStickY() { return rightStickY; }
bool IsVibrating() { return vibrating; }
bool IsPluggedIn() { return pluggedIn; }
private:
float NormalizeStickValue(short value);
};
bool ControllerManager::Initialize()
{
#ifdef WIN32
// TODO: Should we check for version 9.1.0 if we fail to find 1.4?
HMODULE lib = LoadLibraryA("xinput1_4.dll");
if (!lib) return false;
XInputStateGet = (XInputGetState_t*)GetProcAddress(lib, "XInputGetState");
XInputStateSet = (XInputSetState_t*)GetProcAddress(lib, "XInputSetState");
#endif
return true;
}
float ControllerManager::NormalizeStickValue(short value)
{
// The value we are given is in the range -32768 to 32767 with some deadzone around zero.
// We will assume all values in this dead zone to be a reading of zero (the stick is not moved).
if (value > -7000 && value < 7000) return 0;
// Otherwise, we are going to normalize the value.
return ((value + 32768.0f) / (32768.0f + 32767.0f) * 2) - 1;
}
void ControllerManager::Vibrate(short amt, int timeMs)
{
// If we are already vibrating, just ignore this, unless they say zero, in which case we will let them stop it.
if (vibrating && amt != 0) return;
// Only start the timer if we are actually vibrating.
if (amt != 0)
{
vibrateTime = timeMs / 1000.0f;
vibrating = true;
}
#ifdef WIN32
XINPUT_VIBRATION info =
{
amt,
amt
};
XInputStateSet(0, &info);
#endif
}
CBState ControllerManager::GetButton(CButton button)
{
return
{
!lastButtonState[button] && buttonState[button],
lastButtonState[button] && !buttonState[button],
lastButtonState[button] && buttonState[button]
};
}
void ControllerManager::Update(float dt)
{
#ifdef WIN32
if (vibrating)
{
vibrateCounter += dt;
if (vibrateCounter >= vibrateTime)
{
XINPUT_VIBRATION info =
{
0, 0
};
XInputStateSet(0, &info);
vibrating = false;
vibrateCounter = 0;
vibrateTime = 0;
}
}
for (int i = 0; i < C_BUTTON_COUNT; i++)
{
lastButtonState[i] = buttonState[i];
}
XINPUT_STATE state;
// Try and get the first controller. For now we will only support a single one.
DWORD res = XInputStateGet(0, &state);
// If the controller is plugged in, handle input.
if (res == ERROR_SUCCESS)
{
XINPUT_GAMEPAD* pad = &state.Gamepad;
buttonState[UP] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_UP);
buttonState[DOWN] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN);
buttonState[LEFT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT);
buttonState[RIGHT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);
buttonState[START] = (pad->wButtons & XINPUT_GAMEPAD_START);
buttonState[BACK] = (pad->wButtons & XINPUT_GAMEPAD_BACK);
buttonState[LEFT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
buttonState[RIGHT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
buttonState[LEFT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
buttonState[RIGHT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
buttonState[A] = (pad->wButtons & XINPUT_GAMEPAD_A);
buttonState[B] = (pad->wButtons & XINPUT_GAMEPAD_B);
buttonState[X] = (pad->wButtons & XINPUT_GAMEPAD_X);
buttonState[Y] = (pad->wButtons & XINPUT_GAMEPAD_Y);
triggerLeft = pad->bLeftTrigger / 255.0f;
triggerRight = pad->bRightTrigger / 255.0f;
leftStickX = NormalizeStickValue(pad->sThumbLX);
leftStickY = NormalizeStickValue(pad->sThumbLY);
rightStickX = NormalizeStickValue(pad->sThumbRX);
rightStickY = NormalizeStickValue(pad->sThumbRY);
if (!pluggedIn)
{
pluggedIn = true;
// Send callback.
// printf("Plugged in.\n");
}
}
else
{
if (pluggedIn)
{
pluggedIn = false;
// Send callback.
// printf("Unplugged.\n");
}
}
#else
for (int i = 0; i < C_BUTTON_COUNT; i++)
{
lastButtonState[i] = buttonState[i] = false;
}
#endif
#pragma once
#ifdef WIN32
#include <windows.h>
#include <xinput.h>
typedef DWORD(WINAPI XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE* pState);
static XInputGetState_t* XInputStateGet;
typedef DWORD(WINAPI XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration);
static XInputSetState_t* XInputStateSet;
#endif
#define C_BUTTON_COUNT 14
enum CButton
{
UP,
DOWN,
LEFT,
RIGHT,
START,
BACK,
A,
B,
X,
Y,
LEFT_SHOULDER,
RIGHT_SHOULDER,
LEFT_THUMB,
RIGHT_THUMB
};
struct CBState
{
bool bPressed = false;
bool bReleased = false;
bool bHeld = false;
};
class ControllerManager
{
private:
bool buttonState[C_BUTTON_COUNT];
bool lastButtonState[C_BUTTON_COUNT];
// Trigger values are in the range of 0 to 1, where 0 is fully
// released and 1 is fully pressed.
float triggerLeft = 0;
float triggerRight = 0;
// Stick values are in the range of -1 to 1. For X values, -1 is
// all the way to the left while +1 is all the way to the right.
float leftStickX = 0;
float leftStickY = 0;
float rightStickX = 0;
float rightStickY = 0;
// Whether or not the controller is plugged in.
bool pluggedIn = true;
bool vibrating = false;
float vibrateTime = 0;
float vibrateCounter = 0;
public:
bool Initialize();
void Update(float dt);
void Vibrate(short amt, int timeMs);
CBState GetButton(CButton button);
float GetLeftTrigger() { return triggerLeft; }
float GetRightTrigger() { return triggerRight; }
float GetLeftStickX() { return leftStickX; }
float GetLeftStickY() { return leftStickY; }
float GetRightStickX() { return rightStickX; }
float GetRightStickY() { return rightStickY; }
bool IsVibrating() { return vibrating; }
bool IsPluggedIn() { return pluggedIn; }
private:
float NormalizeStickValue(short value);
};
bool ControllerManager::Initialize()
{
#ifdef WIN32
// TODO: Should we check for version 9.1.0 if we fail to find 1.4?
HMODULE lib = LoadLibraryA("xinput1_4.dll");
if (!lib) return false;
XInputStateGet = (XInputGetState_t*)GetProcAddress(lib, "XInputGetState");
XInputStateSet = (XInputSetState_t*)GetProcAddress(lib, "XInputSetState");
#endif
return true;
}
float ControllerManager::NormalizeStickValue(short value)
{
// The value we are given is in the range -32768 to 32767 with some deadzone around zero.
// We will assume all values in this dead zone to be a reading of zero (the stick is not moved).
if (value > -7000 && value < 7000) return 0;
// Otherwise, we are going to normalize the value.
return ((value + 32768.0f) / (32768.0f + 32767.0f) * 2) - 1;
}
void ControllerManager::Vibrate(short amt, int timeMs)
{
// If we are already vibrating, just ignore this, unless they say zero, in which case we will let them stop it.
if (vibrating && amt != 0) return;
// Only start the timer if we are actually vibrating.
if (amt != 0)
{
vibrateTime = timeMs / 1000.0f;
vibrating = true;
}
#ifdef WIN32
XINPUT_VIBRATION info =
{
amt,
amt
};
XInputStateSet(0, &info);
#endif
}
CBState ControllerManager::GetButton(CButton button)
{
return
{
!lastButtonState[button] && buttonState[button],
lastButtonState[button] && !buttonState[button],
lastButtonState[button] && buttonState[button]
};
}
void ControllerManager::Update(float dt)
{
#ifdef WIN32
if (vibrating)
{
vibrateCounter += dt;
if (vibrateCounter >= vibrateTime)
{
XINPUT_VIBRATION info =
{
0, 0
};
XInputStateSet(0, &info);
vibrating = false;
vibrateCounter = 0;
vibrateTime = 0;
}
}
for (int i = 0; i < C_BUTTON_COUNT; i++)
{
lastButtonState[i] = buttonState[i];
}
XINPUT_STATE state;
// Try and get the first controller. For now we will only support a single one.
DWORD res = XInputStateGet(0, &state);
// If the controller is plugged in, handle input.
if (res == ERROR_SUCCESS)
{
XINPUT_GAMEPAD* pad = &state.Gamepad;
buttonState[UP] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_UP);
buttonState[DOWN] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN);
buttonState[LEFT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT);
buttonState[RIGHT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);
buttonState[START] = (pad->wButtons & XINPUT_GAMEPAD_START);
buttonState[BACK] = (pad->wButtons & XINPUT_GAMEPAD_BACK);
buttonState[LEFT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
buttonState[RIGHT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
buttonState[LEFT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
buttonState[RIGHT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
buttonState[A] = (pad->wButtons & XINPUT_GAMEPAD_A);
buttonState[B] = (pad->wButtons & XINPUT_GAMEPAD_B);
buttonState[X] = (pad->wButtons & XINPUT_GAMEPAD_X);
buttonState[Y] = (pad->wButtons & XINPUT_GAMEPAD_Y);
triggerLeft = pad->bLeftTrigger / 255.0f;
triggerRight = pad->bRightTrigger / 255.0f;
leftStickX = NormalizeStickValue(pad->sThumbLX);
leftStickY = NormalizeStickValue(pad->sThumbLY);
rightStickX = NormalizeStickValue(pad->sThumbRX);
rightStickY = NormalizeStickValue(pad->sThumbRY);
if (!pluggedIn)
{
pluggedIn = true;
// Send callback.
// printf("Plugged in.\n");
}
}
else
{
if (pluggedIn)
{
pluggedIn = false;
// Send callback.
// printf("Unplugged.\n");
}
}
#else
for (int i = 0; i < C_BUTTON_COUNT; i++)
{
lastButtonState[i] = buttonState[i] = false;
}
#endif
}

@ -1,42 +1,42 @@
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9

@ -1,313 +1,313 @@
/*
olcPGEX_Graphics2D.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
This is an extension to the olcPixelGameEngine, which provides
advanced olc::Sprite manipulation and drawing routines. To use
it, simply include this header file.
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
Matrices stored as [Column][Row] (i.e. x, y)
|C0R0 C1R0 C2R0| | x | | x'|
|C0R1 C1R1 C2R1| * | y | = | y'|
|C0R2 C1R2 C2R2| |1.0| | - |
*/
#ifndef OLC_PGEX_GFX2D
#define OLC_PGEX_GFX2D
#include <algorithm>
#undef min
#undef max
namespace olc
{
// Container class for Advanced 2D Drawing functions
class GFX2D : public olc::PGEX
{
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class Transform2D
{
public:
Transform2D();
public:
// Set this transformation to unity
void Reset();
// Append a rotation of fTheta radians to this transform
void Rotate(float fTheta);
// Append a translation (ox, oy) to this transform
void Translate(float ox, float oy);
// Append a scaling operation (sx, sy) to this transform
void Scale(float sx, float sy);
// Append a shear operation (sx, sy) to this transform
void Shear(float sx, float sy);
void Perspective(float ox, float oy);
// Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
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)
void Backward(float in_x, float in_y, float &out_x, float &out_y);
// Regenerate the Inverse Transformation
void Invert();
private:
void Multiply();
float matrix[4][3][3];
int nTargetMatrix;
int nSourceMatrix;
bool bDirty;
};
public:
// Draws a sprite with the transform applied
static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
};
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
{
if (sprite == nullptr)
return;
// Work out bounding rectangle of sprite
float ex, ey;
float sx, sy;
float px, py;
transform.Forward(0.0f, 0.0f, sx, sy);
px = sx; py = sy;
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward(0.0f, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Perform inversion of transform if required
transform.Invert();
if (ex < sx)
std::swap(ex, sx);
if (ey < sy)
std::swap(ey, sy);
// Iterate through render space, and sample Sprite from suitable texel location
for (float i = sx; i < ex; i++)
{
for (float j = sy; j < ey; j++)
{
float ox, oy;
transform.Backward(i, j, ox, oy);
pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
}
}
}
olc::GFX2D::Transform2D::Transform2D()
{
Reset();
}
void olc::GFX2D::Transform2D::Reset()
{
nTargetMatrix = 0;
nSourceMatrix = 1;
bDirty = true;
// Columns Then Rows
// Matrices 0 & 1 are used as swaps in Transform accumulation
matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
// Matrix 2 is a cache matrix to hold the immediate transform operation
// Matrix 3 is a cache matrix to hold the inverted transform
}
void olc::GFX2D::Transform2D::Multiply()
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
matrix[2][2][r] * matrix[nSourceMatrix][c][2];
}
}
std::swap(nTargetMatrix, nSourceMatrix);
bDirty = true; // Any transform multiply dirties the inversion
}
void olc::GFX2D::Transform2D::Rotate(float fTheta)
{
// Construct Rotation Matrix
matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Scale(float sx, float sy)
{
// Construct Scale Matrix
matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Shear(float sx, float sy)
{
// Construct Shear Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Translate(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
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()
{
if (bDirty) // Obviously costly so only do if needed
{
float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
float idet = 1.0f / det;
matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
bDirty = false;
}
}
}
#endif
/*
olcPGEX_Graphics2D.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
This is an extension to the olcPixelGameEngine, which provides
advanced olc::Sprite manipulation and drawing routines. To use
it, simply include this header file.
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
Matrices stored as [Column][Row] (i.e. x, y)
|C0R0 C1R0 C2R0| | x | | x'|
|C0R1 C1R1 C2R1| * | y | = | y'|
|C0R2 C1R2 C2R2| |1.0| | - |
*/
#ifndef OLC_PGEX_GFX2D
#define OLC_PGEX_GFX2D
#include <algorithm>
#undef min
#undef max
namespace olc
{
// Container class for Advanced 2D Drawing functions
class GFX2D : public olc::PGEX
{
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class Transform2D
{
public:
Transform2D();
public:
// Set this transformation to unity
void Reset();
// Append a rotation of fTheta radians to this transform
void Rotate(float fTheta);
// Append a translation (ox, oy) to this transform
void Translate(float ox, float oy);
// Append a scaling operation (sx, sy) to this transform
void Scale(float sx, float sy);
// Append a shear operation (sx, sy) to this transform
void Shear(float sx, float sy);
void Perspective(float ox, float oy);
// Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
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)
void Backward(float in_x, float in_y, float &out_x, float &out_y);
// Regenerate the Inverse Transformation
void Invert();
private:
void Multiply();
float matrix[4][3][3];
int nTargetMatrix;
int nSourceMatrix;
bool bDirty;
};
public:
// Draws a sprite with the transform applied
static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
};
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
{
if (sprite == nullptr)
return;
// Work out bounding rectangle of sprite
float ex, ey;
float sx, sy;
float px, py;
transform.Forward(0.0f, 0.0f, sx, sy);
px = sx; py = sy;
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward(0.0f, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Perform inversion of transform if required
transform.Invert();
if (ex < sx)
std::swap(ex, sx);
if (ey < sy)
std::swap(ey, sy);
// Iterate through render space, and sample Sprite from suitable texel location
for (float i = sx; i < ex; i++)
{
for (float j = sy; j < ey; j++)
{
float ox, oy;
transform.Backward(i, j, ox, oy);
pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
}
}
}
olc::GFX2D::Transform2D::Transform2D()
{
Reset();
}
void olc::GFX2D::Transform2D::Reset()
{
nTargetMatrix = 0;
nSourceMatrix = 1;
bDirty = true;
// Columns Then Rows
// Matrices 0 & 1 are used as swaps in Transform accumulation
matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
// Matrix 2 is a cache matrix to hold the immediate transform operation
// Matrix 3 is a cache matrix to hold the inverted transform
}
void olc::GFX2D::Transform2D::Multiply()
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
matrix[2][2][r] * matrix[nSourceMatrix][c][2];
}
}
std::swap(nTargetMatrix, nSourceMatrix);
bDirty = true; // Any transform multiply dirties the inversion
}
void olc::GFX2D::Transform2D::Rotate(float fTheta)
{
// Construct Rotation Matrix
matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Scale(float sx, float sy)
{
// Construct Scale Matrix
matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Shear(float sx, float sy)
{
// Construct Shear Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Translate(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
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()
{
if (bDirty) // Obviously costly so only do if needed
{
float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
float idet = 1.0f / det;
matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
bDirty = false;
}
}
}
#endif
#endif

@ -1,381 +1,381 @@
#pragma once
#include "olcPixelGameEngine.h"
#include <algorithm>
#undef min
#undef max
namespace olc
{
class TILE : public olc::PGEX
{
public:
struct Edge
{
float sx, sy;
float ex, ey;
};
public:
class Atlas
{
public:
Atlas();
void Create(olc::Sprite *tileSheet);
olc::rcode LoadFromFile(std::string filename);
olc::rcode SaveToFile(std::string filename);
public:
olc::Sprite *sprTileSheet;
std::vector<std::tuple<int32_t, int32_t, int32_t, int32_t>> location;
};
public:
template <class T>
class Layer
{
public:
Layer();
void Create(int32_t w, int32_t h, int32_t tw, int32_t th);
olc::rcode LoadFromFile(std::string filename);
olc::rcode SaveToFile(std::string filename);
T* GetTile(int32_t x, int32_t y);
public:
int32_t nLayerWidth;
int32_t nLayerHeight;
int32_t nTileWidth;
int32_t nTileHeight;
private:
T *pTiles;
};
class BasicTile
{
public:
BasicTile();
public:
int32_t id;
bool exist;
int edge_id[4];
bool edge_exist[4];
};
public:
template<typename T>
static void DrawLayer(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int tiles_x, int tiles_y, int nScale = 1);
template<typename T>
static olc::Pixel GetLayerPixel(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float x, float y);
template<typename T>
static std::vector<olc::TILE::Edge> ExtractEdgesFromLayer(olc::TILE::Layer<T> &layer, int sx, int sy, int width, int height);
};
}
namespace olc
{
TILE::BasicTile::BasicTile()
{
exist = false;
id = 0;
for (int i = 0; i < 4; i++)
{
edge_exist[i] = false;
edge_id[i] = 0;
}
}
template <class T>
TILE::Layer<T>::Layer()
{
}
template <class T>
void TILE::Layer<T>::Create(int32_t w, int32_t h, int32_t tw, int32_t th)
{
nLayerWidth = w;
nLayerHeight = h;
nTileWidth = tw;
nTileHeight = th;
pTiles = new T[nLayerWidth * nLayerHeight];
for (int i = 0; i < nLayerWidth*nLayerHeight; i++)
{
pTiles[i].id = 0;
}
}
template <class T>
olc::rcode TILE::Layer<T>::LoadFromFile(std::string filename)
{
return olc::FAIL;
}
template <class T>
olc::rcode TILE::Layer<T>::SaveToFile(std::string filename)
{
return olc::FAIL;
}
template <class T>
T* TILE::Layer<T>::GetTile(int32_t x, int32_t y)
{
if (x < 0 || x >= nLayerWidth || y < 0 || y >= nLayerHeight)
return nullptr;
else
return &pTiles[y*nLayerWidth + x];
}
template<typename T>
void TILE::DrawLayer(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int32_t tiles_x, int32_t tiles_y, int nScale)
{
float fOffsetX = cam_x - (int)cam_x;
float fOffsetY = cam_y - (int)cam_y;
for (int32_t x = 0; x < tiles_x; x++)
{
for (int32_t y = 0; y < tiles_y; y++)
{
olc::TILE::BasicTile *t = layer.GetTile(x + (int)cam_x, y + (int)cam_y);
if (t != nullptr && t->exist)
{
float fx = (int)(((float)x - fOffsetX) * (float)(layer.nTileWidth));
float fy = (int)(((float)y - fOffsetY) * (float)(layer.nTileHeight));
pge->DrawPartialSprite(
fx + 0.5f - (fx < 0.0f),
fy + 0.5f - (fy < 0.0f),
atlas.sprTileSheet,
std::get<0>(atlas.location[t->id]),
std::get<1>(atlas.location[t->id]),
std::get<2>(atlas.location[t->id]),
std::get<3>(atlas.location[t->id]),
nScale);
}
}
}
}
template<typename T>
olc::Pixel TILE::GetLayerPixel(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float x, float y)
{
olc::TILE::BasicTile *t = layer.GetTile((int32_t)x, (int32_t)y);
if (t != nullptr)
{
float fOffsetX = x - (int)x;
float fOffsetY = y - (int)y;
return atlas.sprTileSheet->GetPixel(std::get<0>(atlas.location[t->id]) + fOffsetX * std::get<2>(atlas.location[t->id]),
std::get<1>(atlas.location[t->id]) + fOffsetX * std::get<3>(atlas.location[t->id]));
}
else
return olc::BLANK;
}
template<typename T>
std::vector<olc::TILE::Edge> TILE::ExtractEdgesFromLayer(olc::TILE::Layer<T> &layer, int sx, int sy, int width, int height)
{
enum
{
NORTH = 0,
EAST = 1,
SOUTH = 2,
WEST = 3
};
std::vector<olc::TILE::Edge> vecEdges;
for (int x = -1; x < width + 1; x++)
for (int y = -1; y < height + 1; y++)
for (int j = 0; j < 4; j++)
{
if ((x + sx) >= 0 && (y + sy) >= 0 && (x + sx) < (layer.nLayerWidth - 1) && (y + sy) < (layer.nLayerHeight - 1))
{
layer.GetTile(x + sx, y + sy)->edge_exist[j] = false;
layer.GetTile(x + sx, y + sy)->edge_id[j] = 0;
}
}
// Add boundary edges
vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy)*layer.nTileHeight });
// Iterate through region from top left to bottom right
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
T* i = layer.GetTile(x + sx, y + sy); //This
T* n = layer.GetTile(x + sx, y + sy - 1);
T* s = layer.GetTile(x + sx, y + sy + 1);
T* w = layer.GetTile(x + sx - 1, y + sy);
T* e = layer.GetTile(x + sx + 1, y + sy);
// If this cell exists, check if it needs edges
if (i->exist)
{
// If this cell has no western neighbour, it needs a western edge
if (w && !w->exist)
{
// It can either extend it from its northern neighbour if they have
// one, or It can start a new one.
if (n && n->edge_exist[WEST])
{
// Northern neighbour has a western edge, so grow it downwards
vecEdges[n->edge_id[WEST]].ey += layer.nTileHeight;
i->edge_id[WEST] = n->edge_id[WEST];
i->edge_exist[WEST] = true;
}
else
{
// Northern neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[WEST] = edge_id;
i->edge_exist[WEST] = true;
}
}
// If this cell dont have an eastern neignbour, It needs a eastern edge
if (e && !e->exist)
{
// It can either extend it from its northern neighbour if they have
// one, or It can start a new one.
if (n && n->edge_exist[EAST])
{
// Northern neighbour has one, so grow it downwards
vecEdges[n->edge_id[EAST]].ey += layer.nTileHeight;
i->edge_id[EAST] = n->edge_id[EAST];
i->edge_exist[EAST] = true;
}
else
{
// Northern neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x + 1) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[EAST] = edge_id;
i->edge_exist[EAST] = true;
}
}
// If this cell doesnt have a northern neignbour, It needs a northern edge
if (n && !n->exist)
{
// It can either extend it from its western neighbour if they have
// one, or It can start a new one.
if (w && w->edge_exist[NORTH])
{
// Western neighbour has one, so grow it eastwards
vecEdges[w->edge_id[NORTH]].ex += layer.nTileWidth;
i->edge_id[NORTH] = w->edge_id[NORTH];
i->edge_exist[NORTH] = true;
}
else
{
// Western neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[NORTH] = edge_id;
i->edge_exist[NORTH] = true;
}
}
// If this cell doesnt have a southern neignbour, It needs a southern edge
if (s && !s->exist)
{
// It can either extend it from its western neighbour if they have
// one, or It can start a new one.
if (w && w->edge_exist[SOUTH])
{
// Western neighbour has one, so grow it eastwards
vecEdges[w->edge_id[SOUTH]].ex += layer.nTileWidth;
i->edge_id[SOUTH] = w->edge_id[SOUTH];
i->edge_exist[SOUTH] = true;
}
else
{
// Western neighbour does not have one, so I need to create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y + 1) * layer.nTileHeight;
edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[SOUTH] = edge_id;
i->edge_exist[SOUTH] = true;
}
}
}
}
return vecEdges;
}
TILE::Atlas::Atlas()
{
}
void TILE::Atlas::Create(olc::Sprite *tileSheet)
{
sprTileSheet = tileSheet;
location.clear();
}
olc::rcode TILE::Atlas::LoadFromFile(std::string filename)
{
return olc::FAIL;
}
olc::rcode TILE::Atlas::SaveToFile(std::string filename)
{
return olc::FAIL;
}
}
#pragma once
#include "olcPixelGameEngine.h"
#include <algorithm>
#undef min
#undef max
namespace olc
{
class TILE : public olc::PGEX
{
public:
struct Edge
{
float sx, sy;
float ex, ey;
};
public:
class Atlas
{
public:
Atlas();
void Create(olc::Sprite *tileSheet);
olc::rcode LoadFromFile(std::string filename);
olc::rcode SaveToFile(std::string filename);
public:
olc::Sprite *sprTileSheet;
std::vector<std::tuple<int32_t, int32_t, int32_t, int32_t>> location;
};
public:
template <class T>
class Layer
{
public:
Layer();
void Create(int32_t w, int32_t h, int32_t tw, int32_t th);
olc::rcode LoadFromFile(std::string filename);
olc::rcode SaveToFile(std::string filename);
T* GetTile(int32_t x, int32_t y);
public:
int32_t nLayerWidth;
int32_t nLayerHeight;
int32_t nTileWidth;
int32_t nTileHeight;
private:
T *pTiles;
};
class BasicTile
{
public:
BasicTile();
public:
int32_t id;
bool exist;
int edge_id[4];
bool edge_exist[4];
};
public:
template<typename T>
static void DrawLayer(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int tiles_x, int tiles_y, int nScale = 1);
template<typename T>
static olc::Pixel GetLayerPixel(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float x, float y);
template<typename T>
static std::vector<olc::TILE::Edge> ExtractEdgesFromLayer(olc::TILE::Layer<T> &layer, int sx, int sy, int width, int height);
};
}
namespace olc
{
TILE::BasicTile::BasicTile()
{
exist = false;
id = 0;
for (int i = 0; i < 4; i++)
{
edge_exist[i] = false;
edge_id[i] = 0;
}
}
template <class T>
TILE::Layer<T>::Layer()
{
}
template <class T>
void TILE::Layer<T>::Create(int32_t w, int32_t h, int32_t tw, int32_t th)
{
nLayerWidth = w;
nLayerHeight = h;
nTileWidth = tw;
nTileHeight = th;
pTiles = new T[nLayerWidth * nLayerHeight];
for (int i = 0; i < nLayerWidth*nLayerHeight; i++)
{
pTiles[i].id = 0;
}
}
template <class T>
olc::rcode TILE::Layer<T>::LoadFromFile(std::string filename)
{
return olc::FAIL;
}
template <class T>
olc::rcode TILE::Layer<T>::SaveToFile(std::string filename)
{
return olc::FAIL;
}
template <class T>
T* TILE::Layer<T>::GetTile(int32_t x, int32_t y)
{
if (x < 0 || x >= nLayerWidth || y < 0 || y >= nLayerHeight)
return nullptr;
else
return &pTiles[y*nLayerWidth + x];
}
template<typename T>
void TILE::DrawLayer(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int32_t tiles_x, int32_t tiles_y, int nScale)
{
float fOffsetX = cam_x - (int)cam_x;
float fOffsetY = cam_y - (int)cam_y;
for (int32_t x = 0; x < tiles_x; x++)
{
for (int32_t y = 0; y < tiles_y; y++)
{
olc::TILE::BasicTile *t = layer.GetTile(x + (int)cam_x, y + (int)cam_y);
if (t != nullptr && t->exist)
{
float fx = (int)(((float)x - fOffsetX) * (float)(layer.nTileWidth));
float fy = (int)(((float)y - fOffsetY) * (float)(layer.nTileHeight));
pge->DrawPartialSprite(
fx + 0.5f - (fx < 0.0f),
fy + 0.5f - (fy < 0.0f),
atlas.sprTileSheet,
std::get<0>(atlas.location[t->id]),
std::get<1>(atlas.location[t->id]),
std::get<2>(atlas.location[t->id]),
std::get<3>(atlas.location[t->id]),
nScale);
}
}
}
}
template<typename T>
olc::Pixel TILE::GetLayerPixel(olc::TILE::Layer<T> &layer, olc::TILE::Atlas &atlas, float x, float y)
{
olc::TILE::BasicTile *t = layer.GetTile((int32_t)x, (int32_t)y);
if (t != nullptr)
{
float fOffsetX = x - (int)x;
float fOffsetY = y - (int)y;
return atlas.sprTileSheet->GetPixel(std::get<0>(atlas.location[t->id]) + fOffsetX * std::get<2>(atlas.location[t->id]),
std::get<1>(atlas.location[t->id]) + fOffsetX * std::get<3>(atlas.location[t->id]));
}
else
return olc::BLANK;
}
template<typename T>
std::vector<olc::TILE::Edge> TILE::ExtractEdgesFromLayer(olc::TILE::Layer<T> &layer, int sx, int sy, int width, int height)
{
enum
{
NORTH = 0,
EAST = 1,
SOUTH = 2,
WEST = 3
};
std::vector<olc::TILE::Edge> vecEdges;
for (int x = -1; x < width + 1; x++)
for (int y = -1; y < height + 1; y++)
for (int j = 0; j < 4; j++)
{
if ((x + sx) >= 0 && (y + sy) >= 0 && (x + sx) < (layer.nLayerWidth - 1) && (y + sy) < (layer.nLayerHeight - 1))
{
layer.GetTile(x + sx, y + sy)->edge_exist[j] = false;
layer.GetTile(x + sx, y + sy)->edge_id[j] = 0;
}
}
// Add boundary edges
vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight });
vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy)*layer.nTileHeight });
// Iterate through region from top left to bottom right
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
T* i = layer.GetTile(x + sx, y + sy); //This
T* n = layer.GetTile(x + sx, y + sy - 1);
T* s = layer.GetTile(x + sx, y + sy + 1);
T* w = layer.GetTile(x + sx - 1, y + sy);
T* e = layer.GetTile(x + sx + 1, y + sy);
// If this cell exists, check if it needs edges
if (i->exist)
{
// If this cell has no western neighbour, it needs a western edge
if (w && !w->exist)
{
// It can either extend it from its northern neighbour if they have
// one, or It can start a new one.
if (n && n->edge_exist[WEST])
{
// Northern neighbour has a western edge, so grow it downwards
vecEdges[n->edge_id[WEST]].ey += layer.nTileHeight;
i->edge_id[WEST] = n->edge_id[WEST];
i->edge_exist[WEST] = true;
}
else
{
// Northern neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[WEST] = edge_id;
i->edge_exist[WEST] = true;
}
}
// If this cell dont have an eastern neignbour, It needs a eastern edge
if (e && !e->exist)
{
// It can either extend it from its northern neighbour if they have
// one, or It can start a new one.
if (n && n->edge_exist[EAST])
{
// Northern neighbour has one, so grow it downwards
vecEdges[n->edge_id[EAST]].ey += layer.nTileHeight;
i->edge_id[EAST] = n->edge_id[EAST];
i->edge_exist[EAST] = true;
}
else
{
// Northern neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x + 1) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[EAST] = edge_id;
i->edge_exist[EAST] = true;
}
}
// If this cell doesnt have a northern neignbour, It needs a northern edge
if (n && !n->exist)
{
// It can either extend it from its western neighbour if they have
// one, or It can start a new one.
if (w && w->edge_exist[NORTH])
{
// Western neighbour has one, so grow it eastwards
vecEdges[w->edge_id[NORTH]].ex += layer.nTileWidth;
i->edge_id[NORTH] = w->edge_id[NORTH];
i->edge_exist[NORTH] = true;
}
else
{
// Western neighbour does not have one, so create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight;
edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[NORTH] = edge_id;
i->edge_exist[NORTH] = true;
}
}
// If this cell doesnt have a southern neignbour, It needs a southern edge
if (s && !s->exist)
{
// It can either extend it from its western neighbour if they have
// one, or It can start a new one.
if (w && w->edge_exist[SOUTH])
{
// Western neighbour has one, so grow it eastwards
vecEdges[w->edge_id[SOUTH]].ex += layer.nTileWidth;
i->edge_id[SOUTH] = w->edge_id[SOUTH];
i->edge_exist[SOUTH] = true;
}
else
{
// Western neighbour does not have one, so I need to create one
olc::TILE::Edge edge;
edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y + 1) * layer.nTileHeight;
edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy;
// Add edge to Polygon Pool
int edge_id = vecEdges.size();
vecEdges.push_back(edge);
// Update tile information with edge information
i->edge_id[SOUTH] = edge_id;
i->edge_exist[SOUTH] = true;
}
}
}
}
return vecEdges;
}
TILE::Atlas::Atlas()
{
}
void TILE::Atlas::Create(olc::Sprite *tileSheet)
{
sprTileSheet = tileSheet;
location.clear();
}
olc::rcode TILE::Atlas::LoadFromFile(std::string filename)
{
return olc::FAIL;
}
olc::rcode TILE::Atlas::SaveToFile(std::string filename)
{
return olc::FAIL;
}
}

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,313 @@
/*
olcPGEX_Graphics2D.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Advanced 2D Rendering - v0.4 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
This is an extension to the olcPixelGameEngine, which provides
advanced olc::Sprite manipulation and drawing routines. To use
it, simply include this header file.
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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
/*
Matrices stored as [Column][Row] (i.e. x, y)
|C0R0 C1R0 C2R0| | x | | x'|
|C0R1 C1R1 C2R1| * | y | = | y'|
|C0R2 C1R2 C2R2| |1.0| | - |
*/
#ifndef OLC_PGEX_GFX2D
#define OLC_PGEX_GFX2D
#include <algorithm>
#undef min
#undef max
namespace olc
{
// Container class for Advanced 2D Drawing functions
class GFX2D : public olc::PGEX
{
// A representation of an affine transform, used to rotate, scale, offset & shear space
public:
class Transform2D
{
public:
inline Transform2D();
public:
// Set this transformation to unity
inline void Reset();
// Append a rotation of fTheta radians to this transform
inline void Rotate(float fTheta);
// Append a translation (ox, oy) to this transform
inline void Translate(float ox, float oy);
// Append a scaling operation (sx, sy) to this transform
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)
inline void Backward(float in_x, float in_y, float &out_x, float &out_y);
// Regenerate the Inverse Transformation
inline void Invert();
private:
inline void Multiply();
float matrix[4][3][3];
int nTargetMatrix;
int nSourceMatrix;
bool bDirty;
};
public:
// Draws a sprite with the transform applied
inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
};
}
#ifdef OLC_PGE_GRAPHICS2D
#undef OLC_PGE_GRAPHICS2D
namespace olc
{
void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
{
if (sprite == nullptr)
return;
// Work out bounding rectangle of sprite
float ex, ey;
float sx, sy;
float px, py;
transform.Forward(0.0f, 0.0f, sx, sy);
px = sx; py = sy;
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward(0.0f, (float)sprite->height, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
transform.Forward((float)sprite->width, 0.0f, px, py);
sx = std::min(sx, px); sy = std::min(sy, py);
ex = std::max(ex, px); ey = std::max(ey, py);
// Perform inversion of transform if required
transform.Invert();
if (ex < sx)
std::swap(ex, sx);
if (ey < sy)
std::swap(ey, sy);
// Iterate through render space, and sample Sprite from suitable texel location
for (float i = sx; i < ex; i++)
{
for (float j = sy; j < ey; j++)
{
float ox, oy;
transform.Backward(i, j, ox, oy);
pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
}
}
}
olc::GFX2D::Transform2D::Transform2D()
{
Reset();
}
void olc::GFX2D::Transform2D::Reset()
{
nTargetMatrix = 0;
nSourceMatrix = 1;
bDirty = true;
// Columns Then Rows
// Matrices 0 & 1 are used as swaps in Transform accumulation
matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
// Matrix 2 is a cache matrix to hold the immediate transform operation
// Matrix 3 is a cache matrix to hold the inverted transform
}
void olc::GFX2D::Transform2D::Multiply()
{
for (int c = 0; c < 3; c++)
{
for (int r = 0; r < 3; r++)
{
matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
matrix[2][2][r] * matrix[nSourceMatrix][c][2];
}
}
std::swap(nTargetMatrix, nSourceMatrix);
bDirty = true; // Any transform multiply dirties the inversion
}
void olc::GFX2D::Transform2D::Rotate(float fTheta)
{
// Construct Rotation Matrix
matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Scale(float sx, float sy)
{
// Construct Scale Matrix
matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Shear(float sx, float sy)
{
// Construct Shear Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
Multiply();
}
void olc::GFX2D::Transform2D::Translate(float ox, float oy)
{
// Construct Translate Matrix
matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
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()
{
if (bDirty) // Obviously costly so only do if needed
{
float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
float idet = 1.0f / det;
matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
bDirty = false;
}
}
}
#endif
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,892 @@
/*
olcPGEX_Sound.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| Sound - v0.3 |
+-------------------------------------------------------------+
What is this?
~~~~~~~~~~~~~
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 - 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.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Patreon: https://www.patreon.com/javidx9
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019
*/
#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
class SOUND : public olc::PGEX
{
// 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:
OLC_WAVEFORMATEX wavHeader;
float *fSample = nullptr;
long nSamples = 0;
int nChannels = 0;
bool bSampleValid = false;
};
struct sCurrentlyPlayingSample
{
int nAudioSampleID = 0;
long nSamplePosition = 0;
bool bFinished = false;
bool bLoop = false;
bool bFlagForStop = false;
};
static std::list<sCurrentlyPlayingSample> listActiveSamples;
public:
static bool InitialiseAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, unsigned int nBlocks = 8, unsigned int nBlockSamples = 512);
static bool DestroyAudio();
static void SetUserSynthFunction(std::function<float(int, float, float)> func);
static void SetUserFilterFunction(std::function<float(int, float, float)> func);
public:
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);
private:
#ifdef USE_WINDOWS // Windows specific sound management
static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2);
static unsigned int m_nSampleRate;
static unsigned int m_nChannels;
static unsigned int m_nBlockCount;
static unsigned int m_nBlockSamples;
static unsigned int m_nBlockCurrent;
static short* m_pBlockMemory;
static WAVEHDR *m_pWaveHeaders;
static HWAVEOUT m_hwDevice;
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;
};
}
// 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)
{
LoadFromFile(sWavFile, pack);
}
olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, olc::ResourcePack *pack)
{
auto ReadWave = [&](std::istream &is)
{
char dump[4];
is.read(dump, sizeof(char) * 4); // Read "RIFF"
if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL;
is.read(dump, sizeof(char) * 4); // Not Interested
is.read(dump, sizeof(char) * 4); // Read "WAVE"
if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL;
// Read Wave description chunk
is.read(dump, sizeof(char) * 4); // Read "fmt "
unsigned int nHeaderSize = 0;
is.read((char*)&nHeaderSize, sizeof(unsigned int)); // Not Interested
is.read((char*)&wavHeader, nHeaderSize);// sizeof(WAVEFORMATEX)); // Read Wave Format Structure chunk
// Note the -2, because the structure has 2 bytes to indicate its own size
// which are not in the wav file
// Just check if wave format is compatible with olcPGE
if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100)
return olc::FAIL;
// Search for audio data chunk
uint32_t nChunksize = 0;
is.read(dump, sizeof(char) * 4); // Read chunk header
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(uint32_t));
}
// Finally got to data, so read it all in and convert to float samples
nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3));
nChannels = wavHeader.nChannels;
// Create floating point buffer to hold audio sample
fSample = new float[nSamples * nChannels];
float *pSample = fSample;
// Read in audio data and normalise
for (long i = 0; i < nSamples; i++)
{
for (int c = 0; c < nChannels; c++)
{
short s = 0;
if (!is.eof())
{
is.read((char*)&s, sizeof(short));
*pSample = (float)s / (float)(SHRT_MAX);
pSample++;
}
}
}
// All done, flag sound as valid
bSampleValid = true;
return olc::OK;
};
if (pack != nullptr)
{
olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile);
std::istream is(&entry);
return ReadWave(is);
}
else
{
// Read from file
std::ifstream ifs(sWavFile, std::ifstream::binary);
if (ifs.is_open())
{
return ReadWave(ifs);
}
else
return olc::FAIL;
}
}
// 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
m_bAudioThreadActive = false;
m_nSampleRate = nSampleRate;
m_nChannels = nChannels;
m_nBlockCount = nBlocks;
m_nBlockSamples = nBlockSamples;
m_nBlockFree = m_nBlockCount;
m_nBlockCurrent = 0;
m_pBlockMemory = nullptr;
m_pWaveHeaders = nullptr;
// Device is available
WAVEFORMATEX waveFormat;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nSamplesPerSec = m_nSampleRate;
waveFormat.wBitsPerSample = sizeof(short) * 8;
waveFormat.nChannels = m_nChannels;
waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
waveFormat.cbSize = 0;
listActiveSamples.clear();
// Open Device if valid
if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, CALLBACK_FUNCTION) != S_OK)
return DestroyAudio();
// Allocate Wave|Block Memory
m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples];
if (m_pBlockMemory == nullptr)
return DestroyAudio();
ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples);
m_pWaveHeaders = new WAVEHDR[m_nBlockCount];
if (m_pWaveHeaders == nullptr)
return DestroyAudio();
ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount);
// Link headers to block memory
for (unsigned int n = 0; n < m_nBlockCount; n++)
{
m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short);
m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples));
}
m_bAudioThreadActive = true;
m_AudioThread = std::thread(&SOUND::AudioThread);
// Start the ball rolling with the sound delivery thread
std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
m_cvBlockNotZero.notify_one();
return true;
}
// Stop and clean up audio system
bool SOUND::DestroyAudio()
{
m_bAudioThreadActive = false;
m_AudioThread.join();
return false;
}
// Handler for soundcard request for more data
void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2)
{
if (uMsg != WOM_DONE) return;
m_nBlockFree++;
std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
m_cvBlockNotZero.notify_one();
}
// 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)
{
// Wait for block to become available
if (m_nBlockFree == 0)
{
std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly
m_cvBlockNotZero.wait(lm);
}
// Block is here, so use it
m_nBlockFree--;
// Prepare block for processing
if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED)
waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
short nNewSample = 0;
int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples;
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[nCurrentBlock + n + c] = nNewSample;
nPreviousSample = nNewSample;
}
m_fGlobalTime = m_fGlobalTime + fTimeStep;
}
// Send block to sound device
waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
m_nBlockCurrent++;
m_nBlockCurrent %= m_nBlockCount;
}
}
unsigned int SOUND::m_nSampleRate = 0;
unsigned int SOUND::m_nChannels = 0;
unsigned int SOUND::m_nBlockCount = 0;
unsigned int SOUND::m_nBlockSamples = 0;
unsigned int SOUND::m_nBlockCurrent = 0;
short* SOUND::m_pBlockMemory = nullptr;
WAVEHDR *SOUND::m_pWaveHeaders = nullptr;
HWAVEOUT SOUND::m_hwDevice;
std::atomic<unsigned int> SOUND::m_nBlockFree = 0;
std::condition_variable SOUND::m_cvBlockNotZero;
std::mutex SOUND::m_muxBlockNotZero;
}
#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
#endif
#endif // OLC_PGEX_SOUND

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 971 B

After

Width:  |  Height:  |  Size: 971 B

@ -2,7 +2,7 @@
olcPixelGameEngine.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine v1.19 |
| OneLoneCoder Pixel Game Engine v1.20 |
| "Like the command prompt console one, but not..." - javidx9 |
+-------------------------------------------------------------+
@ -119,10 +119,12 @@
I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim,
JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice
Ralakus, Gorbit99, raoul, joshinils, benedani & MagetzUb for advice, ideas and
testing, and I'd like to extend my appreciation to the 96K YouTube followers,
47 Patreons and 4.5K Discord server members who give me the motivation to keep
testing, and I'd like to extend my appreciation to the 101K YouTube followers,
52 Patreons and 4.6K Discord server members who give me the motivation to keep
going with all this :D
Significant Contributors: @MaGetzUb, @slavka, @Dragoneye & @Gorbit99
Special thanks to those who bring gifts!
GnarGnarHead.......Domina
Gorbit99...........Bastion, Ori & The Blind Forest
@ -176,7 +178,7 @@
#ifndef OLC_PGE_DEF
#define OLC_PGE_DEF
#ifdef _WIN32
#if defined(_WIN32) // WINDOWS specific includes ==============================================
// Link to libraries
#ifndef __MINGW32__
#pragma comment(lib, "user32.lib") // Visual Studio Only
@ -202,7 +204,9 @@
#include <GL/gl.h>
typedef BOOL(WINAPI wglSwapInterval_t) (int interval);
static wglSwapInterval_t *wglSwapInterval;
#else
#endif
#ifdef __linux__ // LINUX specific includes ==============================================
#include <GL/gl.h>
#include <GL/glx.h>
#include <X11/X.h>
@ -232,6 +236,7 @@
#undef min
#undef max
#define UNUSED(x) (void)(x)
namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
{
@ -301,6 +306,8 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
inline v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; }
inline v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; }
inline T& operator [] (std::size_t i) { return *((T*)this + i); /* <-- D'oh :( */ }
inline operator v2d_generic<int>() const { return { static_cast<int32_t>(this->x), static_cast<int32_t>(this->y) }; }
inline operator v2d_generic<float>() const { return { static_cast<float>(this->x), static_cast<float>(this->y) }; }
};
template<class T> inline v2d_generic<T> operator * (const float& lhs, const v2d_generic<T>& rhs) { return v2d_generic<T>(lhs * rhs.x, lhs * rhs.y); }
@ -332,7 +339,7 @@ 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 = 0, nFileOffset = 0, nFileSize = 0; uint8_t* data = nullptr; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); }
};
public:
@ -545,10 +552,12 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
bool pMouseOldState[5]{ 0 };
HWButton pMouseState[5];
#ifdef _WIN32
#if defined(_WIN32)
HDC glDeviceContext = nullptr;
HGLRC glRenderContext = nullptr;
#else
#endif
#if defined(__linux__)
GLXContext glDeviceContext = nullptr;
GLXContext glRenderContext = nullptr;
#endif
@ -569,13 +578,15 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace
void olc_ConstructFontSheet();
#ifdef _WIN32
#if defined(_WIN32)
// Windows specific window handling
HWND olc_hWnd = nullptr;
HWND olc_WindowCreate();
std::wstring wsAppName;
static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#else
#endif
#if defined(__linux__)
// Non-Windows specific window handling
Display* olc_Display = nullptr;
Window olc_WindowRoot;
@ -654,19 +665,23 @@ namespace olc
//==========================================================
#if defined(_WIN32)
std::wstring ConvertS2W(std::string s)
{
#ifdef _WIN32
{
#ifdef __MINGW32__
wchar_t *buffer = new wchar_t[s.length() + 1];
mbstowcs(buffer, s.c_str(), s.length());
buffer[s.length()] = L'\0';
#else
int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
wchar_t* buffer = new wchar_t[count];
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count);
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count);
#endif
std::wstring w(buffer);
delete[] buffer;
return w;
#else
return L"SVN FTW!";
#endif
}
#endif
Sprite::Sprite()
{
@ -756,18 +771,10 @@ namespace olc
olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack)
{
#ifdef _WIN32
UNUSED(pack);
#if defined(_WIN32)
// Use GDI+
std::wstring wsImageFile;
#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
wsImageFile = ConvertS2W(sImageFile);
#endif
std::wstring wsImageFile = ConvertS2W(sImageFile);
Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str());
if (bmp == nullptr)
return olc::NO_FILE;
@ -785,7 +792,9 @@ namespace olc
}
delete bmp;
return olc::OK;
#else
#endif
#if defined(__linux__)
////////////////////////////////////////////////////////////////////////////
// Use libpng, Thanks to Guillaume Cottenceau
// https://gist.github.com/niw/5963798
@ -1094,12 +1103,8 @@ namespace olc
if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0)
return olc::FAIL;
#ifdef _WIN32
#ifdef UNICODE
#ifndef __MINGW32__
#if defined(_WIN32) && defined(UNICODE) && !defined(__MINGW32__)
wsAppName = ConvertS2W(sAppName);
#endif
#endif
#endif
// Load the default font sheet
olc_ConstructFontSheet();
@ -1119,11 +1124,15 @@ namespace olc
pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight);
SetDrawTarget(nullptr);
glClear(GL_COLOR_BUFFER_BIT);
#ifdef _WIN32
#if defined(_WIN32)
SwapBuffers(glDeviceContext);
#else
#endif
#if defined(__linux__)
glXSwapBuffers(olc_Display, olc_Window);
#endif
glClear(GL_COLOR_BUFFER_BIT);
olc_UpdateViewport();
}
@ -1134,21 +1143,11 @@ namespace olc
if (!olc_WindowCreate())
return olc::FAIL;
// Load libraries required for PNG file interaction
//#ifdef _WIN32
// // Windows use GDI+
// Gdiplus::GdiplusStartupInput startupInput;
// ULONG_PTR token;
// Gdiplus::GdiplusStartup(&token, &startupInput, NULL);
//#else
// // Linux use libpng
//
//#endif
// Start the thread
bAtomActive = true;
std::thread t = std::thread(&PixelGameEngine::EngineThread, this);
#ifdef _WIN32
#if defined(_WIN32)
// Handle Windows Message Loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
@ -1521,8 +1520,10 @@ namespace olc
else t2x += signx2;
}
next2:
if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x; if (maxx<t2x) maxx = t2x;
if (minx>t1x) minx = t1x;
if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x;
if (maxx<t2x) maxx = t2x;
drawline(minx, maxx, y); // Draw line from min to max points found on the y
// Now increase y
if (!changed1) t1x += signx1;
@ -1578,8 +1579,10 @@ namespace olc
}
next4:
if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x; if (maxx<t2x) maxx = t2x;
if (minx>t1x) minx = t1x;
if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x;
if (maxx<t2x) maxx = t2x;
drawline(minx, maxx, y);
if (!changed1) t1x += signx1;
t1x += t1xp;
@ -1701,7 +1704,7 @@ namespace olc
bool PixelGameEngine::OnUserCreate()
{ return false; }
bool PixelGameEngine::OnUserUpdate(float fElapsedTime)
{ return false; }
{ UNUSED(fElapsedTime); return false; }
bool PixelGameEngine::OnUserDestroy()
{ return true; }
//////////////////////////////////////////////////////////////////
@ -1743,12 +1746,9 @@ namespace olc
// Mouse coords come in screen space
// But leave in pixel space
//if (bFullScreen)
{
// Full Screen mode may have a weird viewport we must clamp to
x -= nViewX;
y -= nViewY;
}
// Full Screen mode may have a weird viewport we must clamp to
x -= nViewX;
y -= nViewY;
nMousePosXcache = (int32_t)(((float)x / (float)(nWindowWidth - (nViewX * 2)) * (float)nScreenWidth));
nMousePosYcache = (int32_t)(((float)y / (float)(nWindowHeight - (nViewY * 2)) * (float)nScreenHeight));
@ -1800,7 +1800,7 @@ namespace olc
// Our time per frame coefficient
float fElapsedTime = elapsedTime.count();
#ifndef _WIN32
#if defined(__linux__)
// Handle Xlib Message Loop - we do this in the
// same thread that OpenGL was created so we dont
// need to worry too much about multithreading with X11
@ -1958,9 +1958,11 @@ namespace olc
glEnd();
// Present Graphics to screen
#ifdef _WIN32
#if defined(_WIN32)
SwapBuffers(glDeviceContext);
#else
#endif
#if defined(__linux__)
glXSwapBuffers(olc_Display, olc_Window);
#endif
@ -1972,13 +1974,15 @@ namespace olc
fFrameTimer -= 1.0f;
std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount);
#ifdef _WIN32
#if defined(_WIN32)
#ifdef UNICODE
SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str());
#else
SetWindowText(olc_hWnd, sTitle.c_str());
#endif
#else
#endif
#if defined (__linux__)
XStoreName(olc_Display, olc_Window, sTitle.c_str());
#endif
nFrameCount = 0;
@ -1997,10 +2001,12 @@ namespace olc
}
}
#ifdef _WIN32
#if defined(_WIN32)
wglDeleteContext(glRenderContext);
PostMessage(olc_hWnd, WM_DESTROY, 0, 0);
#else
#endif
#if defined (__linux__)
glXMakeCurrent(olc_Display, None, NULL);
glXDestroyContext(olc_Display, glDeviceContext);
XDestroyWindow(olc_Display, olc_Window);
@ -2009,7 +2015,7 @@ namespace olc
}
#ifdef _WIN32
#if defined (_WIN32)
// Thanks @MaGetzUb for this, which allows sprites to be defined
// at construction, by initialising the GDI subsystem
static class GDIPlusStartup
@ -2064,7 +2070,7 @@ namespace olc
}
}
#ifdef _WIN32
#if defined(_WIN32)
HWND PixelGameEngine::olc_WindowCreate()
{
WNDCLASS wc;
@ -2228,7 +2234,9 @@ namespace olc
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
#else
#endif
#if defined(__linux__)
// Do the Linux stuff!
Display* PixelGameEngine::olc_WindowCreate()
{
@ -2325,15 +2333,16 @@ namespace olc
glSwapIntervalEXT = nullptr;
glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT");
if (glSwapIntervalEXT && !bEnableVSYNC)
glSwapIntervalEXT(olc_Display, olc_Window, 0);
else
if (glSwapIntervalEXT == nullptr && !bEnableVSYNC)
{
printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n");
printf(" Don't worry though, things will still work, it's just the\n");
printf(" frame rate will be capped to your monitors refresh rate - javidx9\n");
}
if (glSwapIntervalEXT != nullptr && !bEnableVSYNC)
glSwapIntervalEXT(olc_Display, olc_Window, 0);
return true;
}

Loading…
Cancel
Save