diff --git a/Contributions/README.md b/Contributions/README.md new file mode 100644 index 0000000..3a00410 --- /dev/null +++ b/Contributions/README.md @@ -0,0 +1,34 @@ +# 3rd Party Contributions + +These source code contributions enhance the functionality of the olcPixelGameEngine header file. They are not supported by OneLoneCoder.com or javidx9 so use them at your own risk! Though this is a nice community and to get listed here you have to be trusted... + +## PixelGameEngine Extensions (PGEX) +* Widens support for audio files into olcPGEX_Sound.h + * https://github.com/gorbit99/olcPGEX_AudioConvert +* Cross Platform Controller Support + * https://github.com/gorbit99/olcPGEX_Gamepad +* Sprite Animation, Sprite State Machine and Sprite Sheet Manipulation + * https://github.com/matt-hayward/olcPGEX_AnimatedSprite +* Additional colour constants + * https://github.com/matt-hayward/olcPGEX_AdditionalColours + +## MacOS Support +* These will potentially be absorbed into main build + * https://github.com/MumflrFumperdink/olcPGEMac + +## Build Systems +* CMake script + * https://github.com/plane000/olcPixelGameEngine/blob/master/CMakeLists.txt + +## Utilities +* Additional fonts and font handling tools + * https://github.com/gorbit99/OLC-Font +* Convert olcConsoleGameEngine ".spr" files into olc::Sprite types + * https://github.com/gorbit99/SprConverter + +## Customisations +* Version with SDL backend, and native controller support + * https://github.com/Allersnj/olcPixelGameEngineSDL + +## Cool Projects +Have you made something using olcPixelGameEngine? Contact me to get a link to it here! diff --git a/Extensions/olcPGEX_Graphics2D.h b/Extensions/olcPGEX_Graphics2D.h new file mode 100644 index 0000000..45d473e --- /dev/null +++ b/Extensions/olcPGEX_Graphics2D.h @@ -0,0 +1,313 @@ +/* + olcPGEX_Graphics2D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Advanced 2D Rendering - v0.5 | + +-------------------------------------------------------------+ + + 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 +#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_PGEX_GRAPHICS2D +#undef OLC_PGEX_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 \ No newline at end of file diff --git a/Extensions/olcPGEX_Graphics3D.h b/Extensions/olcPGEX_Graphics3D.h new file mode 100644 index 0000000..9c6dd80 --- /dev/null +++ b/Extensions/olcPGEX_Graphics3D.h @@ -0,0 +1,1174 @@ +/* + olcPGEX_Graphics3D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | 3D Rendering - v0.1 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + support for software rendering 3D graphics. + + NOTE!!! This file is under development and may change! + + 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_GFX3D +#define OLC_PGEX_GFX3D + +#include +#include +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX3D : public olc::PGEX + { + + public: + + struct vec2d + { + float x = 0; + float y = 0; + float z = 0; + }; + + struct vec3d + { + float x = 0; + float y = 0; + float z = 0; + float w = 1; // Need a 4th term to perform sensible matrix vector multiplication + }; + + struct triangle + { + vec3d p[3]; + vec2d t[3]; + olc::Pixel col; + }; + + struct mat4x4 + { + float m[4][4] = { 0 }; + }; + + struct mesh + { + std::vector tris; + }; + + class Math + { + public: + inline Math(); + public: + inline static vec3d Mat_MultiplyVector(mat4x4 &m, vec3d &i); + inline static mat4x4 Mat_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2); + inline static mat4x4 Mat_MakeIdentity(); + inline static mat4x4 Mat_MakeRotationX(float fAngleRad); + inline static mat4x4 Mat_MakeRotationY(float fAngleRad); + inline static mat4x4 Mat_MakeRotationZ(float fAngleRad); + inline static mat4x4 Mat_MakeScale(float x, float y, float z); + inline static mat4x4 Mat_MakeTranslation(float x, float y, float z); + inline static mat4x4 Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar); + inline static mat4x4 Mat_PointAt(vec3d &pos, vec3d &target, vec3d &up); + inline static mat4x4 Mat_QuickInverse(mat4x4 &m); // Only for Rotation/Translation Matrices + inline static mat4x4 Mat_Inverse(olc::GFX3D::mat4x4 &m); + + inline static vec3d Vec_Add(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Sub(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Mul(vec3d &v1, float k); + inline static vec3d Vec_Div(vec3d &v1, float k); + inline static float Vec_DotProduct(vec3d &v1, vec3d &v2); + inline static float Vec_Length(vec3d &v); + inline static vec3d Vec_Normalise(vec3d &v); + inline static vec3d Vec_CrossProduct(vec3d &v1, vec3d &v2); + inline static vec3d Vec_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd, float &t); + + inline static int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2); + }; + + enum RENDERFLAGS + { + RENDER_WIRE = 0x01, + RENDER_FLAT = 0x02, + RENDER_TEXTURED = 0x04, + RENDER_CULL_CW = 0x08, + RENDER_CULL_CCW = 0x10, + RENDER_DEPTH = 0x20, + }; + + + class PipeLine + { + public: + PipeLine(); + + public: + void SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight); + void SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up); + void SetTransform(olc::GFX3D::mat4x4 &transform); + void SetTexture(olc::Sprite *texture); + void SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col); + uint32_t Render(std::vector &triangles, uint32_t flags = RENDER_CULL_CW | RENDER_TEXTURED | RENDER_DEPTH); + + private: + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::mat4x4 matView; + olc::GFX3D::mat4x4 matWorld; + olc::Sprite *sprTexture; + float fViewX; + float fViewY; + float fViewW; + float fViewH; + }; + + + + public: + //static const int RF_TEXTURE = 0x00000001; + //static const int RF_ = 0x00000002; + + inline static void ConfigureDisplay(); + inline static void ClearDepth(); + inline static void AddTriangleToScene(olc::GFX3D::triangle &tri); + inline static void RenderScene(); + + inline static void DrawTriangleFlat(olc::GFX3D::triangle &tri); + inline static void DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col = olc::WHITE); + inline static void DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr); + inline static void TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr); + + // Draws a sprite with the transform applied + //inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + + private: + static float* m_DepthBuffer; + }; +} + + + + +namespace olc +{ + olc::GFX3D::Math::Math() + { + + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Mat_MultiplyVector(olc::GFX3D::mat4x4 &m, olc::GFX3D::vec3d &i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeIdentity() + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationX(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationY(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationZ(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeScale(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = x; + matrix.m[1][1] = y; + matrix.m[2][2] = z; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeTranslation(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MultiplyMatrix(olc::GFX3D::mat4x4 &m1, olc::GFX3D::mat4x4 &m2) + { + olc::GFX3D::mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_PointAt(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &target, olc::GFX3D::vec3d &up) + { + // Calculate new forward direction + olc::GFX3D::vec3d newForward = Vec_Sub(target, pos); + newForward = Vec_Normalise(newForward); + + // Calculate new Up direction + olc::GFX3D::vec3d a = Vec_Mul(newForward, Vec_DotProduct(up, newForward)); + olc::GFX3D::vec3d newUp = Vec_Sub(up, a); + newUp = Vec_Normalise(newUp); + + // New Right direction is easy, its just cross product + olc::GFX3D::vec3d newRight = Vec_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_QuickInverse(olc::GFX3D::mat4x4 &m) // Only for Rotation/Translation Matrices + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_Inverse(olc::GFX3D::mat4x4 &m) + { + double det; + + + mat4x4 matInv; + + matInv.m[0][0] = m.m[1][1] * m.m[2][2] * m.m[3][3] - m.m[1][1] * m.m[2][3] * m.m[3][2] - m.m[2][1] * m.m[1][2] * m.m[3][3] + m.m[2][1] * m.m[1][3] * m.m[3][2] + m.m[3][1] * m.m[1][2] * m.m[2][3] - m.m[3][1] * m.m[1][3] * m.m[2][2]; + matInv.m[1][0] = -m.m[1][0] * m.m[2][2] * m.m[3][3] + m.m[1][0] * m.m[2][3] * m.m[3][2] + m.m[2][0] * m.m[1][2] * m.m[3][3] - m.m[2][0] * m.m[1][3] * m.m[3][2] - m.m[3][0] * m.m[1][2] * m.m[2][3] + m.m[3][0] * m.m[1][3] * m.m[2][2]; + matInv.m[2][0] = m.m[1][0] * m.m[2][1] * m.m[3][3] - m.m[1][0] * m.m[2][3] * m.m[3][1] - m.m[2][0] * m.m[1][1] * m.m[3][3] + m.m[2][0] * m.m[1][3] * m.m[3][1] + m.m[3][0] * m.m[1][1] * m.m[2][3] - m.m[3][0] * m.m[1][3] * m.m[2][1]; + matInv.m[3][0] = -m.m[1][0] * m.m[2][1] * m.m[3][2] + m.m[1][0] * m.m[2][2] * m.m[3][1] + m.m[2][0] * m.m[1][1] * m.m[3][2] - m.m[2][0] * m.m[1][2] * m.m[3][1] - m.m[3][0] * m.m[1][1] * m.m[2][2] + m.m[3][0] * m.m[1][2] * m.m[2][1]; + matInv.m[0][1] = -m.m[0][1] * m.m[2][2] * m.m[3][3] + m.m[0][1] * m.m[2][3] * m.m[3][2] + m.m[2][1] * m.m[0][2] * m.m[3][3] - m.m[2][1] * m.m[0][3] * m.m[3][2] - m.m[3][1] * m.m[0][2] * m.m[2][3] + m.m[3][1] * m.m[0][3] * m.m[2][2]; + matInv.m[1][1] = m.m[0][0] * m.m[2][2] * m.m[3][3] - m.m[0][0] * m.m[2][3] * m.m[3][2] - m.m[2][0] * m.m[0][2] * m.m[3][3] + m.m[2][0] * m.m[0][3] * m.m[3][2] + m.m[3][0] * m.m[0][2] * m.m[2][3] - m.m[3][0] * m.m[0][3] * m.m[2][2]; + matInv.m[2][1] = -m.m[0][0] * m.m[2][1] * m.m[3][3] + m.m[0][0] * m.m[2][3] * m.m[3][1] + m.m[2][0] * m.m[0][1] * m.m[3][3] - m.m[2][0] * m.m[0][3] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[2][3] + m.m[3][0] * m.m[0][3] * m.m[2][1]; + matInv.m[3][1] = m.m[0][0] * m.m[2][1] * m.m[3][2] - m.m[0][0] * m.m[2][2] * m.m[3][1] - m.m[2][0] * m.m[0][1] * m.m[3][2] + m.m[2][0] * m.m[0][2] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[2][2] - m.m[3][0] * m.m[0][2] * m.m[2][1]; + matInv.m[0][2] = m.m[0][1] * m.m[1][2] * m.m[3][3] - m.m[0][1] * m.m[1][3] * m.m[3][2] - m.m[1][1] * m.m[0][2] * m.m[3][3] + m.m[1][1] * m.m[0][3] * m.m[3][2] + m.m[3][1] * m.m[0][2] * m.m[1][3] - m.m[3][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][2] = -m.m[0][0] * m.m[1][2] * m.m[3][3] + m.m[0][0] * m.m[1][3] * m.m[3][2] + m.m[1][0] * m.m[0][2] * m.m[3][3] - m.m[1][0] * m.m[0][3] * m.m[3][2] - m.m[3][0] * m.m[0][2] * m.m[1][3] + m.m[3][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][2] = m.m[0][0] * m.m[1][1] * m.m[3][3] - m.m[0][0] * m.m[1][3] * m.m[3][1] - m.m[1][0] * m.m[0][1] * m.m[3][3] + m.m[1][0] * m.m[0][3] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[1][3] - m.m[3][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][2] = -m.m[0][0] * m.m[1][1] * m.m[3][2] + m.m[0][0] * m.m[1][2] * m.m[3][1] + m.m[1][0] * m.m[0][1] * m.m[3][2] - m.m[1][0] * m.m[0][2] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[1][2] + m.m[3][0] * m.m[0][2] * m.m[1][1]; + matInv.m[0][3] = -m.m[0][1] * m.m[1][2] * m.m[2][3] + m.m[0][1] * m.m[1][3] * m.m[2][2] + m.m[1][1] * m.m[0][2] * m.m[2][3] - m.m[1][1] * m.m[0][3] * m.m[2][2] - m.m[2][1] * m.m[0][2] * m.m[1][3] + m.m[2][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][3] = m.m[0][0] * m.m[1][2] * m.m[2][3] - m.m[0][0] * m.m[1][3] * m.m[2][2] - m.m[1][0] * m.m[0][2] * m.m[2][3] + m.m[1][0] * m.m[0][3] * m.m[2][2] + m.m[2][0] * m.m[0][2] * m.m[1][3] - m.m[2][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][3] = -m.m[0][0] * m.m[1][1] * m.m[2][3] + m.m[0][0] * m.m[1][3] * m.m[2][1] + m.m[1][0] * m.m[0][1] * m.m[2][3] - m.m[1][0] * m.m[0][3] * m.m[2][1] - m.m[2][0] * m.m[0][1] * m.m[1][3] + m.m[2][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][3] = m.m[0][0] * m.m[1][1] * m.m[2][2] - m.m[0][0] * m.m[1][2] * m.m[2][1] - m.m[1][0] * m.m[0][1] * m.m[2][2] + m.m[1][0] * m.m[0][2] * m.m[2][1] + m.m[2][0] * m.m[0][1] * m.m[1][2] - m.m[2][0] * m.m[0][2] * m.m[1][1]; + + det = m.m[0][0] * matInv.m[0][0] + m.m[0][1] * matInv.m[1][0] + m.m[0][2] * matInv.m[2][0] + m.m[0][3] * matInv.m[3][0]; + // if (det == 0) return false; + + det = 1.0 / det; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + matInv.m[i][j] *= (float)det; + + return matInv; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Add(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Sub(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Mul(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Div(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float olc::GFX3D::Math::Vec_DotProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z; + } + + float olc::GFX3D::Math::Vec_Length(olc::GFX3D::vec3d &v) + { + return sqrtf(Vec_DotProduct(v, v)); + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Normalise(olc::GFX3D::vec3d &v) + { + float l = Vec_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_CrossProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_IntersectPlane(olc::GFX3D::vec3d &plane_p, olc::GFX3D::vec3d &plane_n, olc::GFX3D::vec3d &lineStart, olc::GFX3D::vec3d &lineEnd, float &t) + { + plane_n = Vec_Normalise(plane_n); + float plane_d = -Vec_DotProduct(plane_n, plane_p); + float ad = Vec_DotProduct(lineStart, plane_n); + float bd = Vec_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + olc::GFX3D::vec3d lineStartToEnd = Vec_Sub(lineEnd, lineStart); + olc::GFX3D::vec3d lineToIntersect = Vec_Mul(lineStartToEnd, t); + return Vec_Add(lineStart, lineToIntersect); + } + + + int olc::GFX3D::Math::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Math::Vec_Normalise(plane_n); + + out_tri1.t[0] = in_tri.t[0]; + out_tri2.t[0] = in_tri.t[0]; + out_tri1.t[1] = in_tri.t[1]; + out_tri2.t[1] = in_tri.t[1]; + out_tri1.t[2] = in_tri.t[2]; + out_tri2.t[2] = in_tri.t[2]; + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d &p) + { + vec3d n = Math::Vec_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Math::Vec_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.t[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.t[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.t[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.t[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.t[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.t[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col = olc::MAGENTA;// in_tri.col; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[1].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[1].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[1].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.t[2].x = t * (outside_tex[1]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[1]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[1]->z - inside_tex[0]->z) + inside_tex[0]->z; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col = olc::GREEN;// in_tri.col; + out_tri2.col = olc::RED;// in_tri.col; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + out_tri1.p[1] = *inside_points[1]; + out_tri1.t[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[2].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[1] = *inside_points[1]; + out_tri2.t[1] = *inside_tex[1]; + out_tri2.p[0] = out_tri1.p[2]; + out_tri2.t[0] = out_tri1.t[2]; + out_tri2.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.t[2].x = t * (outside_tex[0]->x - inside_tex[1]->x) + inside_tex[1]->x; + out_tri2.t[2].y = t * (outside_tex[0]->y - inside_tex[1]->y) + inside_tex[1]->y; + out_tri2.t[2].z = t * (outside_tex[0]->z - inside_tex[1]->z) + inside_tex[1]->z; + return 2; // Return two newly formed triangles which form a quad + } + + return 0; + } + + void GFX3D::DrawTriangleFlat(olc::GFX3D::triangle &tri) + { + pge->FillTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, tri.col); + } + + void GFX3D::DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col) + { + pge->DrawTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, col); + } + + void GFX3D::TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr) + + { + if (y2 < y1) + { + std::swap(y1, y2); + std::swap(x1, x2); + std::swap(u1, u2); + std::swap(v1, v2); + std::swap(w1, w2); + } + + if (y3 < y1) + { + std::swap(y1, y3); + std::swap(x1, x3); + std::swap(u1, u3); + std::swap(v1, v3); + std::swap(w1, w3); + } + + if (y3 < y2) + { + std::swap(y2, y3); + std::swap(x2, x3); + std::swap(u2, u3); + std::swap(v2, v3); + std::swap(w2, w3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + + float tex_u, tex_v, tex_w; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0, dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + } + } + } + + + void GFX3D::DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr) + { + if (tri.p[1].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[1].y); + std::swap(tri.p[0].x, tri.p[1].x); + std::swap(tri.t[0].x, tri.t[1].x); + std::swap(tri.t[0].y, tri.t[1].y); + std::swap(tri.t[0].z, tri.t[1].z); + } + + if (tri.p[2].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[2].y); + std::swap(tri.p[0].x, tri.p[2].x); + std::swap(tri.t[0].x, tri.t[2].x); + std::swap(tri.t[0].y, tri.t[2].y); + std::swap(tri.t[0].z, tri.t[2].z); + } + + if (tri.p[2].y < tri.p[1].y) + { + std::swap(tri.p[1].y, tri.p[2].y); + std::swap(tri.p[1].x, tri.p[2].x); + std::swap(tri.t[1].x, tri.t[2].x); + std::swap(tri.t[1].y, tri.t[2].y); + std::swap(tri.t[1].z, tri.t[2].z); + } + + int dy1 = tri.p[1].y - tri.p[0].y; + int dx1 = tri.p[1].x - tri.p[0].x; + float dv1 = tri.t[1].y - tri.t[0].y; + float du1 = tri.t[1].x - tri.t[0].x; + float dz1 = tri.t[1].z - tri.t[0].z; + + int dy2 = tri.p[2].y - tri.p[0].y; + int dx2 = tri.p[2].x - tri.p[0].x; + float dv2 = tri.t[2].y - tri.t[0].y; + float du2 = tri.t[2].x - tri.t[0].x; + float dz2 = tri.t[2].z - tri.t[0].z; + + float tex_x, tex_y, tex_z; + + float du1_step = 0, dv1_step = 0, du2_step = 0, dv2_step = 0, dz1_step = 0, dz2_step = 0; + float dax_step = 0, dbx_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dz2_step = dz2 / (float)abs(dy2); + + + + if (dy1) + { + for (int i = tri.p[0].y; i <= tri.p[1].y; i++) + { + int ax = tri.p[0].x + (i - tri.p[0].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[0].x + (float)(i - tri.p[0].y) * du1_step; + float tex_sv = tri.t[0].y + (float)(i - tri.p[0].y) * dv1_step; + float tex_sz = tri.t[0].z + (float)(i - tri.p[0].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + t += tstep; + } + + + } + } + + dy1 = tri.p[2].y - tri.p[1].y; + dx1 = tri.p[2].x - tri.p[1].x; + dv1 = tri.t[2].y - tri.t[1].y; + du1 = tri.t[2].x - tri.t[1].x; + dz1 = tri.t[2].z - tri.t[1].z; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + + du1_step = 0, dv1_step = 0;// , dz1_step = 0;// , du2_step = 0, dv2_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy1) + { + for (int i = tri.p[1].y; i <= tri.p[2].y; i++) + { + int ax = tri.p[1].x + (i - tri.p[1].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[1].x + (float)(i - tri.p[1].y) * du1_step; + float tex_sv = tri.t[1].y + (float)(i - tri.p[1].y) * dv1_step; + float tex_sz = tri.t[1].z + (float)(i - tri.p[1].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + + t += tstep; + } + } + } + + } + + float* GFX3D::m_DepthBuffer = nullptr; + + void GFX3D::ConfigureDisplay() + { + m_DepthBuffer = new float[pge->ScreenWidth() * pge->ScreenHeight()]{ 0 }; + } + + + void GFX3D::ClearDepth() + { + memset(m_DepthBuffer, 0, pge->ScreenWidth() * pge->ScreenHeight() * sizeof(float)); + } + + + + + GFX3D::PipeLine::PipeLine() + { + + } + + void GFX3D::PipeLine::SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight) + { + matProj = GFX3D::Math::Mat_MakeProjection(fFovDegrees, fAspectRatio, fNear, fFar); + fViewX = fLeft; + fViewY = fTop; + fViewW = fWidth; + fViewH = fHeight; + } + + void GFX3D::PipeLine::SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up) + { + matView = GFX3D::Math::Mat_PointAt(pos, lookat, up); + matView = GFX3D::Math::Mat_QuickInverse(matView); + } + + void GFX3D::PipeLine::SetTransform(olc::GFX3D::mat4x4 &transform) + { + matWorld = transform; + } + + void GFX3D::PipeLine::SetTexture(olc::Sprite *texture) + { + sprTexture = texture; + } + + void GFX3D::PipeLine::SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col) + { + + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags) + { + // Calculate Transformation Matrix + mat4x4 matWorldView = Math::Mat_MultiplyMatrix(matWorld, matView); + //matWorldViewProj = Math::Mat_MultiplyMatrix(matWorldView, matProj); + + // Store triangles for rastering later + std::vector vecTrianglesToRaster; + + int nTriangleDrawnCount = 0; + + // Process Triangles + for (auto &tri : triangles) + { + GFX3D::triangle triTransformed; + + // Just copy through texture coordinates + triTransformed.t[0] = { tri.t[0].x, tri.t[0].y, tri.t[0].z }; + triTransformed.t[1] = { tri.t[1].x, tri.t[1].y, tri.t[1].z }; + triTransformed.t[2] = { tri.t[2].x, tri.t[2].y, tri.t[2].z }; // Think! + + // Transform Triangle from object into projected space + triTransformed.p[0] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[0]); + triTransformed.p[1] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[1]); + triTransformed.p[2] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[2]); + + // Calculate Triangle Normal in WorldView Space + GFX3D::vec3d normal, line1, line2; + line1 = GFX3D::Math::Vec_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = GFX3D::Math::Vec_Sub(triTransformed.p[2], triTransformed.p[0]); + normal = GFX3D::Math::Vec_CrossProduct(line1, line2); + normal = GFX3D::Math::Vec_Normalise(normal); + + // Cull triangles that face away from viewer + if (flags & RENDER_CULL_CW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) > 0.0f) continue; + if (flags & RENDER_CULL_CCW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) < 0.0f) continue; + + // If Lighting, calculate shading + triTransformed.col = olc::WHITE; + + // Clip triangle against near plane + int nClippedTriangles = 0; + triangle clipped[2]; + nClippedTriangles = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triTransformed, clipped[0], clipped[1]); + + // This may yield two new triangles + for (int n = 0; n < nClippedTriangles; n++) + { + triangle triProjected = clipped[n]; + + // Project new triangle + triProjected.p[0] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[2]); + + // Apply Projection to Verts + triProjected.p[0].x = triProjected.p[0].x / triProjected.p[0].w; + triProjected.p[1].x = triProjected.p[1].x / triProjected.p[1].w; + triProjected.p[2].x = triProjected.p[2].x / triProjected.p[2].w; + + triProjected.p[0].y = triProjected.p[0].y / triProjected.p[0].w; + triProjected.p[1].y = triProjected.p[1].y / triProjected.p[1].w; + triProjected.p[2].y = triProjected.p[2].y / triProjected.p[2].w; + + triProjected.p[0].z = triProjected.p[0].z / triProjected.p[0].w; + triProjected.p[1].z = triProjected.p[1].z / triProjected.p[1].w; + triProjected.p[2].z = triProjected.p[2].z / triProjected.p[2].w; + + // Apply Projection to Tex coords + triProjected.t[0].x = triProjected.t[0].x / triProjected.p[0].w; + triProjected.t[1].x = triProjected.t[1].x / triProjected.p[1].w; + triProjected.t[2].x = triProjected.t[2].x / triProjected.p[2].w; + + triProjected.t[0].y = triProjected.t[0].y / triProjected.p[0].w; + triProjected.t[1].y = triProjected.t[1].y / triProjected.p[1].w; + triProjected.t[2].y = triProjected.t[2].y / triProjected.p[2].w; + + triProjected.t[0].z = 1.0f / triProjected.p[0].w; + triProjected.t[1].z = 1.0f / triProjected.p[1].w; + triProjected.t[2].z = 1.0f / triProjected.p[2].w; + + // Clip against viewport in screen space + // Clip triangles against all four screen edges, this could yield + // a bunch of triangles, so create a queue that we traverse to + // ensure we only test new triangles generated against planes + triangle sclipped[2]; + std::list listTriangles; + + + // Add initial triangle + listTriangles.push_back(triProjected); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 1: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, +1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 2: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 3: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ +1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + } + + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(sclipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto &triRaster : listTriangles) + { + // Scale to viewport + /*triRaster.p[0].x *= -1.0f; + triRaster.p[1].x *= -1.0f; + triRaster.p[2].x *= -1.0f; + triRaster.p[0].y *= -1.0f; + triRaster.p[1].y *= -1.0f; + triRaster.p[2].y *= -1.0f;*/ + vec3d vOffsetView = { 1,1,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + triRaster.p[0].x *= 0.5f * fViewW; + triRaster.p[0].y *= 0.5f * fViewH; + triRaster.p[1].x *= 0.5f * fViewW; + triRaster.p[1].y *= 0.5f * fViewH; + triRaster.p[2].x *= 0.5f * fViewW; + triRaster.p[2].y *= 0.5f * fViewH; + vOffsetView = { fViewX,fViewY,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + + // For now, just draw triangle + + if (flags & RENDER_TEXTURED) + { + TexturedTriangle( + triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, + triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, + triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, + sprTexture); + } + + if (flags & RENDER_WIRE) + { + DrawTriangleWire(triRaster, olc::RED); + } + + if (flags & RENDER_FLAT) + { + DrawTriangleFlat(triRaster); + } + + nTriangleDrawnCount++; + } + } + } + + return nTriangleDrawnCount; + } +} + +#endif \ No newline at end of file diff --git a/Extensions/olcPGEX_PopUpMenu.h b/Extensions/olcPGEX_PopUpMenu.h new file mode 100644 index 0000000..860b55f --- /dev/null +++ b/Extensions/olcPGEX_PopUpMenu.h @@ -0,0 +1,585 @@ +/* + olcPGEX_PopUp.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Retro PopUp Menu 1.0 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + a quick and easy to use, flexible, skinnable context pop-up + menu system. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 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, 2020 +*/ + + +/* + Example + ~~~~~~~ + + #define OLC_PGEX_POPUPMENU + #include "olcPGEX_PopUpMenu.h" + + NOTE: Requires a 9-patch sprite, by default each patch is + 8x8 pixels, patches are as follows: + + | PANEL TL | PANEL T | PANEL TR | SCROLL UP | CURSOR TL | CURSOR TR | + | PANEL L | PANEL M | PANEL R | SUBMENU | CURSOR BL | CURSOR BR | + | PANEL BL | PANEL B | PANEL BR | SCROLL DOWN | UNUSED | UNUSED | + + You can find an example sprite here: + https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/RetroMenu.png + + Constructing A Menu + ~~~~~~~~~~~~~~~~~~~ + + // Declaration (presumably inside class) + olc::popup::Menu m; + + // Construction (root menu is a 1x5 table) + m.SetTable(1, 5); + + // Add first item to root menu (A 1x5 submenu) + m["Menu1"].SetTable(1, 5); + + // Add items to first item + m["Menu1"]["Item1"]; + m["Menu1"]["Item2"]; + + // Add a 4x3 submenu + m["Menu1"]["Item3"].SetTable(4, 3); + m["Menu1"]["Item3"]["Option1"]; + m["Menu1"]["Item3"]["Option2"]; + + // Set properties of specific item + m["Menu1"]["Item3"]["Option3"].Enable(false); + m["Menu1"]["Item3"]["Option4"]; + m["Menu1"]["Item3"]["Option5"]; + m["Menu1"]["Item4"]; + + // Add second item to root menu + m["Menu2"].SetTable(3, 3); + m["Menu2"]["Item1"]; + m["Menu2"]["Item2"].SetID(1001).Enable(true); + m["Menu2"]["Item3"]; + + // Construct the menu structure + m.Build(); + + + Displaying a Menu + ~~~~~~~~~~~~~~~~~ + + // Declaration of menu manager (presumably inside class) + olc::popup::Manager man; + + // Set the Menu object to the MenuManager (call once per pop) + man.Open(&m); + + // Draw Menu at position (30, 30), using "patch sprite" + man.Draw(sprGFX, { 30,30 }); + + + Interacting with menu + ~~~~~~~~~~~~~~~~~~~~~ + + // Send key events to menu + if (GetKey(olc::Key::UP).bPressed) man.OnUp(); + if (GetKey(olc::Key::DOWN).bPressed) man.OnDown(); + if (GetKey(olc::Key::LEFT).bPressed) man.OnLeft(); + if (GetKey(olc::Key::RIGHT).bPressed) man.OnRight(); + if (GetKey(olc::Key::Z).bPressed) man.OnBack(); + + // "Confirm/Action" Key does something, if it returns non-null + // then a menu item has been selected. The specific item will + // be returned + olc::popup::Menu* command = nullptr; + if (GetKey(olc::Key::SPACE).bPressed) command = man.OnConfirm(); + if (command != nullptr) + { + std::string sLastAction = + "Selected: " + command->GetName() + + " ID: " + std::to_string(command->GetID()); + + // Optionally close menu? + man.Close(); + } + +*/ + +#ifndef OLC_PGEX_POPUPMENU_H +#define OLC_PGEX_POPUPMENU_H + +#include + +namespace olc +{ + namespace popup + { + constexpr int32_t nPatch = 8; + + class Menu + { + public: + Menu(); + Menu(const std::string n); + + Menu& SetTable(int32_t nColumns, int32_t nRows); + Menu& SetID(int32_t id); + Menu& Enable(bool b); + + int32_t GetID(); + std::string& GetName(); + bool Enabled(); + bool HasChildren(); + olc::vi2d GetSize(); + olc::vi2d& GetCursorPosition(); + Menu& operator[](const std::string& name); + void Build(); + void DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset); + void ClampCursor(); + void OnUp(); + void OnDown(); + void OnLeft(); + void OnRight(); + Menu* OnConfirm(); + Menu* GetSelectedItem(); + + protected: + int32_t nID = -1; + olc::vi2d vCellTable = { 1, 0 }; + std::unordered_map itemPointer; + std::vector items; + olc::vi2d vSizeInPatches = { 0, 0 }; + olc::vi2d vCellSize = { 0, 0 }; + olc::vi2d vCellPadding = { 2, 0 }; + olc::vi2d vCellCursor = { 0, 0 }; + int32_t nCursorItem = 0; + int32_t nTopVisibleRow = 0; + int32_t nTotalRows = 0; + const olc::vi2d vPatchSize = { nPatch, nPatch }; + std::string sName; + olc::vi2d vCursorPos = { 0, 0 }; + bool bEnabled = true; + }; + + class Manager : public olc::PGEX + { + public: + Manager(); + void Open(Menu* mo); + void Close(); + void OnUp(); + void OnDown(); + void OnLeft(); + void OnRight(); + void OnBack(); + Menu* OnConfirm(); + void Draw(olc::Sprite* sprGFX, olc::vi2d vScreenOffset); + + private: + std::list panels; + }; + + } +}; + + + + +#ifdef OLC_PGEX_POPUPMENU +#undef OLC_PGEX_POPUPMENU + +namespace olc +{ + namespace popup + { + Menu::Menu() + { + } + + Menu::Menu(const std::string n) + { + sName = n; + } + + + Menu& Menu::SetTable(int32_t nColumns, int32_t nRows) + { + vCellTable = { nColumns, nRows }; + return *this; + } + + Menu& Menu::SetID(int32_t id) + { + nID = id; + return *this; + } + + Menu& Menu::Enable(bool b) + { + bEnabled = b; + return *this; + } + + int32_t Menu::GetID() + { + return nID; + } + + std::string& Menu::GetName() + { + return sName; + } + + bool Menu::Enabled() + { + return bEnabled; + } + + bool Menu::HasChildren() + { + return !items.empty(); + } + + olc::vi2d Menu::GetSize() + { + return { int32_t(sName.size()), 1 }; + } + + olc::vi2d& Menu::GetCursorPosition() + { + return vCursorPos; + } + + Menu& Menu::operator[](const std::string& name) + { + if (itemPointer.count(name) == 0) + { + itemPointer[name] = items.size(); + items.push_back(Menu(name)); + } + + return items[itemPointer[name]]; + } + + void Menu::Build() + { + // Recursively build all children, so they can determine their size, use + // that size to indicate cell sizes if this object contains more than + // one item + for (auto& m : items) + { + if (m.HasChildren()) + { + m.Build(); + } + + // Longest child name determines cell width + vCellSize.x = std::max(m.GetSize().x, vCellSize.x); + vCellSize.y = std::max(m.GetSize().y, vCellSize.y); + } + + // Adjust size of this object (in patches) if it were rendered as a panel + vSizeInPatches.x = vCellTable.x * vCellSize.x + (vCellTable.x - 1) * vCellPadding.x + 2; + vSizeInPatches.y = vCellTable.y * vCellSize.y + (vCellTable.y - 1) * vCellPadding.y + 2; + + // Calculate how many rows this item has to hold + nTotalRows = (items.size() / vCellTable.x) + (((items.size() % vCellTable.x) > 0) ? 1 : 0); + } + + void Menu::DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset) + { + // === Draw Panel + + // Record current pixel mode user is using + olc::Pixel::Mode currentPixelMode = pge.GetPixelMode(); + pge.SetPixelMode(olc::Pixel::MASK); + + // Draw Panel & Border + olc::vi2d vPatchPos = { 0,0 }; + for (vPatchPos.x = 0; vPatchPos.x < vSizeInPatches.x; vPatchPos.x++) + { + for (vPatchPos.y = 0; vPatchPos.y < vSizeInPatches.y; vPatchPos.y++) + { + // Determine position in screen space + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + + // Calculate which patch is needed + olc::vi2d vSourcePatch = { 0, 0 }; + if (vPatchPos.x > 0) vSourcePatch.x = 1; + if (vPatchPos.x == vSizeInPatches.x - 1) vSourcePatch.x = 2; + if (vPatchPos.y > 0) vSourcePatch.y = 1; + if (vPatchPos.y == vSizeInPatches.y - 1) vSourcePatch.y = 2; + + // Draw Actual Patch + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + } + + // === Draw Panel Contents + olc::vi2d vCell = { 0,0 }; + vPatchPos = { 1,1 }; + + // Work out visible items + int32_t nTopLeftItem = nTopVisibleRow * vCellTable.x; + int32_t nBottomRightItem = vCellTable.y * vCellTable.x + nTopLeftItem; + + // Clamp to size of child item vector + nBottomRightItem = std::min(int32_t(items.size()), nBottomRightItem); + int32_t nVisibleItems = nBottomRightItem - nTopLeftItem; + + // Draw Scroll Markers (if required) + if (nTopVisibleRow > 0) + { + vPatchPos = { vSizeInPatches.x - 2, 0 }; + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + olc::vi2d vSourcePatch = { 3, 0 }; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + + if ((nTotalRows - nTopVisibleRow) > vCellTable.y) + { + vPatchPos = { vSizeInPatches.x - 2, vSizeInPatches.y - 1 }; + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + olc::vi2d vSourcePatch = { 3, 2 }; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + + // Draw Visible Items + for (int32_t i = 0; i < nVisibleItems; i++) + { + // Cell location + vCell.x = i % vCellTable.x; + vCell.y = i / vCellTable.x; + + // Patch location (including border offset and padding) + vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1; + vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; + + // Actual screen location in pixels + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + + // Display Item Header + pge.DrawString(vScreenLocation, items[nTopLeftItem + i].sName, items[nTopLeftItem + i].bEnabled ? olc::WHITE : olc::DARK_GREY); + + if (items[nTopLeftItem + i].HasChildren()) + { + // Display Indicator that panel has a sub panel + vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1 + vCellSize.x; + vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; + olc::vi2d vSourcePatch = { 3, 1 }; + vScreenLocation = vPatchPos * nPatch + vScreenOffset; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + } + + // Calculate cursor position in screen space in case system draws it + vCursorPos.x = (vCellCursor.x * (vCellSize.x + vCellPadding.x)) * nPatch + vScreenOffset.x - nPatch; + vCursorPos.y = ((vCellCursor.y - nTopVisibleRow) * (vCellSize.y + vCellPadding.y)) * nPatch + vScreenOffset.y + nPatch; + } + + void Menu::ClampCursor() + { + // Find item in children + nCursorItem = vCellCursor.y * vCellTable.x + vCellCursor.x; + + // Clamp Cursor + if (nCursorItem >= int32_t(items.size())) + { + vCellCursor.y = (items.size() / vCellTable.x); + vCellCursor.x = (items.size() % vCellTable.x) - 1; + nCursorItem = items.size() - 1; + } + } + + void Menu::OnUp() + { + vCellCursor.y--; + if (vCellCursor.y < 0) vCellCursor.y = 0; + + if (vCellCursor.y < nTopVisibleRow) + { + nTopVisibleRow--; + if (nTopVisibleRow < 0) nTopVisibleRow = 0; + } + + ClampCursor(); + } + + void Menu::OnDown() + { + vCellCursor.y++; + if (vCellCursor.y == nTotalRows) vCellCursor.y = nTotalRows - 1; + + if (vCellCursor.y > (nTopVisibleRow + vCellTable.y - 1)) + { + nTopVisibleRow++; + if (nTopVisibleRow > (nTotalRows - vCellTable.y)) + nTopVisibleRow = nTotalRows - vCellTable.y; + } + + ClampCursor(); + } + + void Menu::OnLeft() + { + vCellCursor.x--; + if (vCellCursor.x < 0) vCellCursor.x = 0; + ClampCursor(); + } + + void Menu::OnRight() + { + vCellCursor.x++; + if (vCellCursor.x == vCellTable.x) vCellCursor.x = vCellTable.x - 1; + ClampCursor(); + } + + Menu* Menu::OnConfirm() + { + // Check if selected item has children + if (items[nCursorItem].HasChildren()) + { + return &items[nCursorItem]; + } + else + return this; + } + + Menu* Menu::GetSelectedItem() + { + return &items[nCursorItem]; + } + + // ===================================================================== + + Manager::Manager() + { + } + + void Manager::Open(Menu* mo) + { + Close(); + panels.push_back(mo); + } + + void Manager::Close() + { + panels.clear(); + } + + void Manager::OnUp() + { + if (!panels.empty()) panels.back()->OnUp(); + } + + void Manager::OnDown() + { + if (!panels.empty()) panels.back()->OnDown(); + } + + void Manager::OnLeft() + { + if (!panels.empty()) panels.back()->OnLeft(); + } + + void Manager::OnRight() + { + if (!panels.empty()) panels.back()->OnRight(); + } + + void Manager::OnBack() + { + if (!panels.empty()) panels.pop_back(); + } + + Menu* Manager::OnConfirm() + { + if (panels.empty()) return nullptr; + + Menu* next = panels.back()->OnConfirm(); + if (next == panels.back()) + { + if (panels.back()->GetSelectedItem()->Enabled()) + return panels.back()->GetSelectedItem(); + } + else + { + if (next->Enabled()) + panels.push_back(next); + } + + return nullptr; + } + + void Manager::Draw(olc::Sprite* sprGFX, olc::vi2d vScreenOffset) + { + if (panels.empty()) return; + + // Draw Visible Menu System + for (auto& p : panels) + { + p->DrawSelf(*pge, sprGFX, vScreenOffset); + vScreenOffset += {10, 10}; + } + + // Draw Cursor + olc::Pixel::Mode currentPixelMode = pge->GetPixelMode(); + pge->SetPixelMode(olc::Pixel::ALPHA); + pge->DrawPartialSprite(panels.back()->GetCursorPosition(), sprGFX, olc::vi2d(4, 0) * nPatch, { nPatch * 2, nPatch * 2 }); + pge->SetPixelMode(currentPixelMode); + } + } +}; + + +#endif +#endif diff --git a/Extensions/olcPGEX_Sound.h b/Extensions/olcPGEX_Sound.h new file mode 100644 index 0000000..41e47c3 --- /dev/null +++ b/Extensions/olcPGEX_Sound.h @@ -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 +#include +#include + +#include +#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 +#endif + +#ifdef USE_OPENAL +#include +#include +#include +#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 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 func); + static void SetUserFilterFunction(std::function 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 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 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 m_bAudioThreadActive; + static std::atomic m_fGlobalTime; + static std::function funcUserSynth; + static std::function 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 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 func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function 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 SOUND::m_bAudioThreadActive{ false }; + std::atomic SOUND::m_fGlobalTime{ 0.0f }; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function 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 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 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 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 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(¶ms); + snd_pcm_hw_params_any(m_pPCM, params); + + // Set other parameters + snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); + snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); + snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0); + snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0); + + // Save these parameters + rc = snd_pcm_hw_params(m_pPCM, params); + if (rc < 0) + return DestroyAudio(); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + // Unsure if really needed, helped prevent underrun on my setup + snd_pcm_start(m_pPCM); + for (unsigned int i = 0; i < nBlocks; i++) + rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512); + + snd_pcm_start(m_pPCM); + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + m_AudioThread.join(); + snd_pcm_drain(m_pPCM); + snd_pcm_close(m_pPCM); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + short nNewSample = 0; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + snd_pcm_uframes_t nLeft = m_nBlockSamples; + short *pBlockPos = m_pBlockMemory; + while (nLeft > 0) + { + int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); + if (rc > 0) + { + pBlockPos += rc * m_nChannels; + nLeft -= rc; + } + if (rc == -EAGAIN) continue; + if (rc == -EPIPE) // an underrun occured, prepare the device for more data + snd_pcm_prepare(m_pPCM); + } + } + } + + snd_pcm_t* SOUND::m_pPCM = nullptr; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockSamples = 0; + short* SOUND::m_pBlockMemory = nullptr; +} + +#elif defined(USE_OPENAL) + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; + + // Open the device and create the context + m_pDevice = alcOpenDevice(NULL); + if (m_pDevice) + { + m_pContext = alcCreateContext(m_pDevice, NULL); + alcMakeContextCurrent(m_pContext); + } + else + return DestroyAudio(); + + // Allocate memory for sound data + alGetError(); + m_pBuffers = new ALuint[m_nBlockCount]; + alGenBuffers(m_nBlockCount, m_pBuffers); + alGenSources(1, &m_nSource); + + for (unsigned int i = 0; i < m_nBlockCount; i++) + m_qAvailableBuffers.push(m_pBuffers[i]); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + m_AudioThread.join(); + + alDeleteBuffers(m_nBlockCount, m_pBuffers); + delete[] m_pBuffers; + alDeleteSources(1, &m_nSource); + + alcMakeContextCurrent(NULL); + alcDestroyContext(m_pContext); + alcCloseDevice(m_pDevice); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + std::vector 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 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 \ No newline at end of file diff --git a/LICENCE.md b/LICENCE.md index 0eefa43..2236a34 100644 --- a/LICENCE.md +++ b/LICENCE.md @@ -1,6 +1,6 @@ # License (OLC-3) -Copyright 2018-2019 OneLoneCoder.com +Copyright 2018-2020 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/LastVersion/olcPixelGameEngine.h b/LastVersion/olcPixelGameEngine.h new file mode 100644 index 0000000..0a5b2f5 --- /dev/null +++ b/LastVersion/olcPixelGameEngine.h @@ -0,0 +1,2362 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.20 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprising and wonderful success for me, + and I'm delighted how people have reacted so positively towards it, so thanks + for that. + + However, there are limitations that I simply cannot avoid. Firstly, I need to + maintain several different versions of it to accommodate users on Windows7, + 8, 10, Linux, Mac, Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities and the effect + is becoming underwhelming. The engine itself is not slow at all, but the process + that Windows uses to draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours and glyphs. Sadly + I have no control over this, and recent videos that are extremely graphical + (for a command prompt :P ) have been dipping to unacceptable framerates. As + the channel has been popular with aspiring game developers, I'm concerned that + the visual appeal of the command prompt is perhaps limited to us oldies, and I + dont want to alienate younger learners. Finally, I'd like to demonstrate many + more algorithms and image processing that exist in the graphical domain, for + which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look and feel to the + programmer is almost identical, so all of my existing code from the videos is + easily portable, and the programmer uses this file in exactly the same way. But + I've decided that rather than just build a command prompt emulator, that I + would at least harness some modern(ish) portable technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is written in a + cross-platform style, uses modern(ish) C++ conventions and most importantly, + renders much much faster. I will use this version when my applications are + predominantly graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command prompt silliness to + come yet, but evolution is important!! + + 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 + 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 + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Rapberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + Thanks + ~~~~~~ + 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 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 + Marti Morta........Gris + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#if defined(_WIN32) // WINDOWS specific includes ============================================== + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus + #if !defined _WIN32_WINNT + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#endif + +#ifdef __linux__ // LINUX specific includes ============================================== + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max +#define UNUSED(x) (void)(x) + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + + bool operator==(const Pixel& p) const; + bool operator!=(const Pixel& p) const; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //================================================================================== + + template + struct v2d_generic + { + T x = 0; + T y = 0; + + inline v2d_generic() : x(0), y(0) { } + inline v2d_generic(T _x, T _y) : x(_x), y(_y) { } + inline v2d_generic(const v2d_generic& v) : x(v.x), y(v.y){ } + inline T mag() { return sqrt(x * x + y * y); } + inline T mag2() { return x * x + y * y; } + inline v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); } + inline v2d_generic perp() { return v2d_generic(-y, x); } + inline T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; } + inline T cross(const v2d_generic& rhs) { return this->x * rhs.y - this->y * rhs.x; } + inline v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y);} + inline v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y);} + inline v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + inline v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + inline v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + inline v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + 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() const { return { static_cast(this->x), static_cast(this->y) }; } + inline operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + + typedef v2d_generic vi2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + //============================================================= + + + class ResourcePack + { + public: + ResourcePack(); + ~ResourcePack(); + struct sEntry : public std::streambuf { + uint32_t nID = 0, nFileOffset = 0, nFileSize = 0; uint8_t* data = nullptr; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + bool SetPixel(int32_t x, int32_t y, Pixel p); + + Pixel Sample(float x, float y); + Pixel SampleBL(float u, float v); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen = false, bool vsync = false); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + // Get Mouse Wheel Delta + int32_t GetMouseWheel(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + // Resize the primary screen sprite + void SetScreenSize(int w, int h); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + int32_t nMouseWheelDelta = 0; + int32_t nMousePosXcache = 0; + int32_t nMousePosYcache = 0; + int32_t nMouseWheelDeltaCache = 0; + int32_t nWindowWidth = 0; + int32_t nWindowHeight = 0; + int32_t nViewX = 0; + int32_t nViewY = 0; + int32_t nViewW = 0; + int32_t nViewH = 0; + bool bFullScreen = false; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#if defined(_WIN32) + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#endif + +#if defined(__linux__) + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + + +#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); +#endif + +#if defined(__linux__) + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + If all else fails, create a file called "olcPixelGameEngine.cpp" with the following + two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying + about defining things. Dont forget to include that cpp file as part of your build! + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + bool Pixel::operator==(const Pixel& p) const + { + return n == p.n; + } + + bool Pixel::operator!=(const Pixel& p) const + { + return n != p.n; + } + + //========================================================== + +#if defined(_WIN32) + std::wstring ConvertS2W(std::string s) + { +#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); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } +#endif + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { + UNUSED(pack); +#if defined(_WIN32) + // Use GDI+ + std::wstring wsImageFile = ConvertS2W(sImageFile); + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#endif + +#if defined(__linux__) + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y*width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + uint32_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen, bool vsync) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#if defined(_WIN32) && defined(UNICODE) && !defined(__MINGW32__) + wsAppName = ConvertS2W(sAppName); +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + + void PixelGameEngine::SetScreenSize(int w, int h) + { + delete pDefaultDrawTarget; + nScreenWidth = w; + nScreenHeight = h; + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + glClear(GL_COLOR_BUFFER_BIT); + +#if defined(_WIN32) + SwapBuffers(glDeviceContext); +#endif + +#if defined(__linux__) + glXSwapBuffers(olc_Display, olc_Window); +#endif + + glClear(GL_COLOR_BUFFER_BIT); + olc_UpdateViewport(); + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + if (!olc_WindowCreate()) + return olc::FAIL; + + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + +#if defined(_WIN32) + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::GetMouseWheel() + { + return nMouseWheelDelta; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) + { + pattern = (pattern << 1) | (pattern >> 31); + return pattern & 1; + }; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + if (mask & 0x01) Draw(x + x0, y - y0, p); + if (mask & 0x02) Draw(x + y0, y - x0, p); + if (mask & 0x04) Draw(x + y0, y + x0, p); + if (mask & 0x08) Draw(x + x0, y + y0, p); + if (mask & 0x10) Draw(x - x0, y + y0, p); + if (mask & 0x20) Draw(x - y0, y + x0, p); + if (mask & 0x40) Draw(x - y0, y - x0, p); + if (mask & 0x80) Draw(x - x0, y - y0, p); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { UNUSED(fElapsedTime); return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = nScreenWidth * nPixelWidth; + int32_t wh = nScreenHeight * nPixelHeight; + float wasp = (float)ww / (float)wh; + + nViewW = (int32_t)nWindowWidth; + nViewH = (int32_t)((float)nViewW / wasp); + + if (nViewH > nWindowHeight) + { + nViewH = nWindowHeight; + nViewW = (int32_t)((float)nViewH * wasp); + } + + nViewX = (nWindowWidth - nViewW) / 2; + nViewY = (nWindowHeight - nViewH) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + nWindowWidth = x; + nWindowHeight = y; + olc_UpdateViewport(); + + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { + nMouseWheelDeltaCache += delta; + } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + + // 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)); + + if (nMousePosXcache >= (int32_t)nScreenWidth) + nMousePosXcache = nScreenWidth - 1; + if (nMousePosYcache >= (int32_t)nScreenHeight) + nMousePosYcache = nScreenHeight - 1; + + if (nMousePosXcache < 0) + nMousePosXcache = 0; + if (nMousePosYcache < 0) + nMousePosYcache = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#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 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + glClear(GL_COLOR_BUFFER_BIT); // Thanks Benedani! + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + nWindowWidth = xce.width; + nWindowHeight = xce.height; + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + XKeyEvent *e = (XKeyEvent *)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + XKeyEvent *e = (XKeyEvent *)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = true; break; + case 2: pMouseNewState[2] = true; break; + case 3: pMouseNewState[1] = true; break; + case 4: olc_UpdateMouseWheel(120); break; + case 5: olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = false; break; + case 2: pMouseNewState[2] = false; break; + case 3: pMouseNewState[1] = false; break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + + // Cache mouse coordinates so they remain + // consistent during frame + nMousePosX = nMousePosXcache; + nMousePosY = nMousePosYcache; + + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + glViewport(nViewX, nViewY, nViewW, nViewH); + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#if defined(_WIN32) + SwapBuffers(glDeviceContext); +#endif + +#if defined(__linux__) + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); +#if defined(_WIN32) +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#endif + +#if defined (__linux__) + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#if defined(_WIN32) + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#endif + +#if defined (__linux__) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + +#if defined (_WIN32) + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); + }; + } gdistartup; +#endif + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#if defined(_WIN32) + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + nWindowWidth = (LONG)nScreenWidth * (LONG)nPixelWidth; + nWindowHeight = (LONG)nScreenHeight * (LONG)nPixelHeight; + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + int nCosmeticOffset = 30; + nViewW = nWindowWidth; + nViewH = nWindowHeight; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + nCosmeticOffset = 0; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return NULL; + nWindowWidth = mi.rcMonitor.right; + nWindowHeight = mi.rcMonitor.bottom; + + + } + + olc_UpdateViewport(); + + // Keep client size as requested + RECT rWndRect = { 0, 0, nWindowWidth, nWindowHeight }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + glViewport(nViewX, nViewY, nViewW, nViewH); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bEnableVSYNC) wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: + { + sge->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); + return 0; + } + case WM_MOUSEWHEEL: + { + sge->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; return 0; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#endif + +#if defined(__linux__) + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + 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; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif diff --git a/README.md b/README.md index 23fcc2a..3ece627 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,44 @@ # olcPixelGameEngine Unofficial fork of the olcPixelGameEngine, maintained and updeted by me, a tool used in javidx9's YouTube videos and projects +**You only need the one file - olcPixelGameEngine.h - included in your project!** + +Provides a fast, richly featured, cross platform pixel drawing and user interface framework for + * The development of games + * Visualisation of algorithms + * Prototyping and experimentation + * Education + +olcPixelGameEngine is easily extended! for example: + * 2D Affine transforms + * 3D Software renderer + * Controller input + * Sound + * Hardware interfaces + +olcPixelGameEngine is easy to port! Runs on: + * Windows (all) + * Linux / Raspberry Pi / ChromeOS + * MacOS (coming soon to official, but already available in "Contributors") + * PSP & Switch (Not supported by OneLoneCoder) + +olcPixelGameEngine has been reimplemented in other languages! + * C# + * Rust + * Lua + * Java + +olcPixelGameEngine is actively maintained and developed! + +olcPixelGameEngine is used by 100s, if not 1000s of programmers at all levels of ability! + + +# Documentation +Please see https://github.com/OneLoneCoder/olcPixelGameEngine/wiki + # License (OLC-3) -Copyright 2018, 2019 OneLoneCoder.com +Copyright 2018, 2019, 2020 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/Videos/CarCrimeCity/Part1/City_Roads1_mip0.png b/Videos/CarCrimeCity/Part1/City_Roads1_mip0.png new file mode 100644 index 0000000..46cec50 Binary files /dev/null and b/Videos/CarCrimeCity/Part1/City_Roads1_mip0.png differ diff --git a/Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp b/Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp new file mode 100644 index 0000000..3febabc --- /dev/null +++ b/Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp @@ -0,0 +1,670 @@ +/* + BIG PROJECT - Top Down City Based Car Crime Game Part #1 + "Probably gonna regret starting this one..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Instructions: + ~~~~~~~~~~~~~ + This is the source that accompanies part 1 of the video series which + can be viewed via the link below. Here you can create and edit a city + from a top down perspective ad navigate it with the car. + + Using the mouse left click you can select cells. Using right click clears + all the selected cells. + + "E" - Lowers building height + "T" - Raises building height + "R" - Places road + "Z, X" - Zoom + "Up, Left, Right" - Control car + "F5" - Save current city + "F8" - Load existing city + + A default city is provided for you - "example1.city", please ensure + you have this file also. + + Relevant Video: https://youtu.be/mD6b_hP17WI + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include "olcPGEX_Graphics3D.h" + +#include +#include +#include +#include +#include +#include +#include + +// Override base class with your custom functionality +class CarCrimeCity : public olc::PixelGameEngine +{ +public: + CarCrimeCity() + { + sAppName = "Car Crime City"; + } + +private: + + // Define the cell + struct sCell + { + int nHeight = 0; + int nWorldX = 0; + int nWorldY = 0; + bool bRoad = false; + bool bBuilding = true; + }; + + // Map variables + int nMapWidth; + int nMapHeight; + sCell *pMap; + + olc::Sprite *sprAll; + olc::Sprite *sprGround; + olc::Sprite *sprRoof; + olc::Sprite *sprFrontage; + olc::Sprite *sprWindows; + olc::Sprite *sprRoad[12]; + olc::Sprite *sprCar; + + float fCameraX = 0.0f; + float fCameraY = 0.0f; + float fCameraZ = -10.0f; + + olc::GFX3D::mesh meshCube; + olc::GFX3D::mesh meshFlat; + olc::GFX3D::mesh meshWallsOut; + + float fCarAngle = 0.0f; + float fCarSpeed = 2.0f; + olc::GFX3D::vec3d vecCarVel = { 0,0,0 }; + olc::GFX3D::vec3d vecCarPos = { 0,0,0 }; + + + int nMouseWorldX = 0; + int nMouseWorldY = 0; + int nOldMouseWorldX = 0; + int nOldMouseWorldY = 0; + + bool bMouseDown = false; + std::unordered_set setSelectedCells; + + olc::GFX3D::PipeLine pipeRender; + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::vec3d vUp = { 0,1,0 }; + olc::GFX3D::vec3d vEye = { 0,0,-10 }; + olc::GFX3D::vec3d vLookDir = { 0,0,1 }; + + olc::GFX3D::vec3d viewWorldTopLeft, viewWorldBottomRight; + + + void SaveCity(std::string sFilename) + { + std::ofstream file(sFilename, std::ios::out | std::ios::binary); + file.write((char*)&nMapWidth, sizeof(int)); + file.write((char*)&nMapHeight, sizeof(int)); + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + file.write((char*)&pMap[y*nMapWidth + x], sizeof(sCell)); + } + } + } + + void LoadCity(std::string sFilename) + { + std::ifstream file(sFilename, std::ios::in | std::ios::binary); + file.read((char*)&nMapWidth, sizeof(int)); + file.read((char*)&nMapHeight, sizeof(int)); + delete[] pMap; + pMap = new sCell[nMapWidth * nMapHeight]; + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + file.read((char*)&pMap[y*nMapWidth + x], sizeof(sCell)); + } + } + } + +public: + bool OnUserCreate() override + { + // Load Sprite Sheet + sprAll = new olc::Sprite("City_Roads1_mip0.png"); + + // Here we break up the sprite sheet into individual textures. This is more + // out of convenience than anything else, as it keeps the texture coordinates + // easy to manipulate + + // Building Lowest Floor + sprFrontage = new olc::Sprite(32, 96); + SetDrawTarget(sprFrontage); + DrawPartialSprite(0, 0, sprAll, 288, 64, 32, 96); + + // Building Windows + sprWindows = new olc::Sprite(32, 96); + SetDrawTarget(sprWindows); + DrawPartialSprite(0, 0, sprAll, 320, 64, 32, 96); + + // Plain Grass Field + sprGround = new olc::Sprite(96, 96); + SetDrawTarget(sprGround); + DrawPartialSprite(0, 0, sprAll, 192, 0, 96, 96); + + // Building Roof + sprRoof = new olc::Sprite(96, 96); + SetDrawTarget(sprRoof); + DrawPartialSprite(0, 0, sprAll, 352, 64, 96, 96); + + // There are 12 Road Textures, aranged in a 3x4 grid + for (int r = 0; r < 12; r++) + { + sprRoad[r] = new olc::Sprite(96, 96); + SetDrawTarget(sprRoad[r]); + DrawPartialSprite(0, 0, sprAll, (r%3)*96, (r/3)*96, 96, 96); + } + + // Don't foregt to set the draw target back to being the main screen (been there... wasted 1.5 hours :| ) + SetDrawTarget(nullptr); + + // The Yellow Car + sprCar = new olc::Sprite("car_top.png"); + + + + // Define the city map, a 64x32 array of Cells. Initialise cells + // to be just grass fields + nMapWidth = 64; + nMapHeight = 32; + pMap = new sCell[nMapWidth * nMapHeight]; + for (int x = 0; x < nMapWidth; x++) + { + for (int y = 0; y < nMapHeight; y++) + { + pMap[y*nMapWidth + x].bRoad = false; + pMap[y*nMapWidth + x].nHeight = 0; + pMap[y*nMapWidth + x].nWorldX = x; + pMap[y*nMapWidth + x].nWorldY = y; + } + } + + + // Now we'll hand construct some meshes. These are DELIBERATELY simple and not optimised (see a later video) + // Here the geometry is unit in size (1x1x1) + + // A Full cube - Always useful for debugging + meshCube.tris = + { + // SOUTH + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // EAST + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // NORTH + { 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // WEST + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // TOP + { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // BOTTOM + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + }; + + // A Flat quad + meshFlat.tris = + { + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + }; + + // The four outer walls of a cell + meshWallsOut.tris = + { + // EAST + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, }, + + // WEST + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // TOP + { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, }, + { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + + // BOTTOM + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, }, + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, }, + }; + + + // Initialise the 3D Graphics PGE Extension. This is required + // to setup internal buffers to the same size as the main output + olc::GFX3D::ConfigureDisplay(); + + // Configure the rendering pipeline with projection and viewport properties + pipeRender.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f, 0.0f, 0.0f, ScreenWidth(), ScreenHeight()); + + // Also make a projection matrix, we might need this later + matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f); + + LoadCity("example1.city"); + + // Ok, lets go! + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Directly manipulate camera + //if (GetKey(olc::Key::W).bHeld) fCameraY -= 2.0f * fElapsedTime; + //if (GetKey(olc::Key::S).bHeld) fCameraY += 2.0f * fElapsedTime; + //if (GetKey(olc::Key::A).bHeld) fCameraX -= 2.0f * fElapsedTime; + //if (GetKey(olc::Key::D).bHeld) fCameraX += 2.0f * fElapsedTime; + if (GetKey(olc::Key::Z).bHeld) fCameraZ += 5.0f * fElapsedTime; + if (GetKey(olc::Key::X).bHeld) fCameraZ -= 5.0f * fElapsedTime; + + if (GetKey(olc::Key::F5).bReleased) SaveCity("example1.city"); + if (GetKey(olc::Key::F8).bReleased) LoadCity("example1.city"); + + // === Handle User Input for Editing == + + // If there are no selected cells, then only edit the cell under the current mouse cursor + // otherwise iterate through the set of sleected cells and apply to all of them + + // Check that cell exists in valid 2D map space + if (nMouseWorldX >= 0 && nMouseWorldX < nMapWidth && nMouseWorldY >= 0 && nMouseWorldY < nMapHeight) + { + // Press "R" to toggle Road flag for selected cell(s) + if (GetKey(olc::Key::R).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->bRoad = !cell->bRoad; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].bRoad = !pMap[nMouseWorldY*nMapWidth + nMouseWorldX].bRoad; + } + + // Press "T" to increase height for selected cell(s) + if (GetKey(olc::Key::T).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->nHeight++; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].nHeight++; + } + + // Press "E" to decrease height for selected cell(s) + if (GetKey(olc::Key::E).bPressed) + { + if (!setSelectedCells.empty()) + { + for (auto &cell : setSelectedCells) + { + cell->nHeight--; + } + } + else + pMap[nMouseWorldY*nMapWidth + nMouseWorldX].nHeight--; + } + } + + + // === Car User Input === + + if (GetKey(olc::Key::LEFT).bHeld) fCarAngle -= 4.0f * fElapsedTime; + if (GetKey(olc::Key::RIGHT).bHeld) fCarAngle += 4.0f * fElapsedTime; + + olc::GFX3D::vec3d a = { 1, 0, 0 }; + olc::GFX3D::mat4x4 m = olc::GFX3D::Math::Mat_MakeRotationZ(fCarAngle); + vecCarVel = olc::GFX3D::Math::Mat_MultiplyVector(m, a); + + if (GetKey(olc::Key::UP).bHeld) + { + vecCarPos.x += vecCarVel.x * fCarSpeed * fElapsedTime; + vecCarPos.y += vecCarVel.y * fCarSpeed * fElapsedTime; + } + + // === Position Camera === + + // Our camera currently follows the car, and the car stays in the middle of + // the screen. We need to know where the camera is before we can work with + // on screen interactions + fCameraY = vecCarPos.y; + fCameraX = vecCarPos.x; + vEye = { fCameraX,fCameraY,fCameraZ }; + olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir); + + // Setup the camera properties for the pipeline - aka "view" transform + pipeRender.SetCamera(vEye, vLookTarget, vUp); + + + // === Calculate Mouse Position on Ground Plane === + + // Here we take the screen coordinate of the mouse, transform it into normalised space (-1 --> +1) + // for both axes. Instead of inverting and multiplying by the projection matrix, we only need the + // aspect ratio parameters, with which we'll scale the mouse coordinate. This new point is then + // multiplied by the inverse of the look at matrix (camera view matrix) aka a point at matrix, to + // transform the new point into world space. + // + // Now, the thing is, a 2D point is no good on its own, our world has depth. If we treat the 2D + // point as a ray cast from (0, 0)->(mx, my), we can see where this ray intersects with a plane + // at a specific depth. + + // Create a point at matrix, if you recall, this is the inverse of the look at matrix + // used by the camera + olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp); + + // Assume the origin of the mouse ray is the middle of the screen... + olc::GFX3D::vec3d vecMouseOrigin = { 0.0f, 0.0f, 0.0f }; + + // ...and that a ray is cast to the mouse location from the origin. Here we translate + // the mouse coordinates into viewport coordinates + olc::GFX3D::vec3d vecMouseDir = { + 2.0f * ((GetMouseX() / (float)ScreenWidth()) - 0.5f) / matProj.m[0][0], + 2.0f * ((GetMouseY() / (float)ScreenHeight()) - 0.5f) / matProj.m[1][1], + 1.0f, + 0.0f }; + + // Now transform the origin point and ray direction by the inverse of the camera + vecMouseOrigin = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseOrigin); + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + + // Extend the mouse ray to a large length + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + + // Offset the mouse ray by the mouse origin + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + + // All of our intersections for mouse checks occur in the ground plane (z=0), so + // define a plane at that location + olc::GFX3D::vec3d plane_p = { 0.0f, 0.0f, 0.0f }; + olc::GFX3D::vec3d plane_n = { 0.0f, 0.0f, 1.0f }; + + // Calculate Mouse Location in plane, by doing a line/plane intersection test + float t = 0.0f; + olc::GFX3D::vec3d mouse3d = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + + + // === Now we have the mouse in 3D! Handle mouse user input === + + // Left click & left click drag selects cells by adding them to the set of selected cells + // Here I make sure only to do this if the cell under the mouse has changed from the + // previous frame, but the set will also reject duplicate cells being added + if (GetMouse(0).bHeld && ((nMouseWorldX != nOldMouseWorldX) || (nMouseWorldY != nOldMouseWorldY))) + setSelectedCells.emplace(&pMap[nMouseWorldY * nMapWidth + nMouseWorldX]); + + // Single clicking cells also adds them + if (GetMouse(0).bPressed) + setSelectedCells.emplace(&pMap[nMouseWorldY * nMapWidth + nMouseWorldX]); + + // If the user right clicks, the set of selected cells is emptied + if (GetMouse(1).bReleased) + setSelectedCells.clear(); + + // Cache the current mouse position to use during comparison in next frame + nOldMouseWorldX = nMouseWorldX; + nOldMouseWorldY = nMouseWorldY; + + nMouseWorldX = (int)mouse3d.x; + nMouseWorldY = (int)mouse3d.y; + + + + + // === Rendering === + + // Right, now we're gonna render the scene! + + // First Clear the screen and the depth buffer + Clear(olc::BLUE); + olc::GFX3D::ClearDepth(); + + // === Calculate Visible World === + + // Since we now have the transforms to convert screen space into ground plane space, we + // can calculate the visible extents of the world, regardless of zoom level! The method is + // exactly the same for the mouse, but we use fixed screen coordinates that represent the + // top left, and bottom right of the screen + + // Work out Top Left Ground Cell + vecMouseDir = { -1.0f / matProj.m[0][0],-1.0f / matProj.m[1][1], 1.0f, 0.0f }; + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + viewWorldTopLeft = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + // Work out Bottom Right Ground Cell + vecMouseDir = { 1.0f / matProj.m[0][0], 1.0f / matProj.m[1][1], 1.0f, 0.0f }; + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + viewWorldBottomRight = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + + // Calculate visible tiles + //int nStartX = 0; + //int nEndX = nMapWidth; + //int nStartY = 0; + //int nEndY = nMapHeight; + + int nStartX = std::max(0, (int)viewWorldTopLeft.x - 1); + int nEndX = std::min(nMapWidth, (int)viewWorldBottomRight.x + 1); + int nStartY = std::max(0, (int)viewWorldTopLeft.y - 1); + int nEndY = std::min(nMapHeight, (int)viewWorldBottomRight.y + 1); + + + // Iterate through all the cells we wish to draw. Each cell is 1x1 and elevates in the Z -Axis + for (int x = nStartX; x < nEndX; x++) + { + for (int y = nStartY; y < nEndY; y++) + { + if (pMap[y*nMapWidth + x].bRoad) + { + // Cell is a road, look at neighbouring cells. If they are roads also, + // then choose the appropriate texture that joins up correctly + + int road = 0; + auto r = [&](int i, int j) + { + return pMap[(y + j) * nMapWidth + (x + i)].bRoad; + }; + + if (r(0, -1) && r(0, +1) && !r(-1, 0) && !r(+1, 0)) road = 0; + if (!r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) road = 1; + + if (!r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 3; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) road = 4; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 5; + + if (r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 6; + if (r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) road = 7; + if (r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 8; + + if (r(0, -1) && !r(0, +1) && !r(-1, 0) && r(+1, 0)) road = 9; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) road = 10; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && !r(+1, 0)) road = 11; + + // Create a translation transform to position the cell in the world + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, 0.0f); + pipeRender.SetTransform(matWorld); + + // Set the appropriate texture to use + pipeRender.SetTexture(sprRoad[road]); + + // Draw a flat quad + pipeRender.Render(meshFlat.tris); + + } + else // Not Road + { + // If the cell is not considered road, then draw it appropriately + + if (pMap[y*nMapWidth + x].nHeight < 0) + { + // Cell is blank - for now ;-P + } + + if (pMap[y*nMapWidth + x].nHeight == 0) + { + // Cell is ground, draw a flat grass quad at height 0 + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, 0.0f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprGround); + pipeRender.Render(meshFlat.tris); + } + + if (pMap[y*nMapWidth + x].nHeight > 0) + { + // Cell is Building, for now, we'll draw each storey as a seperate mesh + int h, t; + t = pMap[y*nMapWidth + x].nHeight; + + for (h = 0; h < t; h++) + { + // Create a transform that positions the storey according to its height + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, -(h + 1) * 0.2f); + pipeRender.SetTransform(matWorld); + + // Choose a texture, if its ground level, use the "street level front", otherwise use windows + pipeRender.SetTexture(h == 0 ? sprFrontage : sprWindows); + pipeRender.Render(meshWallsOut.tris); + } + + // Top the building off with a roof + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(x, y, -(h) * 0.2f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprRoof); + pipeRender.Render(meshFlat.tris); + } + } + } + } + + // Draw Selected Cells, iterate through the set of cells, and draw a wireframe quad at ground level + // to indicate it is in the selection set + for (auto &cell : setSelectedCells) + { + // Draw CursorCube + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(cell->nWorldX, cell->nWorldY, 0.0f); + pipeRender.SetTransform(matWorld); + pipeRender.SetTexture(sprRoof); + pipeRender.Render(meshFlat.tris, olc::GFX3D::RENDER_WIRE); + } + + // Draw Car, a few transforms required for this + + // 1) Offset the car to the middle of the quad + olc::GFX3D::mat4x4 matCarOffset = olc::GFX3D::Math::Mat_MakeTranslation(-0.5f, -0.5f, -0.0f); + // 2) The quad is currently unit square, scale it to be more rectangular and smaller than the cells + olc::GFX3D::mat4x4 matCarScale = olc::GFX3D::Math::Mat_MakeScale(0.4f, 0.2f, 1.0f); + // 3) Combine into matrix + olc::GFX3D::mat4x4 matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCarOffset, matCarScale); + // 4) Rotate the car around its offset origin, according to its angle + olc::GFX3D::mat4x4 matCarRot = olc::GFX3D::Math::Mat_MakeRotationZ(fCarAngle); + matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCar, matCarRot); + // 5) Translate the car into its position in the world. Give it a little elevation so its baove the ground + olc::GFX3D::mat4x4 matCarTrans = olc::GFX3D::Math::Mat_MakeTranslation(vecCarPos.x, vecCarPos.y, -0.01f); + matCar = olc::GFX3D::Math::Mat_MultiplyMatrix(matCar, matCarTrans); + + // Set the car texture to the pipeline + pipeRender.SetTexture(sprCar); + // Apply "world" transform to pipeline + pipeRender.SetTransform(matCar); + + // The car has transparency, so enable it + SetPixelMode(olc::Pixel::ALPHA); + // Render the quad + pipeRender.Render(meshFlat.tris); + // Set transparency back to none to optimise drawing other pixels + SetPixelMode(olc::Pixel::NORMAL); + + + // Draw the current camera position for debug information + //DrawString(10, 30, "CX: " + std::to_string(fCameraX) + " CY: " + std::to_string(fCameraY) + " CZ: " + std::to_string(fCameraZ)); + return true; + } +}; + + + +int main() +{ + CarCrimeCity demo; + if (demo.Construct(768, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part1/car_top.png b/Videos/CarCrimeCity/Part1/car_top.png new file mode 100644 index 0000000..ad89ae4 Binary files /dev/null and b/Videos/CarCrimeCity/Part1/car_top.png differ diff --git a/Videos/CarCrimeCity/Part1/car_top1.png b/Videos/CarCrimeCity/Part1/car_top1.png new file mode 100644 index 0000000..15ceb1d Binary files /dev/null and b/Videos/CarCrimeCity/Part1/car_top1.png differ diff --git a/Videos/CarCrimeCity/Part1/example1.city b/Videos/CarCrimeCity/Part1/example1.city new file mode 100644 index 0000000..47dfb0b Binary files /dev/null and b/Videos/CarCrimeCity/Part1/example1.city differ diff --git a/Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h b/Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h new file mode 100644 index 0000000..9c6dd80 --- /dev/null +++ b/Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h @@ -0,0 +1,1174 @@ +/* + olcPGEX_Graphics3D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | 3D Rendering - v0.1 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + support for software rendering 3D graphics. + + NOTE!!! This file is under development and may change! + + 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_GFX3D +#define OLC_PGEX_GFX3D + +#include +#include +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX3D : public olc::PGEX + { + + public: + + struct vec2d + { + float x = 0; + float y = 0; + float z = 0; + }; + + struct vec3d + { + float x = 0; + float y = 0; + float z = 0; + float w = 1; // Need a 4th term to perform sensible matrix vector multiplication + }; + + struct triangle + { + vec3d p[3]; + vec2d t[3]; + olc::Pixel col; + }; + + struct mat4x4 + { + float m[4][4] = { 0 }; + }; + + struct mesh + { + std::vector tris; + }; + + class Math + { + public: + inline Math(); + public: + inline static vec3d Mat_MultiplyVector(mat4x4 &m, vec3d &i); + inline static mat4x4 Mat_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2); + inline static mat4x4 Mat_MakeIdentity(); + inline static mat4x4 Mat_MakeRotationX(float fAngleRad); + inline static mat4x4 Mat_MakeRotationY(float fAngleRad); + inline static mat4x4 Mat_MakeRotationZ(float fAngleRad); + inline static mat4x4 Mat_MakeScale(float x, float y, float z); + inline static mat4x4 Mat_MakeTranslation(float x, float y, float z); + inline static mat4x4 Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar); + inline static mat4x4 Mat_PointAt(vec3d &pos, vec3d &target, vec3d &up); + inline static mat4x4 Mat_QuickInverse(mat4x4 &m); // Only for Rotation/Translation Matrices + inline static mat4x4 Mat_Inverse(olc::GFX3D::mat4x4 &m); + + inline static vec3d Vec_Add(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Sub(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Mul(vec3d &v1, float k); + inline static vec3d Vec_Div(vec3d &v1, float k); + inline static float Vec_DotProduct(vec3d &v1, vec3d &v2); + inline static float Vec_Length(vec3d &v); + inline static vec3d Vec_Normalise(vec3d &v); + inline static vec3d Vec_CrossProduct(vec3d &v1, vec3d &v2); + inline static vec3d Vec_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd, float &t); + + inline static int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2); + }; + + enum RENDERFLAGS + { + RENDER_WIRE = 0x01, + RENDER_FLAT = 0x02, + RENDER_TEXTURED = 0x04, + RENDER_CULL_CW = 0x08, + RENDER_CULL_CCW = 0x10, + RENDER_DEPTH = 0x20, + }; + + + class PipeLine + { + public: + PipeLine(); + + public: + void SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight); + void SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up); + void SetTransform(olc::GFX3D::mat4x4 &transform); + void SetTexture(olc::Sprite *texture); + void SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col); + uint32_t Render(std::vector &triangles, uint32_t flags = RENDER_CULL_CW | RENDER_TEXTURED | RENDER_DEPTH); + + private: + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::mat4x4 matView; + olc::GFX3D::mat4x4 matWorld; + olc::Sprite *sprTexture; + float fViewX; + float fViewY; + float fViewW; + float fViewH; + }; + + + + public: + //static const int RF_TEXTURE = 0x00000001; + //static const int RF_ = 0x00000002; + + inline static void ConfigureDisplay(); + inline static void ClearDepth(); + inline static void AddTriangleToScene(olc::GFX3D::triangle &tri); + inline static void RenderScene(); + + inline static void DrawTriangleFlat(olc::GFX3D::triangle &tri); + inline static void DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col = olc::WHITE); + inline static void DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr); + inline static void TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr); + + // Draws a sprite with the transform applied + //inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + + private: + static float* m_DepthBuffer; + }; +} + + + + +namespace olc +{ + olc::GFX3D::Math::Math() + { + + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Mat_MultiplyVector(olc::GFX3D::mat4x4 &m, olc::GFX3D::vec3d &i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeIdentity() + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationX(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationY(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationZ(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeScale(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = x; + matrix.m[1][1] = y; + matrix.m[2][2] = z; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeTranslation(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MultiplyMatrix(olc::GFX3D::mat4x4 &m1, olc::GFX3D::mat4x4 &m2) + { + olc::GFX3D::mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_PointAt(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &target, olc::GFX3D::vec3d &up) + { + // Calculate new forward direction + olc::GFX3D::vec3d newForward = Vec_Sub(target, pos); + newForward = Vec_Normalise(newForward); + + // Calculate new Up direction + olc::GFX3D::vec3d a = Vec_Mul(newForward, Vec_DotProduct(up, newForward)); + olc::GFX3D::vec3d newUp = Vec_Sub(up, a); + newUp = Vec_Normalise(newUp); + + // New Right direction is easy, its just cross product + olc::GFX3D::vec3d newRight = Vec_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_QuickInverse(olc::GFX3D::mat4x4 &m) // Only for Rotation/Translation Matrices + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_Inverse(olc::GFX3D::mat4x4 &m) + { + double det; + + + mat4x4 matInv; + + matInv.m[0][0] = m.m[1][1] * m.m[2][2] * m.m[3][3] - m.m[1][1] * m.m[2][3] * m.m[3][2] - m.m[2][1] * m.m[1][2] * m.m[3][3] + m.m[2][1] * m.m[1][3] * m.m[3][2] + m.m[3][1] * m.m[1][2] * m.m[2][3] - m.m[3][1] * m.m[1][3] * m.m[2][2]; + matInv.m[1][0] = -m.m[1][0] * m.m[2][2] * m.m[3][3] + m.m[1][0] * m.m[2][3] * m.m[3][2] + m.m[2][0] * m.m[1][2] * m.m[3][3] - m.m[2][0] * m.m[1][3] * m.m[3][2] - m.m[3][0] * m.m[1][2] * m.m[2][3] + m.m[3][0] * m.m[1][3] * m.m[2][2]; + matInv.m[2][0] = m.m[1][0] * m.m[2][1] * m.m[3][3] - m.m[1][0] * m.m[2][3] * m.m[3][1] - m.m[2][0] * m.m[1][1] * m.m[3][3] + m.m[2][0] * m.m[1][3] * m.m[3][1] + m.m[3][0] * m.m[1][1] * m.m[2][3] - m.m[3][0] * m.m[1][3] * m.m[2][1]; + matInv.m[3][0] = -m.m[1][0] * m.m[2][1] * m.m[3][2] + m.m[1][0] * m.m[2][2] * m.m[3][1] + m.m[2][0] * m.m[1][1] * m.m[3][2] - m.m[2][0] * m.m[1][2] * m.m[3][1] - m.m[3][0] * m.m[1][1] * m.m[2][2] + m.m[3][0] * m.m[1][2] * m.m[2][1]; + matInv.m[0][1] = -m.m[0][1] * m.m[2][2] * m.m[3][3] + m.m[0][1] * m.m[2][3] * m.m[3][2] + m.m[2][1] * m.m[0][2] * m.m[3][3] - m.m[2][1] * m.m[0][3] * m.m[3][2] - m.m[3][1] * m.m[0][2] * m.m[2][3] + m.m[3][1] * m.m[0][3] * m.m[2][2]; + matInv.m[1][1] = m.m[0][0] * m.m[2][2] * m.m[3][3] - m.m[0][0] * m.m[2][3] * m.m[3][2] - m.m[2][0] * m.m[0][2] * m.m[3][3] + m.m[2][0] * m.m[0][3] * m.m[3][2] + m.m[3][0] * m.m[0][2] * m.m[2][3] - m.m[3][0] * m.m[0][3] * m.m[2][2]; + matInv.m[2][1] = -m.m[0][0] * m.m[2][1] * m.m[3][3] + m.m[0][0] * m.m[2][3] * m.m[3][1] + m.m[2][0] * m.m[0][1] * m.m[3][3] - m.m[2][0] * m.m[0][3] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[2][3] + m.m[3][0] * m.m[0][3] * m.m[2][1]; + matInv.m[3][1] = m.m[0][0] * m.m[2][1] * m.m[3][2] - m.m[0][0] * m.m[2][2] * m.m[3][1] - m.m[2][0] * m.m[0][1] * m.m[3][2] + m.m[2][0] * m.m[0][2] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[2][2] - m.m[3][0] * m.m[0][2] * m.m[2][1]; + matInv.m[0][2] = m.m[0][1] * m.m[1][2] * m.m[3][3] - m.m[0][1] * m.m[1][3] * m.m[3][2] - m.m[1][1] * m.m[0][2] * m.m[3][3] + m.m[1][1] * m.m[0][3] * m.m[3][2] + m.m[3][1] * m.m[0][2] * m.m[1][3] - m.m[3][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][2] = -m.m[0][0] * m.m[1][2] * m.m[3][3] + m.m[0][0] * m.m[1][3] * m.m[3][2] + m.m[1][0] * m.m[0][2] * m.m[3][3] - m.m[1][0] * m.m[0][3] * m.m[3][2] - m.m[3][0] * m.m[0][2] * m.m[1][3] + m.m[3][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][2] = m.m[0][0] * m.m[1][1] * m.m[3][3] - m.m[0][0] * m.m[1][3] * m.m[3][1] - m.m[1][0] * m.m[0][1] * m.m[3][3] + m.m[1][0] * m.m[0][3] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[1][3] - m.m[3][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][2] = -m.m[0][0] * m.m[1][1] * m.m[3][2] + m.m[0][0] * m.m[1][2] * m.m[3][1] + m.m[1][0] * m.m[0][1] * m.m[3][2] - m.m[1][0] * m.m[0][2] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[1][2] + m.m[3][0] * m.m[0][2] * m.m[1][1]; + matInv.m[0][3] = -m.m[0][1] * m.m[1][2] * m.m[2][3] + m.m[0][1] * m.m[1][3] * m.m[2][2] + m.m[1][1] * m.m[0][2] * m.m[2][3] - m.m[1][1] * m.m[0][3] * m.m[2][2] - m.m[2][1] * m.m[0][2] * m.m[1][3] + m.m[2][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][3] = m.m[0][0] * m.m[1][2] * m.m[2][3] - m.m[0][0] * m.m[1][3] * m.m[2][2] - m.m[1][0] * m.m[0][2] * m.m[2][3] + m.m[1][0] * m.m[0][3] * m.m[2][2] + m.m[2][0] * m.m[0][2] * m.m[1][3] - m.m[2][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][3] = -m.m[0][0] * m.m[1][1] * m.m[2][3] + m.m[0][0] * m.m[1][3] * m.m[2][1] + m.m[1][0] * m.m[0][1] * m.m[2][3] - m.m[1][0] * m.m[0][3] * m.m[2][1] - m.m[2][0] * m.m[0][1] * m.m[1][3] + m.m[2][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][3] = m.m[0][0] * m.m[1][1] * m.m[2][2] - m.m[0][0] * m.m[1][2] * m.m[2][1] - m.m[1][0] * m.m[0][1] * m.m[2][2] + m.m[1][0] * m.m[0][2] * m.m[2][1] + m.m[2][0] * m.m[0][1] * m.m[1][2] - m.m[2][0] * m.m[0][2] * m.m[1][1]; + + det = m.m[0][0] * matInv.m[0][0] + m.m[0][1] * matInv.m[1][0] + m.m[0][2] * matInv.m[2][0] + m.m[0][3] * matInv.m[3][0]; + // if (det == 0) return false; + + det = 1.0 / det; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + matInv.m[i][j] *= (float)det; + + return matInv; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Add(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Sub(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Mul(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Div(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float olc::GFX3D::Math::Vec_DotProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z; + } + + float olc::GFX3D::Math::Vec_Length(olc::GFX3D::vec3d &v) + { + return sqrtf(Vec_DotProduct(v, v)); + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Normalise(olc::GFX3D::vec3d &v) + { + float l = Vec_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_CrossProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_IntersectPlane(olc::GFX3D::vec3d &plane_p, olc::GFX3D::vec3d &plane_n, olc::GFX3D::vec3d &lineStart, olc::GFX3D::vec3d &lineEnd, float &t) + { + plane_n = Vec_Normalise(plane_n); + float plane_d = -Vec_DotProduct(plane_n, plane_p); + float ad = Vec_DotProduct(lineStart, plane_n); + float bd = Vec_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + olc::GFX3D::vec3d lineStartToEnd = Vec_Sub(lineEnd, lineStart); + olc::GFX3D::vec3d lineToIntersect = Vec_Mul(lineStartToEnd, t); + return Vec_Add(lineStart, lineToIntersect); + } + + + int olc::GFX3D::Math::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Math::Vec_Normalise(plane_n); + + out_tri1.t[0] = in_tri.t[0]; + out_tri2.t[0] = in_tri.t[0]; + out_tri1.t[1] = in_tri.t[1]; + out_tri2.t[1] = in_tri.t[1]; + out_tri1.t[2] = in_tri.t[2]; + out_tri2.t[2] = in_tri.t[2]; + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d &p) + { + vec3d n = Math::Vec_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Math::Vec_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.t[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.t[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.t[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.t[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.t[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.t[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col = olc::MAGENTA;// in_tri.col; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[1].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[1].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[1].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.t[2].x = t * (outside_tex[1]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[1]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[1]->z - inside_tex[0]->z) + inside_tex[0]->z; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col = olc::GREEN;// in_tri.col; + out_tri2.col = olc::RED;// in_tri.col; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + out_tri1.p[1] = *inside_points[1]; + out_tri1.t[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[2].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[1] = *inside_points[1]; + out_tri2.t[1] = *inside_tex[1]; + out_tri2.p[0] = out_tri1.p[2]; + out_tri2.t[0] = out_tri1.t[2]; + out_tri2.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.t[2].x = t * (outside_tex[0]->x - inside_tex[1]->x) + inside_tex[1]->x; + out_tri2.t[2].y = t * (outside_tex[0]->y - inside_tex[1]->y) + inside_tex[1]->y; + out_tri2.t[2].z = t * (outside_tex[0]->z - inside_tex[1]->z) + inside_tex[1]->z; + return 2; // Return two newly formed triangles which form a quad + } + + return 0; + } + + void GFX3D::DrawTriangleFlat(olc::GFX3D::triangle &tri) + { + pge->FillTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, tri.col); + } + + void GFX3D::DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col) + { + pge->DrawTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, col); + } + + void GFX3D::TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr) + + { + if (y2 < y1) + { + std::swap(y1, y2); + std::swap(x1, x2); + std::swap(u1, u2); + std::swap(v1, v2); + std::swap(w1, w2); + } + + if (y3 < y1) + { + std::swap(y1, y3); + std::swap(x1, x3); + std::swap(u1, u3); + std::swap(v1, v3); + std::swap(w1, w3); + } + + if (y3 < y2) + { + std::swap(y2, y3); + std::swap(x2, x3); + std::swap(u2, u3); + std::swap(v2, v3); + std::swap(w2, w3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + + float tex_u, tex_v, tex_w; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0, dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + } + } + } + + + void GFX3D::DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr) + { + if (tri.p[1].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[1].y); + std::swap(tri.p[0].x, tri.p[1].x); + std::swap(tri.t[0].x, tri.t[1].x); + std::swap(tri.t[0].y, tri.t[1].y); + std::swap(tri.t[0].z, tri.t[1].z); + } + + if (tri.p[2].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[2].y); + std::swap(tri.p[0].x, tri.p[2].x); + std::swap(tri.t[0].x, tri.t[2].x); + std::swap(tri.t[0].y, tri.t[2].y); + std::swap(tri.t[0].z, tri.t[2].z); + } + + if (tri.p[2].y < tri.p[1].y) + { + std::swap(tri.p[1].y, tri.p[2].y); + std::swap(tri.p[1].x, tri.p[2].x); + std::swap(tri.t[1].x, tri.t[2].x); + std::swap(tri.t[1].y, tri.t[2].y); + std::swap(tri.t[1].z, tri.t[2].z); + } + + int dy1 = tri.p[1].y - tri.p[0].y; + int dx1 = tri.p[1].x - tri.p[0].x; + float dv1 = tri.t[1].y - tri.t[0].y; + float du1 = tri.t[1].x - tri.t[0].x; + float dz1 = tri.t[1].z - tri.t[0].z; + + int dy2 = tri.p[2].y - tri.p[0].y; + int dx2 = tri.p[2].x - tri.p[0].x; + float dv2 = tri.t[2].y - tri.t[0].y; + float du2 = tri.t[2].x - tri.t[0].x; + float dz2 = tri.t[2].z - tri.t[0].z; + + float tex_x, tex_y, tex_z; + + float du1_step = 0, dv1_step = 0, du2_step = 0, dv2_step = 0, dz1_step = 0, dz2_step = 0; + float dax_step = 0, dbx_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dz2_step = dz2 / (float)abs(dy2); + + + + if (dy1) + { + for (int i = tri.p[0].y; i <= tri.p[1].y; i++) + { + int ax = tri.p[0].x + (i - tri.p[0].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[0].x + (float)(i - tri.p[0].y) * du1_step; + float tex_sv = tri.t[0].y + (float)(i - tri.p[0].y) * dv1_step; + float tex_sz = tri.t[0].z + (float)(i - tri.p[0].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + t += tstep; + } + + + } + } + + dy1 = tri.p[2].y - tri.p[1].y; + dx1 = tri.p[2].x - tri.p[1].x; + dv1 = tri.t[2].y - tri.t[1].y; + du1 = tri.t[2].x - tri.t[1].x; + dz1 = tri.t[2].z - tri.t[1].z; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + + du1_step = 0, dv1_step = 0;// , dz1_step = 0;// , du2_step = 0, dv2_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy1) + { + for (int i = tri.p[1].y; i <= tri.p[2].y; i++) + { + int ax = tri.p[1].x + (i - tri.p[1].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[1].x + (float)(i - tri.p[1].y) * du1_step; + float tex_sv = tri.t[1].y + (float)(i - tri.p[1].y) * dv1_step; + float tex_sz = tri.t[1].z + (float)(i - tri.p[1].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + + t += tstep; + } + } + } + + } + + float* GFX3D::m_DepthBuffer = nullptr; + + void GFX3D::ConfigureDisplay() + { + m_DepthBuffer = new float[pge->ScreenWidth() * pge->ScreenHeight()]{ 0 }; + } + + + void GFX3D::ClearDepth() + { + memset(m_DepthBuffer, 0, pge->ScreenWidth() * pge->ScreenHeight() * sizeof(float)); + } + + + + + GFX3D::PipeLine::PipeLine() + { + + } + + void GFX3D::PipeLine::SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight) + { + matProj = GFX3D::Math::Mat_MakeProjection(fFovDegrees, fAspectRatio, fNear, fFar); + fViewX = fLeft; + fViewY = fTop; + fViewW = fWidth; + fViewH = fHeight; + } + + void GFX3D::PipeLine::SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up) + { + matView = GFX3D::Math::Mat_PointAt(pos, lookat, up); + matView = GFX3D::Math::Mat_QuickInverse(matView); + } + + void GFX3D::PipeLine::SetTransform(olc::GFX3D::mat4x4 &transform) + { + matWorld = transform; + } + + void GFX3D::PipeLine::SetTexture(olc::Sprite *texture) + { + sprTexture = texture; + } + + void GFX3D::PipeLine::SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col) + { + + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags) + { + // Calculate Transformation Matrix + mat4x4 matWorldView = Math::Mat_MultiplyMatrix(matWorld, matView); + //matWorldViewProj = Math::Mat_MultiplyMatrix(matWorldView, matProj); + + // Store triangles for rastering later + std::vector vecTrianglesToRaster; + + int nTriangleDrawnCount = 0; + + // Process Triangles + for (auto &tri : triangles) + { + GFX3D::triangle triTransformed; + + // Just copy through texture coordinates + triTransformed.t[0] = { tri.t[0].x, tri.t[0].y, tri.t[0].z }; + triTransformed.t[1] = { tri.t[1].x, tri.t[1].y, tri.t[1].z }; + triTransformed.t[2] = { tri.t[2].x, tri.t[2].y, tri.t[2].z }; // Think! + + // Transform Triangle from object into projected space + triTransformed.p[0] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[0]); + triTransformed.p[1] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[1]); + triTransformed.p[2] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[2]); + + // Calculate Triangle Normal in WorldView Space + GFX3D::vec3d normal, line1, line2; + line1 = GFX3D::Math::Vec_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = GFX3D::Math::Vec_Sub(triTransformed.p[2], triTransformed.p[0]); + normal = GFX3D::Math::Vec_CrossProduct(line1, line2); + normal = GFX3D::Math::Vec_Normalise(normal); + + // Cull triangles that face away from viewer + if (flags & RENDER_CULL_CW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) > 0.0f) continue; + if (flags & RENDER_CULL_CCW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) < 0.0f) continue; + + // If Lighting, calculate shading + triTransformed.col = olc::WHITE; + + // Clip triangle against near plane + int nClippedTriangles = 0; + triangle clipped[2]; + nClippedTriangles = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triTransformed, clipped[0], clipped[1]); + + // This may yield two new triangles + for (int n = 0; n < nClippedTriangles; n++) + { + triangle triProjected = clipped[n]; + + // Project new triangle + triProjected.p[0] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[2]); + + // Apply Projection to Verts + triProjected.p[0].x = triProjected.p[0].x / triProjected.p[0].w; + triProjected.p[1].x = triProjected.p[1].x / triProjected.p[1].w; + triProjected.p[2].x = triProjected.p[2].x / triProjected.p[2].w; + + triProjected.p[0].y = triProjected.p[0].y / triProjected.p[0].w; + triProjected.p[1].y = triProjected.p[1].y / triProjected.p[1].w; + triProjected.p[2].y = triProjected.p[2].y / triProjected.p[2].w; + + triProjected.p[0].z = triProjected.p[0].z / triProjected.p[0].w; + triProjected.p[1].z = triProjected.p[1].z / triProjected.p[1].w; + triProjected.p[2].z = triProjected.p[2].z / triProjected.p[2].w; + + // Apply Projection to Tex coords + triProjected.t[0].x = triProjected.t[0].x / triProjected.p[0].w; + triProjected.t[1].x = triProjected.t[1].x / triProjected.p[1].w; + triProjected.t[2].x = triProjected.t[2].x / triProjected.p[2].w; + + triProjected.t[0].y = triProjected.t[0].y / triProjected.p[0].w; + triProjected.t[1].y = triProjected.t[1].y / triProjected.p[1].w; + triProjected.t[2].y = triProjected.t[2].y / triProjected.p[2].w; + + triProjected.t[0].z = 1.0f / triProjected.p[0].w; + triProjected.t[1].z = 1.0f / triProjected.p[1].w; + triProjected.t[2].z = 1.0f / triProjected.p[2].w; + + // Clip against viewport in screen space + // Clip triangles against all four screen edges, this could yield + // a bunch of triangles, so create a queue that we traverse to + // ensure we only test new triangles generated against planes + triangle sclipped[2]; + std::list listTriangles; + + + // Add initial triangle + listTriangles.push_back(triProjected); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 1: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, +1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 2: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 3: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ +1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + } + + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(sclipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto &triRaster : listTriangles) + { + // Scale to viewport + /*triRaster.p[0].x *= -1.0f; + triRaster.p[1].x *= -1.0f; + triRaster.p[2].x *= -1.0f; + triRaster.p[0].y *= -1.0f; + triRaster.p[1].y *= -1.0f; + triRaster.p[2].y *= -1.0f;*/ + vec3d vOffsetView = { 1,1,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + triRaster.p[0].x *= 0.5f * fViewW; + triRaster.p[0].y *= 0.5f * fViewH; + triRaster.p[1].x *= 0.5f * fViewW; + triRaster.p[1].y *= 0.5f * fViewH; + triRaster.p[2].x *= 0.5f * fViewW; + triRaster.p[2].y *= 0.5f * fViewH; + vOffsetView = { fViewX,fViewY,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + + // For now, just draw triangle + + if (flags & RENDER_TEXTURED) + { + TexturedTriangle( + triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, + triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, + triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, + sprTexture); + } + + if (flags & RENDER_WIRE) + { + DrawTriangleWire(triRaster, olc::RED); + } + + if (flags & RENDER_FLAT) + { + DrawTriangleFlat(triRaster); + } + + nTriangleDrawnCount++; + } + } + } + + return nTriangleDrawnCount; + } +} + +#endif \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part1/olcPixelGameEngine.h b/Videos/CarCrimeCity/Part1/olcPixelGameEngine.h new file mode 100644 index 0000000..0c524ac --- /dev/null +++ b/Videos/CarCrimeCity/Part1/olcPixelGameEngine.h @@ -0,0 +1,2067 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.12 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprsing and wonderful + success for me, and I'm delighted how people have reacted so + positively towards it, so thanks for that. + + However, there are limitations that I simply cannot avoid. + Firstly, I need to maintain several different versions of + it to accommodate users on Windows7, 8, 10, Linux, Mac, + Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities + and the effect is becoming underwhelming. The engine itself + is not slow at all, but the process that Windows uses to + draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours + and glyphs. Sadly I have no control over this, and recent + videos that are extremely graphical (for a command prompt :P ) + have been dipping to unacceptable framerates. As the channel + has been popular with aspiring game developers, I'm concerned + that the visual appeal of the command prompt is perhaps + limited to us oldies, and I dont want to alienate younger + learners. Finally, I'd like to demonstrate many more + algorithms and image processing that exist in the graphical + domain, for which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look + and feel to the programmer is almost identical, so all of my + existing code from the videos is easily portable, and the + programmer uses this file in exactly the same way. But I've + decided that rather than just build a command prompt emulator, + that I would at least harness some modern(ish) portable + technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is + written in a cross-platform style, uses modern(ish) C++ + conventions and most importantly, renders much much faster. I + will use this version when my applications are predominantly + graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command + prompt silliness to come yet, but evolution is important!! + + 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 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Thanks + ~~~~~~ + I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, + JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice + Ralakus, Gorbit99, raoul & MagetzUb for advice, ideas and testing, and I'd like + to extend my appreciation to the 23K YouTube followers and 1.5K Discord server + members who give me the motivation to keep going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set tru for all frames between pressed and released events + }; + + //============================================================= + + class ResourcePack + { + public: + 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)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + void SetPixel(int32_t x, int32_t y, Pixel p); + Pixel Sample(float x, float y); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual void Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + +#ifdef _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 + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + Consider the following project structure: + + Class1.h - Includes olcPixelGameEngine.h, overrides olc::PixelGameEngine + Class1.cpp - #define OLC_PGE_APPLICATION #include "Class1.h" + Class2.h - Includes Class1.h, which includes olcPixelGameEngine.h + Class2.cpp - #define OLC_PGE_APPLICATION #include "Class2.h" + main.cpp - Includes Class1.h and Class2.h + + If all of this is a bit too confusing, you can split this file in two! + Everything below this comment block can go into olcPixelGameEngineOOP.cpp + and everything above it can go into olcPixelGameEngineOOP.h + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + + std::wstring ConvertS2W(std::string s) + { +#ifdef _WIN32 + 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); + std::wstring w(buffer); + delete[] buffer; + return w; +#endif +//#ifdef __MINGW32__ +// wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; +// mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); +// buffer[sImageFile.length()] = L'\0'; +// wsImageFile = buffer; +// delete[] buffer; +//#else + } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _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 + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + void Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + pColData[y*width + x] = p; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = (int32_t)((x * (float)width) - 0.5f); + int32_t sy = (int32_t)((y * (float)height) - 0.5f); + return GetPixel(sx, sy); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + size_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(size_t)); + for (size_t i = 0; i < nMapEntries; i++) + { + size_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(size_t)); + + std::string sFileName(nFilePathSize, ' '); + for (size_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + 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 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + void PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return; + + + if (nPixelMode == Pixel::NORMAL) + { + pDrawTarget->SetPixel(x, y, p); + return; + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + pDrawTarget->SetPixel(x, y, p); + return; + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + return; + } + + if (nPixelMode == Pixel::CUSTOM) + { + pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + return; + } + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + Draw(x - x0, y - y0, p);//upper left left + Draw(x - y0, y - x0, p);//upper upper left + Draw(x + y0, y - x0, p);//upper upper right + Draw(x + x0, y - y0, p);//upper right right + Draw(x - x0, y + y0, p);//lower left left + Draw(x - y0, y + x0, p);//lower lower left + Draw(x + y0, y + x0, p);//lower lower right + Draw(x + x0, y + y0, p);//lower right right + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + nMousePosX = x / (int32_t)nPixelWidth; + nMousePosY = y / (int32_t)nPixelHeight; + + if (nMousePosX >= (int32_t)nScreenWidth) + nMousePosX = nScreenWidth - 1; + if (nMousePosY >= (int32_t)nScreenHeight) + nMousePosY = nScreenHeight - 1; + + if (nMousePosX < 0) + nMousePosX = 0; + if (nMousePosY < 0) + nMousePosY = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // 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 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + pMouseNewState[xev.xbutton.button-1] = true; + } + else if (xev.type == ButtonRelease) + { + pMouseNewState[xev.xbutton.button-1] = false; + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE; + RECT rWndRect = { 0, 0, (LONG)nScreenWidth * (LONG)nPixelWidth, (LONG)nScreenHeight * (LONG)nPixelHeight }; + + // Keep client size as requested + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + // Create Keyboard Mapping + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + if (glSwapIntervalEXT) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + 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"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h b/Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h new file mode 100644 index 0000000..ddb7c22 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h @@ -0,0 +1,256 @@ +/* +** $Id: lauxlib.h,v 1.129 2015/11/23 11:29:43 roberto Exp $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "lua.h" + + + +/* extra error code for 'luaL_load' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +typedef struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + char initb[LUAL_BUFFERSIZE]; /* initial buffer */ +} luaL_Buffer; + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + + + +/* compatibility with old module system */ +#if defined(LUA_COMPAT_MODULE) + +LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, + int sizehint); +LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); + +#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0)) + +#endif + + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/Videos/CarCrimeCity/Part2/Lua533/include/lua.h b/Videos/CarCrimeCity/Part2/Lua533/include/lua.h new file mode 100644 index 0000000..f78899f --- /dev/null +++ b/Videos/CarCrimeCity/Part2/Lua533/include/lua.h @@ -0,0 +1,486 @@ +/* +** $Id: lua.h,v 1.331 2016/05/30 15:53:28 roberto Exp $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "3" +#define LUA_VERSION_NUM 503 +#define LUA_VERSION_RELEASE "3" + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2016 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRGCMM 5 +#define LUA_ERRERR 6 + + +typedef struct lua_State lua_State; + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTAGS 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API const lua_Number *(lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int n); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API int (lua_getuservalue) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *name); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API void (lua_setuservalue) (lua_State *L, int idx); + + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) + + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + + + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) \ + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros for unsigned conversions +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debugger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2016 Lua.org, PUC-Rio. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + + +#endif diff --git a/Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp b/Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp new file mode 100644 index 0000000..ec417f5 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h b/Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h new file mode 100644 index 0000000..867e9cb --- /dev/null +++ b/Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h @@ -0,0 +1,769 @@ +/* +** $Id: luaconf.h,v 1.255 2016/05/01 20:06:09 roberto Exp $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance compiling it with 32-bit numbers or +** restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You +** can also define LUA_32BITS in the make file, but changing here you +** ensure that all software connected to Lua will be compiled with the +** same configuration. +*/ +/* #define LUA_32BITS */ + + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#define LUA_USE_READLINE /* needs some extra libraries */ +#endif + + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#define LUA_USE_READLINE /* needs an extra library: -lreadline */ +#endif + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS +#endif + + + +/* +@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. +*/ +/* avoid undefined shifts */ +#if ((INT_MAX >> 15) >> 15) >= 1 +#define LUAI_BITSINT 32 +#else +/* 'int' always must have at least 16 bits */ +#define LUAI_BITSINT 16 +#endif + + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options (if supported +** by your C compiler). The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif defined(LUA_C89_NUMBERS) /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE +#endif + +/* }================================================================== */ + + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll;" \ + LUA_CDIR"?53.dll;" ".\\?53.dll" + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so;" \ + LUA_CDIR"lib?53.so;" "./lib?53.so" +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* more often than not the libs go together with the core */ +#define LUALIB_API LUA_API +#define LUAMOD_API LUALIB_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables +** that are not to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_2) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_BITLIB controls the presence of library 'bit32'. +*/ +#define LUA_COMPAT_BITLIB + +/* +@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. +*/ +#define LUA_COMPAT_IPAIRS + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +*/ +#define LUA_COMPAT_APIINTCASTS + +#endif /* } */ + + +#if defined(LUA_COMPAT_5_1) /* { */ + +/* Incompatibilities from 5.2 -> 5.3 */ +#define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_APIINTCASTS + +/* +@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. +** You can replace it with 'table.unpack'. +*/ +#define LUA_COMPAT_UNPACK + +/* +@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'. +** You can replace it with 'package.searchers'. +*/ +#define LUA_COMPAT_LOADERS + +/* +@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall. +** You can call your C function directly (with light C functions). +*/ +#define lua_cpcall(L,f,u) \ + (lua_pushcfunction(L, (f)), \ + lua_pushlightuserdata(L,(u)), \ + lua_pcall(L,1,0,0)) + + +/* +@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library. +** You can rewrite 'log10(x)' as 'log(x, 10)'. +*/ +#define LUA_COMPAT_LOG10 + +/* +@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base +** library. You can rewrite 'loadstring(s)' as 'load(s)'. +*/ +#define LUA_COMPAT_LOADSTRING + +/* +@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library. +*/ +#define LUA_COMPAT_MAXN + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +/* +@@ LUA_COMPAT_MODULE controls compatibility with previous +** module functions 'module' (Lua) and 'luaL_register' (C). +*/ +#define LUA_COMPAT_MODULE + +#endif /* } */ + + +/* +@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a +@@ a float mark ('.0'). +** This macro is not on by default even in compatibility mode, +** because this is not really an incompatibility. +*/ +/* #define LUA_COMPAT_FLOATSTRING */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@@ over a floating number. +@@ l_mathlim(x) corrects limit name 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeric string to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) l_sprintf((s), sz, LUA_NUMBER_FMT, (n)) + +/* +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_mathlim(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_mathlim(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_mathlim(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + + + +/* +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of an 'usual argument conversion' +@@ over a lUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" +#define lua_integer2str(s,sz,n) l_sprintf((s), sz, LUA_INTEGER_FMT, (n)) + +#define LUAI_UACINT LUA_INTEGER + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + +/* +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_number2strx converts a float to an hexadecimal numeric string. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) ((void)L, l_sprintf(b,sz,f,n)) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). You probably do not want/need to change them. +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. (For long double, +** 'string.format("%.99f", 1e4932)' needs ~5030 bytes, so a +** smaller buffer would force a memory allocation for each call to +** 'string.format'.) +*/ +#if defined(LUA_FLOAT_LONGDOUBLE) +#define LUAL_BUFFERSIZE 8192 +#else +#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) +#endif + +/* }================================================================== */ + + +/* +@@ LUA_QL describes how error messages quote program elements. +** Lua does not use these macros anymore; they are here for +** compatibility only. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/Videos/CarCrimeCity/Part2/Lua533/include/lualib.h b/Videos/CarCrimeCity/Part2/Lua533/include/lualib.h new file mode 100644 index 0000000..5165c0f --- /dev/null +++ b/Videos/CarCrimeCity/Part2/Lua533/include/lualib.h @@ -0,0 +1,58 @@ +/* +** $Id: lualib.h,v 1.44 2014/02/06 17:32:33 roberto Exp $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + +#define LUA_BITLIBNAME "bit32" +LUAMOD_API int (luaopen_bit32) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#if !defined(lua_assert) +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/Videos/CarCrimeCity/Part2/Lua533/liblua53.a b/Videos/CarCrimeCity/Part2/Lua533/liblua53.a new file mode 100644 index 0000000..32646db Binary files /dev/null and b/Videos/CarCrimeCity/Part2/Lua533/liblua53.a differ diff --git a/Videos/CarCrimeCity/Part2/Lua533/lua53.dll b/Videos/CarCrimeCity/Part2/Lua533/lua53.dll new file mode 100644 index 0000000..2ab8168 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/Lua533/lua53.dll differ diff --git a/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj new file mode 100644 index 0000000..fe83e45 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj @@ -0,0 +1,716 @@ +# Blender v2.79 (sub 0) OBJ File: 'olc_building0.blend' +# www.blender.org +mtllib olc_building0.mtl +o Plane +v 0.000000 0.000000 0.000000 +v 0.000000 -1.000000 0.000000 +v -1.000000 0.000000 0.000000 +v -1.000000 -1.000000 0.000000 +v -0.025000 -0.412671 -0.052133 +v -0.025000 -0.587329 -0.052133 +v -0.975000 -0.025000 -0.050000 +v -0.975000 -0.975000 -0.050000 +v -1.000000 0.000000 -0.050000 +v 0.000000 0.000000 -0.050000 +v 0.000000 -1.000000 -0.050000 +v -1.000000 -1.000000 -0.050000 +v -0.975000 -0.025000 -0.517611 +v -0.975000 -0.975000 -0.517611 +v -1.000000 0.000000 -0.527316 +v 0.000000 0.000000 -0.527316 +v 0.000000 -1.000000 -0.527316 +v -1.000000 -1.000000 -0.527316 +v -0.975000 -0.025000 -0.550000 +v -0.025000 -0.025000 -0.550000 +v -0.025000 -0.975000 -0.550000 +v -0.975000 -0.975000 -0.550000 +v -0.025000 -0.601364 -0.402078 +v -0.025000 -0.601364 -0.044706 +v -0.025000 -0.398636 -0.044706 +v -0.025000 -0.398636 -0.402078 +v -0.374620 -0.412671 -0.045097 +v -0.374620 -0.587329 -0.047815 +v -0.374620 -0.587329 -0.388044 +v -0.374620 -0.412671 -0.388044 +v -0.005099 -0.601364 -0.402078 +v -0.005099 -0.601364 -0.044706 +v -0.005099 -0.398636 -0.044706 +v -0.005099 -0.398636 -0.402078 +v -0.005099 -0.587329 -0.388044 +v -0.005099 -0.587329 -0.047815 +v -0.005099 -0.412671 -0.045097 +v -0.005099 -0.412671 -0.388044 +v -0.017305 -0.419472 -0.044438 +v -0.017305 -0.594130 -0.044438 +v -0.025000 -0.975000 -0.517611 +v -0.025000 -0.975000 -0.050000 +v -0.025000 -0.601364 -0.402078 +v -0.025000 -0.601364 -0.044706 +v -0.025000 -0.025000 -0.050000 +v -0.025000 -0.025000 -0.517611 +v -0.025000 -0.398636 -0.044706 +v -0.025000 -0.398636 -0.402078 +v -0.925000 -0.075000 -1.550000 +v -0.075000 -0.075000 -1.550000 +v -0.075000 -0.925000 -1.550000 +v -0.925000 -0.925000 -1.550000 +v -0.975000 -0.025000 -1.550000 +v -0.025000 -0.025000 -1.550000 +v -0.025000 -0.975000 -1.550000 +v -0.975000 -0.975000 -1.550000 +v -0.975000 -0.025000 -1.600000 +v -0.025000 -0.025000 -1.600000 +v -0.025000 -0.975000 -1.600000 +v -0.975000 -0.975000 -1.600000 +v -0.925000 -0.075000 -1.600000 +v -0.075000 -0.075000 -1.600000 +v -0.075000 -0.925000 -1.600000 +v -0.925000 -0.925000 -1.600000 +v 0.160449 -0.127295 -1.107365 +v 0.160449 -0.127295 -1.287365 +v -0.026426 -0.127295 -1.107365 +v -0.026426 -0.127295 -1.287365 +v 0.160449 -0.427295 -1.107365 +v 0.160449 -0.427295 -1.287365 +v -0.026426 -0.427295 -1.107365 +v -0.026426 -0.427295 -1.287365 +v -0.026426 -0.127295 -1.287365 +v -0.026426 -0.127295 -1.107365 +v 0.160449 -0.127295 -1.287365 +v 0.160449 -0.127295 -1.107365 +v 0.160449 -0.427295 -1.287365 +v 0.160449 -0.427295 -1.107365 +v 0.160449 -0.127295 -1.287365 +v 0.160449 -0.127295 -1.107365 +v -0.026426 -0.427295 -1.287365 +v -0.026426 -0.427295 -1.107365 +v 0.160449 -0.427295 -1.287365 +v 0.160449 -0.427295 -1.107365 +v -0.157753 -0.144986 -1.539514 +v -0.157753 -0.144986 -1.637282 +v -0.240677 -0.144986 -1.539514 +v -0.240677 -0.144986 -1.637282 +v -0.157753 -0.328413 -1.539514 +v -0.157753 -0.328413 -1.637282 +v -0.240677 -0.328413 -1.539514 +v -0.240677 -0.328413 -1.637282 +v -0.480538 -0.445573 -1.455033 +v -0.480538 -0.445573 -1.817243 +v -0.732419 -0.445573 -1.455033 +v -0.732419 -0.445573 -1.817243 +v -0.480538 -0.823959 -1.455033 +v -0.480538 -0.823959 -1.685969 +v -0.732419 -0.823959 -1.455033 +v -0.732419 -0.823959 -1.685969 +v -0.157753 -0.144986 -1.539514 +v -0.157753 -0.144986 -1.637282 +v -0.240677 -0.144986 -1.539514 +v -0.240677 -0.144986 -1.637282 +v -0.157753 -0.328413 -1.539514 +v -0.157753 -0.328413 -1.637282 +v -0.240677 -0.328413 -1.539514 +v -0.240677 -0.328413 -1.637282 +v -0.026426 -0.585532 -1.287365 +v -0.026426 -0.585532 -1.107365 +v 0.160449 -0.585532 -1.287365 +v 0.160449 -0.585532 -1.107365 +v 0.160449 -0.885532 -1.287365 +v 0.160449 -0.885532 -1.107365 +v 0.160449 -0.585532 -1.287365 +v 0.160449 -0.585532 -1.107365 +v -0.026426 -0.885532 -1.287365 +v -0.026426 -0.885532 -1.107365 +v 0.160449 -0.885532 -1.287365 +v 0.160449 -0.885532 -1.107365 +v 0.160449 -0.586497 -0.622366 +v 0.160449 -0.586497 -0.802366 +v -0.026426 -0.586497 -0.622366 +v -0.026426 -0.586497 -0.802366 +v 0.160449 -0.886497 -0.802366 +v 0.160449 -0.886497 -0.622366 +v 0.160449 -0.586497 -0.802366 +v 0.160449 -0.586497 -0.622366 +v -0.026426 -0.886497 -0.802366 +v -0.026426 -0.886497 -0.622366 +v 0.160449 -0.886497 -0.802366 +v 0.160449 -0.886497 -0.622366 +v 0.160449 -0.126028 -0.622366 +v 0.160449 -0.126028 -0.802366 +v -0.026426 -0.126028 -0.622366 +v -0.026426 -0.126028 -0.802366 +v 0.160449 -0.426028 -0.802366 +v 0.160449 -0.426028 -0.622366 +v 0.160449 -0.126028 -0.802366 +v 0.160449 -0.126028 -0.622366 +v -0.026426 -0.426028 -0.802366 +v -0.026426 -0.426028 -0.622366 +v 0.160449 -0.426028 -0.802366 +v 0.160449 -0.426028 -0.622366 +v -0.026426 -0.585532 -1.287365 +v -0.026426 -0.585532 -1.107365 +v 0.160449 -0.585532 -1.287365 +v 0.160449 -0.585532 -1.107365 +v 0.160449 -0.885532 -1.287365 +v 0.160449 -0.885532 -1.107365 +v 0.160449 -0.585532 -1.287365 +v 0.160449 -0.585532 -1.107365 +v -0.026426 -0.885532 -1.287365 +v -0.026426 -0.885532 -1.107365 +v 0.160449 -0.885532 -1.287365 +v 0.160449 -0.885532 -1.107365 +v 0.160449 -0.586497 -0.622366 +v 0.160449 -0.586497 -0.802366 +v -0.026426 -0.586497 -0.622366 +v -0.026426 -0.586497 -0.802366 +v 0.160449 -0.886497 -0.802366 +v 0.160449 -0.886497 -0.622366 +v 0.160449 -0.586497 -0.802366 +v 0.160449 -0.586497 -0.622366 +v -0.026426 -0.886497 -0.802366 +v -0.026426 -0.886497 -0.622366 +v 0.160449 -0.886497 -0.802366 +v 0.160449 -0.886497 -0.622366 +v 0.160449 -0.126028 -0.622366 +v 0.160449 -0.126028 -0.802366 +v -0.026426 -0.126028 -0.622366 +v -0.026426 -0.126028 -0.802366 +v 0.160449 -0.426028 -0.802366 +v 0.160449 -0.426028 -0.622366 +v 0.160449 -0.126028 -0.802366 +v 0.160449 -0.126028 -0.622366 +v -0.026426 -0.426028 -0.802366 +v -0.026426 -0.426028 -0.622366 +v 0.160449 -0.426028 -0.802366 +v 0.160449 -0.426028 -0.622366 +v 0.160449 -0.584850 -1.107365 +v -0.026426 -0.584850 -1.107365 +v 0.160449 -0.884850 -1.107365 +v -0.026426 -0.884850 -1.107365 +v 0.160449 -0.127295 -0.622373 +v -0.026426 -0.127295 -0.622373 +v 0.160449 -0.427295 -0.622373 +v -0.026426 -0.427295 -0.622373 +v 0.160449 -0.584850 -0.622373 +v -0.026426 -0.584850 -0.622373 +v 0.160449 -0.884850 -0.622373 +v -0.026426 -0.884850 -0.622373 +vt 0.014847 0.403582 +vt 0.153703 0.694145 +vt 0.014847 0.694145 +vt 0.034115 0.694145 +vt 0.048963 1.000000 +vt 0.034115 1.000000 +vt 0.047359 0.372997 +vt 0.032511 0.067142 +vt 0.047358 0.067142 +vt 0.048963 0.694145 +vt 0.063810 1.000000 +vt 0.048963 1.000000 +vt 0.019268 0.694145 +vt 0.034115 1.000000 +vt 0.019268 1.000000 +vt 0.710478 0.701791 +vt 1.000000 0.694145 +vt 0.992576 0.701791 +vt 1.000000 1.000000 +vt 0.992576 0.992353 +vt 0.703054 1.000000 +vt 0.710478 0.992354 +vt 0.703054 0.694145 +vt 1.000000 0.051849 +vt 0.717901 0.357704 +vt 0.717901 0.051849 +vt 0.047359 0.082435 +vt 0.186214 0.372997 +vt 0.047359 0.372997 +vt 0.186214 0.082435 +vt 0.325069 0.372997 +vt 0.186214 0.372997 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +vt 0.703054 0.694145 +vt 0.413541 0.701796 +vt 0.406108 0.694145 +vt 0.703054 1.000000 +vt 0.695640 0.701796 +vt 0.406108 1.000000 +vt 0.695640 0.992358 +vt 0.413541 0.992358 +vt 0.004421 0.885721 +vt 0.002849 0.709438 +vt 0.004421 0.823716 +vt 0.118100 0.779467 +vt 0.115894 0.721754 +vt 0.118100 0.726047 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +vt 0.119844 0.783760 +vt 0.124009 0.890693 +vt 0.124009 0.890693 +vt 0.509125 0.003716 +vt 0.515034 0.113020 +vt 0.509125 0.113020 +vt 0.616064 0.113020 +vt 0.717901 0.000000 +vt 0.717901 0.113020 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +vt 0.119842 0.995708 +vt 0.124009 0.890696 +vt 0.124009 1.000000 +vt 0.067977 0.995708 +vt 0.063810 0.890696 +vt 0.067977 0.890815 +vt 0.063810 1.000000 +vt 0.515034 0.113020 +vt 0.616064 0.000000 +vt 0.616064 0.113020 +vt 0.118100 0.721754 +vt 0.124009 0.783760 +vt 0.118100 0.783760 +vt 0.509125 0.113020 +vt 0.503215 0.003716 +vt 0.509125 0.003716 +vt 0.115815 0.777387 +vt 0.115815 0.723967 +vt 0.431190 0.196713 +vt 0.465497 0.082435 +vt 0.465497 0.372997 +vt 0.465497 0.113020 +vt 0.717901 0.372997 +vt 0.465497 0.372997 +vt 0.717901 0.372997 +vt 0.435802 0.678852 +vt 0.435802 0.372997 +vt 1.000000 0.372997 +vt 0.717901 0.678852 +vt 0.717901 0.372997 +vt 0.435802 0.372997 +vt 0.153703 0.678852 +vt 0.153703 0.372997 +vt 0.717901 0.678852 +vt 0.435802 0.694145 +vt 0.017664 0.372997 +vt 0.002817 0.113020 +vt 0.017664 0.113020 +vt 1.000000 0.357704 +vt 0.717901 0.372997 +vt 0.032511 0.372997 +vt 0.017664 0.372997 +vt 0.138856 0.724730 +vt 0.406108 0.709437 +vt 0.391260 0.724730 +vt 0.406108 1.000000 +vt 0.391260 0.984707 +vt 0.124009 1.000000 +vt 0.138856 0.984707 +vt 0.124009 0.709438 +vt 0.014847 0.694145 +vt 0.000000 0.434168 +vt 0.014847 0.434168 +vt 1.000000 0.678852 +vt 0.717901 0.694145 +vt 0.435802 0.678852 +vt 0.153703 0.694145 +vt 0.004421 0.740023 +vt 0.019268 1.000000 +vt 0.004421 1.000000 +vt 0.316450 0.067652 +vt 0.252222 0.005787 +vt 0.316449 0.005787 +vt 0.483784 0.067652 +vt 0.419557 0.005787 +vt 0.483784 0.005787 +vt 0.419557 0.067652 +vt 0.248111 0.067096 +vt 0.165675 0.015745 +vt 0.248111 0.015745 +vt 0.252222 0.005787 +vt 0.316450 0.067652 +vt 0.316449 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.419557 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.483784 0.005787 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.525322 0.836366 +vt 0.586878 0.919862 +vt 0.525322 0.919862 +vt 0.552285 0.749140 +vt 0.608723 0.836366 +vt 0.552285 0.836366 +vt 0.586878 0.894429 +vt 0.643316 0.836366 +vt 0.643316 0.894429 +vt 0.520203 0.749140 +vt 0.463765 0.836366 +vt 0.463765 0.749140 +vt 0.463765 0.836366 +vt 0.525322 0.928691 +vt 0.463765 0.928691 +vt 0.892836 0.808021 +vt 0.844939 0.752099 +vt 0.892836 0.752099 +vt 0.884262 0.808021 +vt 0.827791 0.912939 +vt 0.827791 0.808021 +vt 0.940733 0.808021 +vt 0.892836 0.752099 +vt 0.940733 0.752099 +vt 0.884262 0.912939 +vt 0.940733 0.808021 +vt 0.940733 0.912939 +vt 0.779894 0.808021 +vt 0.827791 0.912939 +vt 0.779894 0.912939 +vt 0.252222 0.005787 +vt 0.316450 0.067652 +vt 0.316449 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.419557 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.483784 0.005787 +vt 0.252222 0.005787 +vt 0.316450 0.067652 +vt 0.316449 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.419557 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.483784 0.005787 +vt 0.252222 0.005787 +vt 0.316450 0.067652 +vt 0.316449 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.419557 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.483784 0.005787 +vt 0.316450 0.067652 +vt 0.252222 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.316449 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.419557 0.005787 +vt 0.483784 0.005787 +vt 0.316450 0.067652 +vt 0.252222 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.316449 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.419557 0.005787 +vt 0.483784 0.005787 +vt 0.316450 0.067652 +vt 0.252222 0.005787 +vt 0.316449 0.005787 +vt 0.419557 0.067652 +vt 0.316449 0.005787 +vt 0.419557 0.005787 +vt 0.483784 0.067652 +vt 0.419557 0.005787 +vt 0.483784 0.005787 +vt 0.248111 0.067096 +vt 0.165675 0.015745 +vt 0.248111 0.015745 +vt 0.248111 0.067096 +vt 0.165675 0.015745 +vt 0.248111 0.015745 +vt 0.248111 0.067096 +vt 0.165675 0.015745 +vt 0.248111 0.015745 +vt 0.153703 0.403582 +vt 0.048963 0.694145 +vt 0.032511 0.372997 +vt 0.063810 0.694145 +vt 0.034115 0.694145 +vt 0.186214 0.082435 +vt 0.325069 0.082435 +vt 0.002849 1.000000 +vt 0.115894 0.783760 +vt 1.000000 1.000000 +vt 0.119840 0.890696 +vt 0.067976 0.890695 +vt 0.067979 0.783760 +vt 0.063810 0.890691 +vt 0.063810 0.890691 +vt 0.515034 0.003716 +vt 0.616064 0.000000 +vt 0.119842 0.891647 +vt 0.515034 0.000000 +vt 0.124009 0.721754 +vt 0.503215 0.113020 +vt 0.326641 0.372997 +vt 0.431190 0.258718 +vt 0.325069 0.258718 +vt 0.325069 0.196713 +vt 0.326641 0.082435 +vt 0.717901 0.113020 +vt 0.717901 0.694145 +vt 0.002817 0.372997 +vt 1.000000 0.372997 +vt 0.032511 0.113020 +vt 0.000000 0.694145 +vt 1.000000 0.694145 +vt 0.435802 0.694145 +vt 0.019268 0.740023 +vt 0.252222 0.067652 +vt 0.165675 0.067096 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.586878 0.836366 +vt 0.640804 0.749140 +vt 0.586878 0.836366 +vt 0.552285 0.836366 +vt 0.844939 0.808021 +vt 0.884262 0.912939 +vt 0.892836 0.808021 +vt 0.884262 0.808021 +vt 0.827791 0.808021 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.252222 0.067652 +vt 0.316450 0.067652 +vt 0.419557 0.067652 +vt 0.165675 0.067096 +vt 0.165675 0.067096 +vt 0.165675 0.067096 +vn 0.0000 -1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 0.6720 -0.7406 +vn 0.6720 0.0000 -0.7406 +vn 0.0000 -0.6720 -0.7406 +vn -0.6720 0.0000 -0.7406 +vn 0.7071 0.0000 -0.7071 +vn 0.0000 -0.3278 -0.9448 +vn -0.0178 0.4677 -0.8837 +vn -0.0123 0.0000 -0.9999 +vn -0.0201 0.0156 -0.9997 +vn -0.0109 -0.4677 -0.8838 +usemtl Material.001 +s off +f 8/1/1 41/2/1 42/3/1 +f 3/4/2 12/5/2 4/6/2 +f 2/7/3 10/8/3 1/9/3 +f 4/10/1 11/11/1 2/12/1 +f 1/13/4 9/14/4 3/15/4 +f 7/16/5 10/17/5 45/18/5 +f 45/18/5 11/19/5 42/20/5 +f 42/20/5 12/21/5 8/22/5 +f 8/22/5 9/23/5 7/16/5 +f 22/24/1 55/25/1 21/26/1 +f 45/27/4 13/28/4 7/29/4 +f 7/30/2 14/31/2 8/32/2 +f 43/33/6 26/34/6 23/35/6 +f 16/36/7 19/37/7 15/38/7 +f 17/39/8 20/40/8 16/36/8 +f 18/41/9 21/42/9 17/39/9 +f 15/38/10 22/43/10 18/41/10 +f 47/44/2 42/45/2 44/46/2 +f 5/47/3 44/48/3 6/49/3 +f 48/50/6 25/51/6 26/34/6 +f 27/52/6 25/53/6 47/54/6 +f 25/55/4 34/56/4 26/57/4 +f 30/58/1 37/59/1 27/60/1 +f 44/61/6 23/35/6 24/62/6 +f 35/63/3 32/64/3 31/65/3 +f 38/66/3 33/67/3 37/68/3 +f 35/63/3 34/69/3 38/66/3 +f 28/70/4 35/71/4 29/72/4 +f 26/73/5 31/74/5 23/75/5 +f 23/76/1 32/77/1 24/78/1 +f 6/49/11 39/79/11 40/80/11 +f 43/81/3 41/82/3 46/83/3 +f 51/84/5 49/85/5 50/86/5 +f 20/87/4 53/88/4 19/89/4 +f 19/90/2 56/91/2 22/92/2 +f 21/93/3 54/94/3 20/95/3 +f 54/96/4 57/97/4 53/88/4 +f 52/98/3 61/99/3 49/100/3 +f 56/101/1 59/102/1 55/25/1 +f 49/100/1 62/103/1 50/104/1 +f 61/105/5 58/106/5 62/107/5 +f 62/107/5 59/108/5 63/109/5 +f 63/109/5 60/110/5 64/111/5 +f 64/111/5 57/112/5 61/105/5 +f 50/113/2 63/114/2 51/115/2 +f 53/116/2 60/117/2 56/91/2 +f 55/118/3 58/119/3 54/94/3 +f 51/120/4 64/121/4 52/122/4 +f 66/123/4 67/124/4 65/125/4 +f 72/126/1 69/127/1 71/128/1 +f 70/129/3 65/125/3 69/127/3 +f 65/130/5 71/131/5 67/132/5 +f 74/133/1 75/134/1 76/135/1 +f 80/136/2 77/137/2 78/138/2 +f 84/139/4 81/140/4 82/141/4 +f 86/142/4 87/143/4 85/144/4 +f 88/145/2 91/146/2 87/143/2 +f 92/147/1 89/148/1 91/146/1 +f 90/149/3 85/144/3 89/148/3 +f 88/145/5 90/149/5 92/147/5 +f 94/150/4 95/151/4 93/152/4 +f 95/153/2 100/154/2 99/155/2 +f 100/156/1 97/157/1 99/158/1 +f 98/159/3 93/160/3 97/161/3 +f 96/162/12 98/163/12 100/164/12 +f 102/165/4 103/166/4 101/167/4 +f 104/168/2 107/169/2 103/170/2 +f 108/171/1 105/172/1 107/173/1 +f 106/174/3 101/175/3 105/176/3 +f 104/177/5 106/178/5 108/179/5 +f 110/180/1 111/181/1 112/182/1 +f 116/183/2 113/184/2 114/185/2 +f 120/186/4 117/187/4 118/188/4 +f 123/189/1 122/190/1 121/191/1 +f 128/192/2 125/193/2 126/194/2 +f 132/195/4 129/196/4 130/197/4 +f 135/198/1 134/199/1 133/200/1 +f 140/201/2 137/202/2 138/203/2 +f 144/204/4 141/205/4 142/206/4 +f 147/207/4 146/208/4 148/209/4 +f 149/210/3 152/211/3 150/212/3 +f 153/213/1 156/214/1 154/215/1 +f 158/216/4 159/217/4 157/218/4 +f 161/219/3 164/220/3 162/221/3 +f 165/222/1 168/223/1 166/224/1 +f 170/225/4 171/226/4 169/227/4 +f 173/228/3 176/229/3 174/230/3 +f 177/231/1 180/232/1 178/233/1 +f 181/234/5 184/235/5 182/236/5 +f 185/237/5 188/238/5 186/239/5 +f 189/240/5 192/241/5 190/242/5 +f 8/1/1 14/243/1 41/2/1 +f 3/4/2 9/244/2 12/5/2 +f 2/7/3 11/245/3 10/8/3 +f 4/10/1 12/246/1 11/11/1 +f 1/13/4 10/247/4 9/14/4 +f 7/16/5 9/23/5 10/17/5 +f 45/18/5 10/17/5 11/19/5 +f 42/20/5 11/19/5 12/21/5 +f 8/22/5 12/21/5 9/23/5 +f 22/24/1 56/101/1 55/25/1 +f 45/27/4 46/248/4 13/28/4 +f 7/30/2 13/249/2 14/31/2 +f 43/33/6 48/50/6 26/34/6 +f 16/36/7 20/40/7 19/37/7 +f 17/39/8 21/42/8 20/40/8 +f 18/41/9 22/43/9 21/42/9 +f 15/38/10 19/37/10 22/43/10 +f 47/44/2 45/250/2 42/45/2 +f 5/47/3 47/251/3 44/48/3 +f 48/50/6 47/252/6 25/51/6 +f 47/54/13 5/253/13 27/52/13 +f 5/253/14 6/254/14 28/255/14 +f 44/256/6 24/257/6 28/255/6 +f 5/253/15 28/255/15 27/52/15 +f 6/254/16 44/256/16 28/255/16 +f 25/55/4 33/258/4 34/56/4 +f 30/58/1 38/259/1 37/59/1 +f 44/61/6 43/33/6 23/35/6 +f 35/63/3 36/260/3 32/64/3 +f 38/66/3 34/69/3 33/67/3 +f 35/63/3 31/65/3 34/69/3 +f 28/70/4 36/261/4 35/71/4 +f 26/73/5 34/262/5 31/74/5 +f 23/76/1 31/263/1 32/77/1 +f 6/49/11 5/47/11 39/79/11 +f 46/83/3 45/264/3 48/265/3 +f 45/264/3 47/266/3 48/265/3 +f 44/267/3 42/268/3 43/81/3 +f 42/268/3 41/82/3 43/81/3 +f 46/83/3 48/265/3 43/81/3 +f 51/84/5 52/269/5 49/85/5 +f 20/87/4 54/96/4 53/88/4 +f 19/90/2 53/116/2 56/91/2 +f 21/93/3 55/118/3 54/94/3 +f 54/96/4 58/270/4 57/97/4 +f 52/98/3 64/271/3 61/99/3 +f 56/101/1 60/272/1 59/102/1 +f 49/100/1 61/273/1 62/103/1 +f 61/105/5 57/112/5 58/106/5 +f 62/107/5 58/106/5 59/108/5 +f 63/109/5 59/108/5 60/110/5 +f 64/111/5 60/110/5 57/112/5 +f 50/113/2 62/274/2 63/114/2 +f 53/116/2 57/275/2 60/117/2 +f 55/118/3 59/276/3 58/119/3 +f 51/120/4 63/277/4 64/121/4 +f 66/123/4 68/278/4 67/124/4 +f 72/126/1 70/129/1 69/127/1 +f 70/129/3 66/123/3 65/125/3 +f 65/130/5 69/279/5 71/131/5 +f 74/133/1 73/280/1 75/134/1 +f 80/136/2 79/281/2 77/137/2 +f 84/139/4 83/282/4 81/140/4 +f 86/142/4 88/145/4 87/143/4 +f 88/145/2 92/147/2 91/146/2 +f 92/147/1 90/149/1 89/148/1 +f 90/149/3 86/142/3 85/144/3 +f 88/145/5 86/142/5 90/149/5 +f 94/150/4 96/283/4 95/151/4 +f 95/153/2 96/284/2 100/154/2 +f 100/156/1 98/285/1 97/157/1 +f 98/159/3 94/286/3 93/160/3 +f 96/162/12 94/150/12 98/163/12 +f 102/165/4 104/287/4 103/166/4 +f 104/168/2 108/288/2 107/169/2 +f 108/171/1 106/289/1 105/172/1 +f 106/174/3 102/290/3 101/175/3 +f 104/177/5 102/291/5 106/178/5 +f 110/180/1 109/292/1 111/181/1 +f 116/183/2 115/293/2 113/184/2 +f 120/186/4 119/294/4 117/187/4 +f 123/189/1 124/295/1 122/190/1 +f 128/192/2 127/296/2 125/193/2 +f 132/195/4 131/297/4 129/196/4 +f 135/198/1 136/298/1 134/199/1 +f 140/201/2 139/299/2 137/202/2 +f 144/204/4 143/300/4 141/205/4 +f 147/207/4 145/301/4 146/208/4 +f 149/210/3 151/302/3 152/211/3 +f 153/213/1 155/303/1 156/214/1 +f 158/216/4 160/304/4 159/217/4 +f 161/219/3 163/305/3 164/220/3 +f 165/222/1 167/306/1 168/223/1 +f 170/225/4 172/307/4 171/226/4 +f 173/228/3 175/308/3 176/229/3 +f 177/231/1 179/309/1 180/232/1 +f 181/234/5 183/310/5 184/235/5 +f 185/237/5 187/311/5 188/238/5 +f 189/240/5 191/312/5 192/241/5 diff --git a/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png new file mode 100644 index 0000000..746be5b Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend new file mode 100644 index 0000000..bcc3b6c Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend differ diff --git a/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 new file mode 100644 index 0000000..f0ef2f6 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 differ diff --git a/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj new file mode 100644 index 0000000..3683256 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj @@ -0,0 +1,26 @@ +# Blender v2.79 (sub 0) OBJ File: 'unit_building.blend' +# www.blender.org +v 1.0000 1.000 -0.000 +v 1.0000 1.0 -0.50 +v 0.0000 1.00 -0.000 +v 0.0000 1.000 -0.5 +v 1.0 0.00 0.000 +v 1.0 0.00000 -0.5 +v -0.000081 0.004528 0.004407 +v -0.000081 0.000101 -0.495573 +vn 0.0002 1.0000 -0.0089 +vn -1.0000 0.0002 -0.0000 +vn -0.0002 -1.0000 0.0089 +vn 1.0000 -0.0002 0.0000 +vn -0.0000 -0.0089 -1.0000 +s off +f 2//1 3//1 1//1 +f 4//2 7//2 3//2 +f 8//3 5//3 7//3 +f 6//4 1//4 5//4 +f 4//5 6//5 8//5 +f 2//1 4//1 3//1 +f 4//2 8//2 7//2 +f 8//3 6//3 5//3 +f 6//4 2//4 1//4 +f 4//5 2//5 6//5 diff --git a/Videos/CarCrimeCity/Part2/assets/cities/example1.city b/Videos/CarCrimeCity/Part2/assets/cities/example1.city new file mode 100644 index 0000000..47dfb0b Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/cities/example1.city differ diff --git a/Videos/CarCrimeCity/Part2/assets/config.lua b/Videos/CarCrimeCity/Part2/assets/config.lua new file mode 100644 index 0000000..bf19e0e --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/config.lua @@ -0,0 +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} + diff --git a/Videos/CarCrimeCity/Part2/assets/system/car_top.png b/Videos/CarCrimeCity/Part2/assets/system/car_top.png new file mode 100644 index 0000000..ad89ae4 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/car_top.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/car_top3.png b/Videos/CarCrimeCity/Part2/assets/system/car_top3.png new file mode 100644 index 0000000..25db6a7 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/car_top3.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png b/Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png new file mode 100644 index 0000000..fd02a35 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/clouds1.png b/Videos/CarCrimeCity/Part2/assets/system/clouds1.png new file mode 100644 index 0000000..e0f20f4 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/clouds1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/clouds2.png b/Videos/CarCrimeCity/Part2/assets/system/clouds2.png new file mode 100644 index 0000000..400f99a Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/clouds2.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/grass1.png b/Videos/CarCrimeCity/Part2/assets/system/grass1.png new file mode 100644 index 0000000..bb1c9bf Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/grass1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/roads1.png b/Videos/CarCrimeCity/Part2/assets/system/roads1.png new file mode 100644 index 0000000..24dce8c Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/roads1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/roads2.png b/Videos/CarCrimeCity/Part2/assets/system/roads2.png new file mode 100644 index 0000000..e2b6231 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/roads2.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/roads3.png b/Videos/CarCrimeCity/Part2/assets/system/roads3.png new file mode 100644 index 0000000..95b8752 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/roads3.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/roads4.png b/Videos/CarCrimeCity/Part2/assets/system/roads4.png new file mode 100644 index 0000000..04d56a4 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/roads4.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png b/Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png new file mode 100644 index 0000000..78c9b3d Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/water1.png b/Videos/CarCrimeCity/Part2/assets/system/water1.png new file mode 100644 index 0000000..9194ae0 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/water1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/system/waterside1.png b/Videos/CarCrimeCity/Part2/assets/system/waterside1.png new file mode 100644 index 0000000..9cb1f44 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/system/waterside1.png differ diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj new file mode 100644 index 0000000..0952689 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj @@ -0,0 +1,121 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v 0.600000 -0.400000 -1.700000 +v 0.600000 1.700000 -1.700000 +v -0.600000 -0.400000 -1.700000 +v -0.600000 1.700000 -1.700000 +v -0.800000 2.000000 -1.100000 +v 0.800000 2.000000 -1.100000 +v -0.800000 2.000000 -1.100000 +v 0.800000 2.000000 -1.100000 +v -0.800000 -1.000000 -0.950000 +v 0.800000 -1.000000 -0.950000 +v -0.800000 -2.000000 -0.900000 +v 0.800000 -2.000000 -0.900000 +v -0.800000 2.000000 -0.400000 +v 0.800000 2.000000 -0.400000 +v -0.800000 -2.000000 -0.400000 +v 0.800000 -2.000000 -0.400000 +v 0.800000 -2.000000 -0.400000 +v 0.800000 2.000000 -0.400000 +v 0.800000 -2.000000 -0.900000 +v 0.800000 2.000000 -1.100000 +v -0.800000 -2.000000 -0.900000 +v -0.800000 2.000000 -1.100000 +v -0.800000 -2.000000 -0.400000 +v -0.800000 2.000000 -0.400000 +v 0.780000 1.200000 -0.400000 +v 0.780000 1.500000 -0.400000 +v 0.780000 1.400000 0.000000 +v 0.780000 1.000000 0.000000 +v 0.780000 0.900000 -0.400000 +v 0.780000 -1.100000 -0.400000 +v 0.780000 -0.800000 -0.400000 +v 0.780000 -0.900000 0.000000 +v 0.780000 -1.300000 0.000000 +v 0.780000 -1.400000 -0.400000 +v -0.780000 -1.100000 -0.400000 +v -0.780000 -1.400000 -0.400000 +v -0.780000 -1.300000 0.000000 +v -0.780000 -0.900000 0.000000 +v -0.780000 -0.800000 -0.400000 +v -0.780000 1.200000 -0.400000 +v -0.780000 0.900000 -0.400000 +v -0.780000 1.000000 0.000000 +v -0.780000 1.400000 0.000000 +v -0.780000 1.500000 -0.400000 +vt 0.564792 0.689981 +vt 0.564792 0.953949 +vt 0.338554 0.953949 +vt 0.338554 0.689981 +vt 0.334296 0.692438 +vt 0.334296 0.954445 +vt 0.203294 0.986197 +vt 0.203294 0.656186 +vt 0.695616 0.984433 +vt 0.572654 0.959219 +vt 0.695616 0.657719 +vt 0.572795 0.683358 +vt 0.941945 0.999442 +vt 0.694306 0.999442 +vt 0.941945 0.647349 +vt 0.694306 0.647349 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.331416 0.690927 +vt 0.238667 0.577426 +vt 0.563274 0.690927 +vt 0.735343 0.574985 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.743883 0.446331 +vt 0.779872 0.385852 +vt 0.817351 0.446331 +vt 0.853830 0.385852 +vt 0.890820 0.446331 +s off +f 1/1 3/2 4/3 +f 2/4 1/1 4/3 +f 2/5 4/6 5/7 +f 5/7 6/8 2/5 +f 9/9 3/10 10/11 +f 3/10 1/12 10/11 +f 11/13 9/14 12/15 +f 10/16 12/15 9/14 +f 8/17 7/18 13/19 +f 13/19 14/20 8/17 +f 15/21 11/22 16/23 +f 12/24 16/23 11/22 +f 2/25 6/26 1/27 +f 10/28 1/27 6/26 +f 4/25 3/27 5/26 +f 9/28 5/26 3/27 +f 17/29 19/30 20/31 +f 18/32 17/29 20/31 +f 21/30 23/29 24/32 +f 22/31 21/30 24/32 +f 26/33 27/34 25/35 +f 27/34 28/36 25/35 +f 28/36 29/37 25/35 +f 31/38 32/39 30/40 +f 32/39 33/41 30/40 +f 33/41 34/42 30/40 +f 36/42 37/41 35/40 +f 37/41 38/39 35/40 +f 38/39 39/38 35/40 +f 41/37 42/36 40/35 +f 42/36 43/34 40/35 +f 43/34 44/33 40/35 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj new file mode 100644 index 0000000..77daa43 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj @@ -0,0 +1,127 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v 0.600000 -0.200000 -1.100000 +v 0.600000 1.000000 -1.100000 +v -0.600000 -0.200000 -1.100000 +v -0.600000 1.000000 -1.100000 +v -0.800000 1.400000 -0.700000 +v 0.800000 1.400000 -0.700000 +v -0.800000 2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -1.000000 -0.700000 +v 0.800000 -1.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v 0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v -0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v 0.800000 -2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.300000 +v -0.800000 2.000000 -0.300000 +v 0.780000 1.200000 -0.300000 +v 0.780000 1.500000 -0.300000 +v 0.780000 1.400000 0.000000 +v 0.780000 1.000000 0.000000 +v 0.780000 0.900000 -0.300000 +v 0.780000 -1.100000 -0.300000 +v 0.780000 -0.800000 -0.300000 +v 0.780000 -0.900000 0.000000 +v 0.780000 -1.300000 0.000000 +v 0.780000 -1.400000 -0.300000 +v -0.780000 -1.100000 -0.300000 +v -0.780000 -1.400000 -0.300000 +v -0.780000 -1.300000 0.000000 +v -0.780000 -0.900000 0.000000 +v -0.780000 -0.800000 -0.300000 +v -0.780000 1.200000 -0.300000 +v -0.780000 0.900000 -0.300000 +v -0.780000 1.000000 0.000000 +v -0.780000 1.400000 0.000000 +v -0.780000 1.500000 -0.300000 +vt 0.564792 0.689981 +vt 0.564792 0.953949 +vt 0.338554 0.953949 +vt 0.338554 0.689981 +vt 0.334296 0.692438 +vt 0.334296 0.954445 +vt 0.203294 0.986197 +vt 0.203294 0.656186 +vt 0.203326 0.657931 +vt 0.203326 0.986286 +vt 0.080997 0.986286 +vt 0.080997 0.657931 +vt 0.695616 0.984433 +vt 0.572654 0.959219 +vt 0.695616 0.657719 +vt 0.572795 0.683358 +vt 0.941945 0.999442 +vt 0.694306 0.999442 +vt 0.941945 0.647349 +vt 0.694306 0.647349 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.331416 0.690927 +vt 0.238667 0.577426 +vt 0.563274 0.690927 +vt 0.735343 0.574985 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.743883 0.446331 +vt 0.779872 0.385852 +vt 0.817351 0.446331 +vt 0.853830 0.385852 +vt 0.890820 0.446331 +s off +f 1/1 3/2 4/3 +f 2/4 1/1 4/3 +f 2/5 4/6 5/7 +f 5/7 6/8 2/5 +f 6/9 5/10 7/11 +f 7/11 8/12 6/9 +f 9/13 3/14 10/15 +f 3/14 1/16 10/15 +f 11/17 9/18 12/19 +f 10/20 12/19 9/18 +f 8/21 7/22 13/23 +f 13/23 14/24 8/21 +f 15/25 11/26 16/27 +f 12/28 16/27 11/26 +f 2/29 6/30 1/31 +f 10/32 1/31 6/30 +f 4/29 3/31 5/30 +f 9/32 5/30 3/31 +f 17/33 19/34 20/35 +f 18/36 17/33 20/35 +f 21/34 23/33 24/36 +f 22/35 21/34 24/36 +f 26/37 27/38 25/39 +f 27/38 28/40 25/39 +f 28/40 29/41 25/39 +f 31/42 32/43 30/44 +f 32/43 33/45 30/44 +f 33/45 34/46 30/44 +f 36/46 37/45 35/44 +f 37/45 38/43 35/44 +f 38/43 39/42 35/44 +f 41/41 42/40 40/39 +f 42/40 43/38 40/39 +f 43/38 44/37 40/39 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj new file mode 100644 index 0000000..f4806a9 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj @@ -0,0 +1,137 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v 0.600000 -1.900000 -1.500000 +v 0.600000 -1.000000 -1.500000 +v -0.600000 -1.900000 -1.500000 +v -0.600000 -1.000000 -1.500000 +v -0.800000 -0.900000 -0.700000 +v 0.800000 -0.900000 -0.700000 +v -0.800000 0.600000 -0.700000 +v 0.800000 0.600000 -0.700000 +v -0.800000 -2.400000 -0.700000 +v 0.800000 -2.400000 -0.700000 +v -0.800000 -2.400000 -0.700000 +v 0.800000 -2.400000 -0.700000 +v -0.800000 0.600000 -0.300000 +v 0.800000 0.600000 -0.300000 +v -0.800000 -2.400000 -0.300000 +v 0.800000 -2.400000 -0.300000 +v 0.800000 -2.400000 -0.300000 +v 0.800000 0.600000 -0.300000 +v 0.800000 -2.400000 -0.700000 +v 0.800000 0.600000 -0.700000 +v -0.800000 -2.400000 -0.700000 +v -0.800000 0.600000 -0.700000 +v -0.800000 -2.400000 -0.300000 +v -0.800000 0.600000 -0.300000 +v 0.780000 0.100000 -0.300000 +v 0.780000 0.400000 -0.300000 +v 0.780000 0.300000 0.000000 +v 0.780000 -0.100000 0.000000 +v 0.780000 -0.200000 -0.300000 +v 0.780000 -1.900000 -0.300000 +v 0.780000 -1.600000 -0.300000 +v 0.780000 -1.700000 0.000000 +v 0.780000 -2.100000 0.000000 +v 0.780000 -2.200000 -0.300000 +v -0.780000 -1.900000 -0.300000 +v -0.780000 -2.200000 -0.300000 +v -0.780000 -2.100000 0.000000 +v -0.780000 -1.700000 0.000000 +v -0.780000 -1.600000 -0.300000 +v -0.780000 0.100000 -0.300000 +v -0.780000 -0.200000 -0.300000 +v -0.780000 -0.100000 0.000000 +v -0.780000 0.300000 0.000000 +v -0.780000 0.400000 -0.300000 +v 0.780000 -0.600000 -0.300000 +v 0.780000 -0.300000 -0.300000 +v 0.780000 -0.400000 0.000000 +v 0.780000 -0.800000 0.000000 +v 0.780000 -0.900000 -0.300000 +v -0.780000 -0.600000 -0.300000 +v -0.780000 -0.900000 -0.300000 +v -0.780000 -0.800000 0.000000 +v -0.780000 -0.400000 0.000000 +v -0.780000 -0.300000 -0.300000 +vt 0.564792 0.689981 +vt 0.564792 0.953949 +vt 0.338554 0.953949 +vt 0.338554 0.689981 +vt 0.334296 0.692438 +vt 0.334296 0.954445 +vt 0.203294 0.986197 +vt 0.203294 0.656186 +vt 0.695616 0.984433 +vt 0.572654 0.959219 +vt 0.695616 0.657719 +vt 0.572795 0.683358 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.471796 0.690927 +vt 0.487691 0.573764 +vt 0.563274 0.690927 +vt 0.735343 0.574985 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.743883 0.446331 +vt 0.779872 0.385852 +vt 0.817351 0.446331 +vt 0.853830 0.385852 +vt 0.890820 0.446331 +vt 0.122341 0.504785 +vt 0.003867 0.504785 +vt 0.122341 0.378413 +vt 0.003867 0.378413 +s off +f 1/1 3/2 4/3 +f 2/4 1/1 4/3 +f 2/5 4/6 5/7 +f 5/7 6/8 2/5 +f 9/9 3/10 10/11 +f 3/10 1/12 10/11 +f 8/13 7/14 13/15 +f 13/15 14/16 8/13 +f 15/17 11/18 16/19 +f 12/20 16/19 11/18 +f 2/21 6/22 1/23 +f 10/24 1/23 6/22 +f 4/21 3/23 5/22 +f 9/24 5/22 3/23 +f 17/25 19/26 20/27 +f 18/28 17/25 20/27 +f 21/26 23/25 24/28 +f 22/27 21/26 24/28 +f 26/29 27/30 25/31 +f 27/30 28/32 25/31 +f 28/32 29/33 25/31 +f 31/34 32/35 30/36 +f 32/35 33/37 30/36 +f 33/37 34/38 30/36 +f 36/38 37/37 35/36 +f 37/37 38/35 35/36 +f 38/35 39/34 35/36 +f 41/33 42/32 40/31 +f 42/32 43/30 40/31 +f 43/30 44/29 40/31 +f 5/39 7/40 6/41 +f 7/40 8/42 6/41 +f 46/29 47/30 45/31 +f 47/30 48/32 45/31 +f 48/32 49/33 45/31 +f 51/33 52/32 50/31 +f 52/32 53/30 50/31 +f 53/30 54/29 50/31 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj new file mode 100644 index 0000000..bc18133 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj @@ -0,0 +1,106 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v -0.800000 2.900000 -0.700000 +v 0.800000 2.900000 -0.700000 +v -0.800000 1.200000 -0.700000 +v 0.800000 1.200000 -0.700000 +v -0.800000 2.900000 -0.300000 +v 0.800000 2.900000 -0.300000 +v -0.800000 1.200000 -0.300000 +v 0.800000 1.200000 -0.300000 +v 0.800000 1.200000 -0.300000 +v 0.800000 2.900000 -0.300000 +v 0.800000 1.200000 -0.700000 +v 0.800000 2.900000 -0.700000 +v -0.800000 1.200000 -0.700000 +v -0.800000 2.900000 -0.700000 +v -0.800000 1.200000 -0.300000 +v -0.800000 2.900000 -0.300000 +v 0.780000 2.400000 -0.300000 +v 0.780000 2.700000 -0.300000 +v 0.780000 2.600000 0.000000 +v 0.780000 2.200000 0.000000 +v 0.780000 2.100000 -0.300000 +v -0.780000 2.400000 -0.300000 +v -0.780000 2.100000 -0.300000 +v -0.780000 2.200000 0.000000 +v -0.780000 2.600000 0.000000 +v -0.780000 2.700000 -0.300000 +v 0.780000 1.700000 -0.300000 +v 0.780000 2.000000 -0.300000 +v 0.780000 1.900000 0.000000 +v 0.780000 1.500000 0.000000 +v 0.780000 1.400000 -0.300000 +v -0.780000 1.700000 -0.300000 +v -0.780000 1.400000 -0.300000 +v -0.780000 1.500000 0.000000 +v -0.780000 1.900000 0.000000 +v -0.780000 2.000000 -0.300000 +v 0.800000 -0.600000 -2.000000 +v 0.800000 2.900000 -2.000000 +v -0.800000 -0.600000 -2.000000 +v -0.800000 2.900000 -2.000000 +v 0.800000 -0.600000 -0.800000 +v 0.800000 2.900000 -0.800000 +v -0.800000 -0.600000 -0.800000 +v -0.800000 2.900000 -0.800000 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.880027 0.325855 +vt 0.591008 0.325855 +vt 0.880027 0.000323 +vt 0.589446 0.324644 +vt 0.001453 0.324644 +vt 0.589446 0.001209 +vt 0.584620 0.002788 +vt 0.584620 0.324497 +vt 0.001010 0.324497 +vt 0.001010 0.002788 +vt 0.591008 0.000323 +vt 0.001453 0.001209 +s off +f 2/1 1/2 5/3 +f 5/3 6/4 2/1 +f 7/5 3/6 8/7 +f 4/8 8/7 3/6 +f 9/9 11/10 12/11 +f 10/12 9/9 12/11 +f 13/10 15/9 16/12 +f 14/11 13/10 16/12 +f 18/13 19/14 17/15 +f 19/14 20/16 17/15 +f 20/16 21/17 17/15 +f 23/17 24/16 22/15 +f 24/16 25/14 22/15 +f 25/14 26/13 22/15 +f 39/18 37/19 43/20 +f 37/21 38/22 41/23 +f 28/13 29/14 27/15 +f 29/14 30/16 27/15 +f 30/16 31/17 27/15 +f 33/17 34/16 32/15 +f 34/16 35/14 32/15 +f 35/14 36/13 32/15 +f 37/24 39/25 40/26 +f 38/27 37/24 40/26 +f 37/19 41/28 43/20 +f 40/18 44/20 42/28 +f 39/21 43/23 44/29 +f 38/22 42/29 41/23 +f 38/19 40/18 42/28 +f 40/22 39/21 44/29 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj new file mode 100644 index 0000000..1d3e8bb --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj @@ -0,0 +1,127 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v 0.600000 -0.200000 -1.100000 +v 0.600000 0.200000 -1.100000 +v -0.600000 -0.200000 -1.100000 +v -0.600000 0.200000 -1.100000 +v -0.800000 0.300000 -0.700000 +v 0.800000 0.300000 -0.700000 +v -0.800000 2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -1.000000 -0.700000 +v 0.800000 -1.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v 0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v -0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v 0.800000 -2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.300000 +v -0.800000 2.000000 -0.300000 +v 0.780000 1.200000 -0.300000 +v 0.780000 1.500000 -0.300000 +v 0.780000 1.400000 0.000000 +v 0.780000 1.000000 0.000000 +v 0.780000 0.900000 -0.300000 +v 0.780000 -1.100000 -0.300000 +v 0.780000 -0.800000 -0.300000 +v 0.780000 -0.900000 0.000000 +v 0.780000 -1.300000 0.000000 +v 0.780000 -1.400000 -0.300000 +v -0.780000 -1.100000 -0.300000 +v -0.780000 -1.400000 -0.300000 +v -0.780000 -1.300000 0.000000 +v -0.780000 -0.900000 0.000000 +v -0.780000 -0.800000 -0.300000 +v -0.780000 1.200000 -0.300000 +v -0.780000 0.900000 -0.300000 +v -0.780000 1.000000 0.000000 +v -0.780000 1.400000 0.000000 +v -0.780000 1.500000 -0.300000 +vt 0.564792 0.689981 +vt 0.564792 0.953949 +vt 0.338554 0.953949 +vt 0.338554 0.689981 +vt 0.334296 0.692438 +vt 0.334296 0.954445 +vt 0.203294 0.986197 +vt 0.203294 0.656186 +vt 0.695616 0.984433 +vt 0.572654 0.959219 +vt 0.695616 0.657719 +vt 0.572795 0.683358 +vt 0.941945 0.999442 +vt 0.694306 0.999442 +vt 0.941945 0.647349 +vt 0.694306 0.647349 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.471796 0.690927 +vt 0.487691 0.573764 +vt 0.563274 0.690927 +vt 0.735343 0.574985 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.743883 0.446331 +vt 0.779872 0.385852 +vt 0.817351 0.446331 +vt 0.853830 0.385852 +vt 0.890820 0.446331 +vt 0.122341 0.504785 +vt 0.003867 0.504785 +vt 0.122341 0.378413 +vt 0.003867 0.378413 +s off +f 1/1 3/2 4/3 +f 2/4 1/1 4/3 +f 2/5 4/6 5/7 +f 5/7 6/8 2/5 +f 9/9 3/10 10/11 +f 3/10 1/12 10/11 +f 11/13 9/14 12/15 +f 10/16 12/15 9/14 +f 8/17 7/18 13/19 +f 13/19 14/20 8/17 +f 15/21 11/22 16/23 +f 12/24 16/23 11/22 +f 2/25 6/26 1/27 +f 10/28 1/27 6/26 +f 4/25 3/27 5/26 +f 9/28 5/26 3/27 +f 17/29 19/30 20/31 +f 18/32 17/29 20/31 +f 21/30 23/29 24/32 +f 22/31 21/30 24/32 +f 26/33 27/34 25/35 +f 27/34 28/36 25/35 +f 28/36 29/37 25/35 +f 31/38 32/39 30/40 +f 32/39 33/41 30/40 +f 33/41 34/42 30/40 +f 36/42 37/41 35/40 +f 37/41 38/39 35/40 +f 38/39 39/38 35/40 +f 41/37 42/36 40/35 +f 42/36 43/34 40/35 +f 43/34 44/33 40/35 +f 5/43 7/44 6/45 +f 7/44 8/46 6/45 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj new file mode 100644 index 0000000..d6774d9 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj @@ -0,0 +1,121 @@ +# Blender v2.69 (sub 0) OBJ File: 'Car002.blend' +# www.blender.org +v 0.600000 -0.200000 -1.100000 +v 0.600000 1.600000 -1.100000 +v -0.600000 -0.200000 -1.100000 +v -0.600000 1.600000 -1.100000 +v -0.800000 2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -1.000000 -0.700000 +v 0.800000 -1.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v 0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v -0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 -2.000000 -0.300000 +v 0.800000 2.000000 -0.300000 +v 0.800000 -2.000000 -0.700000 +v 0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.700000 +v -0.800000 2.000000 -0.700000 +v -0.800000 -2.000000 -0.300000 +v -0.800000 2.000000 -0.300000 +v 0.780000 1.200000 -0.300000 +v 0.780000 1.500000 -0.300000 +v 0.780000 1.400000 0.000000 +v 0.780000 1.000000 0.000000 +v 0.780000 0.900000 -0.300000 +v 0.780000 -1.100000 -0.300000 +v 0.780000 -0.800000 -0.300000 +v 0.780000 -0.900000 0.000000 +v 0.780000 -1.300000 0.000000 +v 0.780000 -1.400000 -0.300000 +v -0.780000 -1.100000 -0.300000 +v -0.780000 -1.400000 -0.300000 +v -0.780000 -1.300000 0.000000 +v -0.780000 -0.900000 0.000000 +v -0.780000 -0.800000 -0.300000 +v -0.780000 1.200000 -0.300000 +v -0.780000 0.900000 -0.300000 +v -0.780000 1.000000 0.000000 +v -0.780000 1.400000 0.000000 +v -0.780000 1.500000 -0.300000 +vt 0.564792 0.689981 +vt 0.564792 0.953949 +vt 0.338554 0.953949 +vt 0.338554 0.689981 +vt 0.334296 0.692438 +vt 0.334296 0.954445 +vt 0.203294 0.986197 +vt 0.203294 0.656186 +vt 0.695616 0.984433 +vt 0.572654 0.959219 +vt 0.695616 0.657719 +vt 0.572795 0.683358 +vt 0.941945 0.999442 +vt 0.694306 0.999442 +vt 0.941945 0.647349 +vt 0.694306 0.647349 +vt 0.070499 0.643570 +vt 0.070499 0.998147 +vt 0.003817 0.998147 +vt 0.003817 0.643570 +vt 0.997239 0.998410 +vt 0.940308 0.998410 +vt 0.997239 0.647262 +vt 0.940308 0.647262 +vt 0.331416 0.690927 +vt 0.238667 0.577426 +vt 0.563274 0.690927 +vt 0.735343 0.574985 +vt 0.970266 0.522520 +vt 0.962942 0.572543 +vt 0.072079 0.572543 +vt 0.079403 0.522520 +vt 0.744362 0.446044 +vt 0.780351 0.385565 +vt 0.817830 0.446044 +vt 0.854309 0.385565 +vt 0.891299 0.446044 +vt 0.743883 0.446331 +vt 0.779872 0.385852 +vt 0.817351 0.446331 +vt 0.853830 0.385852 +vt 0.890820 0.446331 +s off +f 1/1 3/2 4/3 +f 2/4 1/1 4/3 +f 2/5 4/6 5/7 +f 5/7 6/8 2/5 +f 9/9 3/10 10/11 +f 3/10 1/12 10/11 +f 11/13 9/14 12/15 +f 10/16 12/15 9/14 +f 8/17 7/18 13/19 +f 13/19 14/20 8/17 +f 15/21 11/22 16/23 +f 12/24 16/23 11/22 +f 2/25 6/26 1/27 +f 10/28 1/27 6/26 +f 4/25 3/27 5/26 +f 9/28 5/26 3/27 +f 17/29 19/30 20/31 +f 18/32 17/29 20/31 +f 21/30 23/29 24/32 +f 22/31 21/30 24/32 +f 26/33 27/34 25/35 +f 27/34 28/36 25/35 +f 28/36 29/37 25/35 +f 31/38 32/39 30/40 +f 32/39 33/41 30/40 +f 33/41 34/42 30/40 +f 36/42 37/41 35/40 +f 37/41 38/39 35/40 +f 38/39 39/38 35/40 +f 41/37 42/36 40/35 +f 42/36 43/34 40/35 +f 43/34 44/33 40/35 diff --git a/Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png b/Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png new file mode 100644 index 0000000..049eb5c Binary files /dev/null and b/Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png differ diff --git a/Videos/CarCrimeCity/Part2/cAutomata.cpp b/Videos/CarCrimeCity/Part2/cAutomata.cpp new file mode 100644 index 0000000..b289bbc --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cAutomata.cpp @@ -0,0 +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); + } +} + + + + + diff --git a/Videos/CarCrimeCity/Part2/cAutomata.h b/Videos/CarCrimeCity/Part2/cAutomata.h new file mode 100644 index 0000000..f612481 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cAutomata.h @@ -0,0 +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 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 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; + +}; diff --git a/Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp b/Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp new file mode 100644 index 0000000..6067839 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp @@ -0,0 +1,709 @@ +#include "cCarCrimeCity.h" + +cCarCrimeCity::cCarCrimeCity() +{ + sAppName = "Car Crime City"; +} + +cCarCrimeCity::~cCarCrimeCity() +{ +} + +bool cCarCrimeCity::OnUserCreate() +{ + // Initialise PGEX 3D + olc::GFX3D::ConfigureDisplay(); + + // Load fixed system assets, i.e. those need to simply do anything + if (!LoadAssets()) return false; + + // Create Default city + pCity = new cCityMap(cGameSettings::nDefaultMapWidth, cGameSettings::nDefaultMapHeight, mapAssetTextures, mapAssetMeshes, mapAssetTransform); + + // If a city map file has been specified, then load it + if (!cGameSettings::sDefaultCityFile.empty()) + { + if (!pCity->LoadCity(cGameSettings::sDefaultCityFile)) + { + std::cout << "Failed to load '" << cGameSettings::sDefaultCityFile << "'" << std::endl; + return false; + } + } + + return true; +} + +bool cCarCrimeCity::LoadAssets() +{ + // Game Settings should have loaded all the relevant file information + // to start loading asset information. Game assets will be stored in + // a map structure. Maps can have slightly longer access times, so each + // in game object will have facility to extract required resources once + // when it is created, meaning no map search during normal use + + // System Meshes + // A simple flat unit quad + olc::GFX3D::mesh* meshQuad = new olc::GFX3D::mesh(); + 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::WHITE, olc::WHITE, olc::WHITE }, + { 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::WHITE, olc::WHITE, olc::WHITE }, + }; + mapAssetMeshes["UnitQuad"] = meshQuad; + + //// The four outer walls of a cell + olc::GFX3D::mesh* meshWallsOut = new olc::GFX3D::mesh(); + meshWallsOut->tris = + { + // EAST + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + + // WEST + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + { 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + + // TOP + { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + { 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + + // BOTTOM + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + { 1.0f, 0.0f, 0.2f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::WHITE, olc::WHITE, olc::WHITE }, + }; + mapAssetMeshes["WallsOut"] = meshWallsOut; + + + // System Textures + for (auto &asset : cGameSettings::vecAssetTextures) + { + olc::Sprite *sprAsset = new olc::Sprite(); + if (sprAsset->LoadFromFile(asset.sFile)) + { + mapAssetTextures[asset.sName] = sprAsset; + } + else + { + std::cout << "Failed to load " << asset.sName << std::endl; + return false; + } + } + + // Break up roads sprite into individual sprites. Why? Its easier to maintain + // the roads sprite as a single image, but easier to use if they are all individual. + // Breaking it up manually in the image editing software is time consuming so just + // do it here + int nRoadTexSize = 256; // In pixels in base texture + int nRoadTexOffset = 64; // There exists a 64 pixel offset from top left of source image + for (int r = 0; r < 12; r++) + { + olc::Sprite* road = new olc::Sprite(nRoadTexSize, nRoadTexSize); + SetDrawTarget(road); + DrawPartialSprite(0, 0, mapAssetTextures["AllRoads"], ((r % 3) * nRoadTexSize) + nRoadTexOffset, ((r / 3) * nRoadTexSize) + nRoadTexOffset, nRoadTexSize, nRoadTexSize); + switch (r) + { + case 0: mapAssetTextures["Road_V"] = road; break; + case 1: mapAssetTextures["Road_H"] = road; break; + case 2: mapAssetTextures["Pavement"] = road; break; + case 3: mapAssetTextures["Road_C1"] = road; break; + case 4: mapAssetTextures["Road_T1"] = road; break; + case 5: mapAssetTextures["Road_C2"] = road; break; + case 6: mapAssetTextures["Road_T2"] = road; break; + case 7: mapAssetTextures["Road_X"] = road; break; + case 8: mapAssetTextures["Road_T3"] = road; break; + case 9: mapAssetTextures["Road_C3"] = road; break; + case 10: mapAssetTextures["Road_T4"] = road; break; + case 11: mapAssetTextures["Road_C4"] = road; break; + } + } + SetDrawTarget(nullptr); + + + // Load Buildings + for (auto &asset : cGameSettings::vecAssetBuildings) + { + mapAssetMeshes[asset.sDescription] = new olc::GFX3D::mesh(); + mapAssetMeshes[asset.sDescription]->LoadOBJFile(asset.sModelOBJ); + mapAssetTextures[asset.sDescription] = new olc::Sprite(asset.sModelPNG); + + olc::GFX3D::mat4x4 matScale = olc::GFX3D::Math::Mat_MakeScale(asset.fScale[0], asset.fScale[1], asset.fScale[2]); + olc::GFX3D::mat4x4 matTranslate = olc::GFX3D::Math::Mat_MakeTranslation(asset.fTranslate[0], asset.fTranslate[1], asset.fTranslate[2]); + olc::GFX3D::mat4x4 matRotateX = olc::GFX3D::Math::Mat_MakeRotationX(asset.fRotate[0]); + olc::GFX3D::mat4x4 matRotateY = olc::GFX3D::Math::Mat_MakeRotationY(asset.fRotate[1]); + olc::GFX3D::mat4x4 matRotateZ = olc::GFX3D::Math::Mat_MakeRotationZ(asset.fRotate[2]); + olc::GFX3D::mat4x4 matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTranslate, matScale); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateX); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateY); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateZ); + mapAssetTransform[asset.sDescription] = matTransform; + } + + // Load Vehicles + for (auto &asset : cGameSettings::vecAssetVehicles) + { + mapAssetMeshes[asset.sDescription] = new olc::GFX3D::mesh(); + mapAssetMeshes[asset.sDescription]->LoadOBJFile(asset.sModelOBJ); + mapAssetTextures[asset.sDescription] = new olc::Sprite(asset.sModelPNG); + + olc::GFX3D::mat4x4 matScale = olc::GFX3D::Math::Mat_MakeScale(asset.fScale[0], asset.fScale[1], asset.fScale[2]); + olc::GFX3D::mat4x4 matTranslate = olc::GFX3D::Math::Mat_MakeTranslation(asset.fTranslate[0], asset.fTranslate[1], asset.fTranslate[2]); + olc::GFX3D::mat4x4 matRotateX = olc::GFX3D::Math::Mat_MakeRotationX(asset.fRotate[0]); + olc::GFX3D::mat4x4 matRotateY = olc::GFX3D::Math::Mat_MakeRotationY(asset.fRotate[1]); + olc::GFX3D::mat4x4 matRotateZ = olc::GFX3D::Math::Mat_MakeRotationZ(asset.fRotate[2]); + olc::GFX3D::mat4x4 matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTranslate, matScale); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateX); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateY); + matTransform = olc::GFX3D::Math::Mat_MultiplyMatrix(matTransform, matRotateZ); + mapAssetTransform[asset.sDescription] = matTransform; + } + + return true; +} + +void cCarCrimeCity::SpawnPedestrian(int x, int y) +{ + cCell* cell = pCity->Cell(x, y); + + cAuto_Track *t = ((cCell_Road*)cell)->pSafePedestrianTrack; + if (t == nullptr) return; + + cAuto_Body *a = new cAuto_Body(); + a->fAutoLength = 0.05f; + a->pCurrentTrack = t; + a->pCurrentTrack->listAutos.push_back(a); + a->pTrackOriginNode = t->node[0]; + a->UpdateAuto(0.0f); + listAutomata.push_back(a); +} + +void cCarCrimeCity::SpawnVehicle(int x, int y) +{ + cCell* cell = pCity->Cell(x, y); + + cAuto_Track *t = ((cCell_Road*)cell)->pSafeCarTrack; + if (t == nullptr) return; + + cAuto_Body *a = new cAuto_Body(); + a->fAutoLength = 0.2f; + a->pCurrentTrack = t; + a->pCurrentTrack->listAutos.push_back(a); + a->pTrackOriginNode = t->node[0]; + a->UpdateAuto(0.0f); + listAutomata.push_back(a); +} + +void cCarCrimeCity::DoEditMode(float fElapsedTime) +{ + // Get cell under mouse cursor + cCell* mcell = pCity->Cell(nMouseX, nMouseY); + bool bTempCellAdded = false; + + // Left click and drag adds cells + if (mcell != nullptr && GetMouse(0).bHeld) + setSelectedCells.emplace(nMouseY * pCity->GetWidth() + nMouseX); + + // Right click clears selection + if (GetMouse(1).bReleased) + setSelectedCells.clear(); + + if (setSelectedCells.empty()) + { + // If nothing can be edited validly then just exit + if (mcell == nullptr) + return; + + // else set is empty, so temporarily add current cell to it + setSelectedCells.emplace(nMouseY * pCity->GetWidth() + nMouseX); + bTempCellAdded = true; + } + + // If the map changes, we will need to update + // the automata, and adjacency + bool bMapChanged = false; + + // Press "G" to apply grass + if (GetKey(olc::Key::G).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + cCell* cell = pCity->Replace(x, y, new cCell_Plane(pCity, x, y, PLANE_GRASS)); + cell->LinkAssets(mapAssetTextures, mapAssetMeshes, mapAssetTransform); + } + + bMapChanged = true; + } + + // Press "P" to apply Pavement + if (GetKey(olc::Key::P).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + cCell* cell = pCity->Replace(x, y, new cCell_Plane(pCity, x, y, PLANE_ASPHALT)); + cell->LinkAssets(mapAssetTextures, mapAssetMeshes, mapAssetTransform); + } + + bMapChanged = true; + } + + // Press "W" to apply Water + if (GetKey(olc::Key::W).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + cCell* cell = pCity->Replace(x, y, new cCell_Water(pCity, x, y)); + cell->LinkAssets(mapAssetTextures, mapAssetMeshes, mapAssetTransform); + } + + bMapChanged = true; + } + + // Press "R" to apply Roads + if (GetKey(olc::Key::Q).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + cCell* cell = pCity->Replace(x, y, new cCell_Building("Apartments_1", pCity, x, y)); + cell->LinkAssets(mapAssetTextures, mapAssetMeshes, mapAssetTransform); + } + + bMapChanged = true; + } + + + // Press "R" to apply Roads + if (GetKey(olc::Key::R).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + cCell* cell = pCity->Replace(x, y, new cCell_Road(pCity, x, y)); + cell->LinkAssets(mapAssetTextures, mapAssetMeshes, mapAssetTransform); + } + + bMapChanged = true; + } + + + + if (GetKey(olc::Key::C).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + SpawnVehicle(x, y); + } + } + + + if (GetKey(olc::Key::V).bPressed) + { + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + SpawnPedestrian(x, y); + } + } + + if (bMapChanged) + { + // The navigation nodes may have tracks attached to them, so get rid of them + // all. Below we will reconstruct all tracks because city has changed + pCity->RemoveAllTracks(); + + for (auto &a : listAutomata) delete a; + listAutomata.clear(); + + for (int x = 0; x < pCity->GetWidth(); x++) + { + for (int y = 0; y < pCity->GetHeight(); y++) + { + cCell *c = pCity->Cell(x, y); + + // Update adjacency information, i.e. those cells whose + // state changes based on neighbouring cells + c->CalculateAdjacency(); + } + } + } + + + // To facilitate "edit under cursor" we added a temporary cell + // which needs to be removed now + if (bTempCellAdded) + setSelectedCells.clear(); +} + +olc::vf2d cCarCrimeCity::GetMouseOnGround(const olc::vf2d &vMouseScreen) +{ + 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.1f, 1000.0f); + olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp); + olc::GFX3D::vec3d vecMouseDir = { + 2.0f * ((vMouseScreen.x / (float)ScreenWidth()) - 0.5f) / matProj.m[0][0], + 2.0f * ((vMouseScreen.y / (float)ScreenHeight()) - 0.5f) / matProj.m[1][1], + 1.0f, + 0.0f }; + + olc::GFX3D::vec3d vecMouseOrigin = { 0.0f, 0.0f, 0.0f }; + vecMouseOrigin = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseOrigin); + vecMouseDir = olc::GFX3D::Math::Mat_MultiplyVector(matView, vecMouseDir); + vecMouseDir = olc::GFX3D::Math::Vec_Mul(vecMouseDir, 1000.0f); + vecMouseDir = olc::GFX3D::Math::Vec_Add(vecMouseOrigin, vecMouseDir); + + // Perform line/plane intersection to determine mouse position in world space + olc::GFX3D::vec3d plane_p = { 0.0f, 0.0f, 0.0f }; + olc::GFX3D::vec3d plane_n = { 0.0f, 0.0f, 1.0f }; + float t = 0.0f; + olc::GFX3D::vec3d mouse3d = olc::GFX3D::Math::Vec_IntersectPlane(plane_p, plane_n, vecMouseOrigin, vecMouseDir, t); + return { mouse3d.x, mouse3d.y }; +} + +bool cCarCrimeCity::OnUserUpdate(float fElapsedTime) +{ + fGlobalTime += fElapsedTime; + + if (GetKey(olc::Key::TAB).bReleased) bEditMode = !bEditMode; + + if (bEditMode) // Use mouse to pan and zoom, and place objects + { + vEye = vCamera; + olc::vf2d vMouseScreen = { (float)GetMouseX(), (float)GetMouseY() }; + olc::vf2d vMouseOnGroundBeforeZoom = GetMouseOnGround(vMouseScreen); + + vOffset = { 0,0 }; + + if (IsFocused()) + { + if (GetMouse(2).bPressed) { vStartPan = vMouseOnGroundBeforeZoom; } + if (GetMouse(2).bHeld) { vOffset = (vStartPan - vMouseOnGroundBeforeZoom); }; + + if (GetMouseWheel() > 0) + { + vCamera.z *= 0.5f; + } + + if (GetMouseWheel() < 0) + { + vCamera.z *= 1.5f; + } + } + + vEye = vCamera; + olc::vf2d vMouseOnGroundAfterZoom = GetMouseOnGround(vMouseScreen); + vOffset += (vMouseOnGroundBeforeZoom - vMouseOnGroundAfterZoom); + vCamera.x += vOffset.x; + vCamera.y += vOffset.y; + vEye = vCamera; + + // Get Integer versions of mouse coords in world space + nMouseX = (int)vMouseOnGroundAfterZoom.x; + nMouseY = (int)vMouseOnGroundAfterZoom.y; + + DoEditMode(fElapsedTime); + } + else + { + // Not in edit mode, so camera follows player + if (GetKey(olc::Key::LEFT).bHeld) fAngle += -2.5f * fElapsedTime; + if (GetKey(olc::Key::RIGHT).bHeld) fAngle += 2.5f * fElapsedTime; + if (GetKey(olc::Key::UP).bHeld) + { + carvel = { cos(fAngle), sin(fAngle) }; + carpos += carvel * 2.0f * fElapsedTime; + } + + vCamera.x = carpos.x; + vCamera.y = carpos.y; + vEye = vCamera; + } + + /*fAngle = 0.0f; + if (GetKey(olc::Key::LEFT).bHeld) fAngle = -0.8f; + if (GetKey(olc::Key::RIGHT).bHeld) fAngle = 0.8f;*/ + + + //car.UpdateDrive(fElapsedTime, 1.0f, GetKey(olc::Key::UP).bHeld, GetKey(olc::Key::SPACE).bHeld, GetKey(olc::Key::DOWN).bHeld, fAngle); + + + //if (car.bSkidding && fmod(fGlobalTime, 0.05f) < 0.01f) + //{ + // listDecalSmoke.push_front({ 0.1f, {car.vPosRear.x, car.vPosRear.y, -0.03f} }); + //} + + + //// Update Decals + //for (auto &d : listDecalSmoke) + //{ + // d.fLifetime += fElapsedTime; + //} + + //listDecalSmoke.remove_if([](const sSmokeDecal &d) {return d.fLifetime > 2.0f; }); + + //if (!bEditMode) + //{ + // vCamera.x = car.GetOrigin().x; + // vCamera.y = car.GetOrigin().y; + //} + + + //float fTargetHeight = -1.0f; + //int nCarX = vCamera.x; + //int nCarY = vCamera.y; + + std::vector vecNeighbours; + + //// Check surrounding cells height + //for (int x = nCarX - 1; x < nCarX + 2; x++) + // for (int y = nCarY - 1; y < nCarY + 2; y++) + // { + // if (pCity->Cell(x,y) && pCity->Cell(x, y)->bBuilding) + // { + // cGameObjectQuad ob(1.0f, 1.0f); + // ob.pos = { (float)x + 0.5f, (float)y + 0.5f, 0.0f, 1.0f }; + // ob.TransformModelToWorld(); + // vecNeighbours.push_back(ob); + // fTargetHeight = -2.0f; + // } + // } + + //goCar->pos.x = car.GetOrigin().x; + //goCar->pos.y = car.GetOrigin().y; + //goCar->fAngle = car.GetRotation(); + //goCar->TransformModelToWorld(); + + //for (auto &ob : vecNeighbours) + //{ + // if (goCar->StaticCollisionWith(ob, true)) + // { + // goCar->TransformModelToWorld(); + // car.vPosRear.x += goCar->pos.x - car.GetOrigin().x; + // car.vPosRear.y += goCar->pos.y - car.GetOrigin().y; + // car.vPosFront.x += goCar->pos.x - car.GetOrigin().x; + // car.vPosFront.y += goCar->pos.y - car.GetOrigin().y; + // car.fSpeed = 0.0f; + // } + //} + + //if(!bEditMode) + // vCamera.z += (fTargetHeight - vCamera.z) * 10.0f * fElapsedTime; + + + //car.UpdateTow(fElapsedTime, { mouse3d.x, mouse3d.y }); + + + + //for (int v = 1; vGetWidth(), (int)viewWorldBottomRight.x + 1); + int nStartY = std::max(0, (int)viewWorldTopLeft.y - 1); + int nEndY = std::min(pCity->GetHeight(), (int)viewWorldBottomRight.y + 1); + + // Only update automata for cells near player + int nAutomStartX = std::max(0, (int)viewWorldTopLeft.x - 3); + int nAutomEndX = std::min(pCity->GetWidth(), (int)viewWorldBottomRight.x + 3); + int nAutomStartY = std::max(0, (int)viewWorldTopLeft.y - 3); + int nAutomEndY = std::min(pCity->GetHeight(), (int)viewWorldBottomRight.y + 3); + + int nLocalStartX = std::max(0, (int)vCamera.x - 3); + int nLocalEndX = std::min(pCity->GetWidth(), (int)vCamera.x + 3); + int nLocalStartY = std::max(0, (int)vCamera.y - 3); + int nLocalEndY = std::min(pCity->GetHeight(), (int)vCamera.y + 3); + + + // Update Cells + for (int x = nStartX; x < nEndX; x++) + { + for (int y = nStartY; y < nEndY; y++) + { + pCity->Cell(x, y)->Update(fElapsedTime); + } + } + + // Update Automata + for (auto &a : listAutomata) + { + a->UpdateAuto(fElapsedTime); + + // If automata is too far from camera, remove it + if ((a->vAutoPos - olc::vf2d(vCamera.x, vCamera.y)).mag() > 5.0f) + { + // Despawn automata + + // 1) Disconnect it from track + a->pCurrentTrack->listAutos.remove(a); + + // 2) Erase it from memory + delete a; a = nullptr; + } + } + + // Remove dead automata, their pointer has been set to nullptr in the list + listAutomata.remove(nullptr); + + // Maintain a certain level of automata in vicinty of player + if (listAutomata.size() < 20) + { + bool bSpawnOK = false; + int nSpawnAttempt = 20; + while (!bSpawnOK && nSpawnAttempt > 0) + { + // Find random cell on edge of vicinty, which is out of view of the player + float fRandomAngle = ((float)rand() / (float)RAND_MAX) * 2.0f * 3.14159f; + int nRandomCellX = vCamera.x + cos(fRandomAngle) * 3.0f; + int nRandomCellY = vCamera.y + sin(fRandomAngle) * 3.0f; + + nSpawnAttempt--; + + if (pCity->Cell(nRandomCellX, nRandomCellY) && pCity->Cell(nRandomCellX, nRandomCellY)->nCellType == CELL_ROAD) + { + bSpawnOK = true; + + // Add random automata + if (rand() % 100 < 50) + { + // Spawn Pedestrian + SpawnPedestrian(nRandomCellX, nRandomCellY); + } + else + { + // Spawn Vehicle + SpawnVehicle(nRandomCellX, nRandomCellY); + // TODO: Get % chance of vehicle spawn from lua script + } + } + } + } + + + + + // Render Scene + Clear(olc::BLUE); + olc::GFX3D::ClearDepth(); + + // Create rendering pipeline + olc::GFX3D::PipeLine pipe; + pipe.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f, 0.0f, 0.0f, (float)ScreenWidth(), (float)ScreenHeight()); + olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir); + pipe.SetCamera(vEye, vLookTarget, vUp); + + + // Add global illumination vector (sunlight) + olc::GFX3D::vec3d lightdir = { 1.0f, 1.0f, -1.0f }; + pipe.SetLightSource(0, olc::GFX3D::LIGHT_AMBIENT, olc::Pixel(100,100,100), { 0,0,0 }, lightdir); + pipe.SetLightSource(1, olc::GFX3D::LIGHT_DIRECTIONAL, olc::WHITE, { 0,0,0 }, lightdir); + + + // RENDER CELL CONTENTS + + // Render Base Objects (those without alpha components) + for (int x = nStartX; x < nEndX; x++) + { + //omp_set_dynamic(0); + //omp_set_num_threads(4); + //#pragma omp parallel for + for (int y = nStartY; y < nEndY; y++) + { + pCity->Cell(x, y)->DrawBase(this, pipe); + } + //#pragma omp barrier + } + + // Render Upper Objects (those with alpha components) + for (int x = nStartX; x < nEndX; x++) + { + for (int y = nStartY; y < nEndY; y++) + { + pCity->Cell(x, y)->DrawAlpha(this, pipe); + } + } + + if (bEditMode) + { + // Render additional per cell debug information + for (int x = nStartX; x < nEndX; x++) + { + for (int y = nStartY; y < nEndY; y++) + { + pCity->Cell(x, y)->DrawDebug(this, pipe); + } + } + } + + if (bEditMode) + { + // Draw Selections + for (auto &c : setSelectedCells) + { + int x = c % pCity->GetWidth(); + int y = c / pCity->GetWidth(); + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.01f); + pipe.SetTransform(matWorld); + pipe.Render(mapAssetMeshes["UnitQuad"]->tris, olc::GFX3D::RENDER_WIRE); + } + } + + // RENDER AUTOMATA + + std::string test[] = { "Sedan", "SUV", "TruckCab", "TruckTrailer", "UTE", "Wagon" }; + int i = 0; + for (auto &a : listAutomata) + { + olc::GFX3D::vec3d v = { a->vAutoPos.x, a->vAutoPos.y, 0.0f }; + + /*olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MakeTranslation(a->vAutoPos.x, a->vAutoPos.y, 0.01f); + matWorld = olc::GFX3D::Math::Mat_MultiplyMatrix(mapAssetTransform[test[i]], matWorld); + pipe.SetTransform(matWorld); + pipe.SetTexture(mapAssetTextures[test[i]]); + pipe.Render(mapAssetMeshes[test[i]]->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_LIGHTS); + i++; + i = i % 6;*/ + + pipe.RenderCircleXZ(v, a->fAutoLength < 0.1f ? 0.05f : 0.07f, a->fAutoLength < 0.1f ? olc::MAGENTA : olc::YELLOW); + } + + + // Draw Player Vehicle + { + olc::GFX3D::mat4x4 matRotateZ = olc::GFX3D::Math::Mat_MakeRotationZ(fAngle); + olc::GFX3D::mat4x4 matTranslate = olc::GFX3D::Math::Mat_MakeTranslation(carpos.x, carpos.y, 0.01f); + olc::GFX3D::mat4x4 matWorld = olc::GFX3D::Math::Mat_MultiplyMatrix(mapAssetTransform["Sedan"], matRotateZ); + matWorld = olc::GFX3D::Math::Mat_MultiplyMatrix(matWorld, matTranslate); + pipe.SetTransform(matWorld); + pipe.SetTexture(mapAssetTextures["Sedan"]); + pipe.Render(mapAssetMeshes[test[i]]->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED | olc::GFX3D::RENDER_LIGHTS); + } + + DrawString(10, 10, "Automata: " + std::to_string(listAutomata.size()), olc::WHITE); + + + if (GetKey(olc::Key::ESCAPE).bPressed) + return false; + + return true; +} + +bool cCarCrimeCity::OnUserDestroy() +{ + return true; +} diff --git a/Videos/CarCrimeCity/Part2/cCarCrimeCity.h b/Videos/CarCrimeCity/Part2/cCarCrimeCity.h new file mode 100644 index 0000000..a2a481c --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCarCrimeCity.h @@ -0,0 +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 +#include + +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 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 meshTris; + std::vector vecPointsModel; + std::vector vecPointsWorld; + olc::GFX3D::vec3d pos; + + float fWidth; + float fHeight; + float fOriginX; + float fOriginY; + float fAngle; + }; + + bool LoadAssets(); + + std::map mapAssetTextures; + std::map mapAssetMeshes; + std::map 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 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 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 setSelectedCells; + + //std::list listDecalSmoke; + + //int nTrafficState = 0; + + void DoEditMode(float fElapsedTime); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCell.cpp b/Videos/CarCrimeCity/Part2/cCell.cpp new file mode 100644 index 0000000..9f37d8d --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell.cpp @@ -0,0 +1,121 @@ +#include "cCell.h" + +#include "cCityMap.h" +#include "olcPixelGameEngine.h" +#include + +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 &mapTextures, std::map &mapMesh, std::map &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() +{ + +} diff --git a/Videos/CarCrimeCity/Part2/cCell.h b/Videos/CarCrimeCity/Part2/cCell.h new file mode 100644 index 0000000..4f27a29 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#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 listTracks; + +public: + virtual void CalculateAdjacency(); + virtual bool LinkAssets(std::map &mapTextures, std::map &mapMesh, std::map &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); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCell_Building.cpp b/Videos/CarCrimeCity/Part2/cCell_Building.cpp new file mode 100644 index 0000000..6635163 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Building.cpp @@ -0,0 +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& mapTextures, std::map& mapMesh, std::map &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; +} diff --git a/Videos/CarCrimeCity/Part2/cCell_Building.h b/Videos/CarCrimeCity/Part2/cCell_Building.h new file mode 100644 index 0000000..874ab42 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Building.h @@ -0,0 +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 &mapTextures, std::map &mapMesh, std::map &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); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCell_Plane.cpp b/Videos/CarCrimeCity/Part2/cCell_Plane.cpp new file mode 100644 index 0000000..a0a3da7 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Plane.cpp @@ -0,0 +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 &mapTextures, std::map &mapMesh, std::map &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; +} diff --git a/Videos/CarCrimeCity/Part2/cCell_Plane.h b/Videos/CarCrimeCity/Part2/cCell_Plane.h new file mode 100644 index 0000000..252296c --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Plane.h @@ -0,0 +1,34 @@ +#pragma once +#include "cCell.h" +#include "olcPixelGameEngine.h" +#include "olcPGEX_Graphics3D.h" +#include + + +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 &mapTextures, std::map &mapMesh, std::map &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); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCell_Road.cpp b/Videos/CarCrimeCity/Part2/cCell_Road.cpp new file mode 100644 index 0000000..5887e07 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Road.cpp @@ -0,0 +1,812 @@ +#include "cCell_Road.h" +#include "cCityMap.h" + + +cCell_Road::cCell_Road(cCityMap* map, int x, int y) : cCell(map, x, y) +{ + bSolid = false; + nCellType = CELL_ROAD; +} + +cCell_Road::~cCell_Road() +{ +} + +void cCell_Road::CalculateAdjacency() +{ + + // Calculate suitable road junction type + auto r = [&](int i, int j) + { + return (pMap->Cell(nWorldX + i, nWorldY + j) != nullptr && pMap->Cell(nWorldX + i, nWorldY + j)->nCellType == CELL_ROAD); + }; + + if (r(0, -1) && r(0, +1) && !r(-1, 0) && !r(+1, 0)) nRoadType = ROAD_V; + if (!r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) nRoadType =ROAD_H; + if (!r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) nRoadType = ROAD_C1; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) nRoadType =ROAD_T1; + if (!r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) nRoadType = ROAD_C2; + if (r(0, -1) && r(0, +1) && !r(-1, 0) && r(+1, 0)) nRoadType = ROAD_T2; + if (r(0, -1) && r(0, +1) && r(-1, 0) && r(+1, 0)) nRoadType = ROAD_X; + if (r(0, -1) && r(0, +1) && r(-1, 0) && !r(+1, 0)) nRoadType = ROAD_T3; + if (r(0, -1) && !r(0, +1) && !r(-1, 0) && r(+1, 0)) nRoadType = ROAD_C3; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && r(+1, 0)) nRoadType = ROAD_T4; + if (r(0, -1) && !r(0, +1) && r(-1, 0) && !r(+1, 0)) nRoadType = ROAD_C4; + + // Add navigation tracks based on type + + auto AddTrack = [&](int n1, int n2) -> cAuto_Track* + { + if (pNaviNodes[n1] == nullptr || pNaviNodes[n2] == nullptr) + { + // Can't add track + return nullptr; + } + else + { + // Nodes exist so add track + cAuto_Track t; + t.node[0] = pNaviNodes[n1]; + t.node[1] = pNaviNodes[n2]; + t.cell = this; + t.fTrackLength = (pNaviNodes[n1]->pos - pNaviNodes[n2]->pos).mag(); + listTracks.push_back(t); + + // Add pointers to track to start and end nodes + pNaviNodes[n1]->listTracks.push_back(&listTracks.back()); + pNaviNodes[n2]->listTracks.push_back(&listTracks.back()); + + return &listTracks.back(); + } + }; + + // Ensure list of tracks for this cell is clear + listTracks.clear(); + + // Add tracks depending on junction type + pSafePedestrianTrack = nullptr; + pSafeCarTrack = nullptr; + pSafeChaseTrack = nullptr; + + // Add Pedestrian Tracks + switch (nRoadType) + { + case ROAD_H: pSafePedestrianTrack = AddTrack(7, 13); AddTrack(41, 35); break; + case ROAD_V: pSafePedestrianTrack = AddTrack(1, 43); AddTrack(5, 47); break; + + case ROAD_C1: pSafePedestrianTrack = AddTrack(43, 8); AddTrack(8, 13); AddTrack(47, 40); AddTrack(40, 41); break; + case ROAD_C2: AddTrack(7, 12); AddTrack(12, 47); pSafePedestrianTrack = AddTrack(35, 36); AddTrack(36, 43); break; + case ROAD_C3: AddTrack(1, 36); pSafePedestrianTrack = AddTrack(36, 41); AddTrack(5, 12); AddTrack(12, 13); break; + case ROAD_C4: AddTrack(35, 40); AddTrack(40, 5); pSafePedestrianTrack = AddTrack(7, 8); AddTrack(8, 1); break; + + case ROAD_T1: pSafePedestrianTrack = AddTrack(7, 8); AddTrack(8, 12); AddTrack(12, 13); AddTrack(35, 36); AddTrack(36, 38); AddTrack(38, 40); AddTrack(40, 41); AddTrack(8, 22); AddTrack(22, 36); AddTrack(36, 43); AddTrack(12, 26); AddTrack(26, 40); AddTrack(40, 47); break; + case ROAD_T2: pSafePedestrianTrack = AddTrack(1, 8); AddTrack(8, 36); AddTrack(36, 43); AddTrack(5, 12); AddTrack(12, 26); AddTrack(26, 40); AddTrack(40, 47); AddTrack(8, 10); AddTrack(10, 12); AddTrack(12, 13); AddTrack(36, 38), AddTrack(38, 40); AddTrack(40, 41); break; + case ROAD_T3: pSafePedestrianTrack = AddTrack(5, 12); AddTrack(12, 40); AddTrack(40, 47); AddTrack(1, 8); AddTrack(8, 22); AddTrack(22, 36); AddTrack(36, 43); AddTrack(12, 10); AddTrack(10, 8); AddTrack(8, 7); AddTrack(40, 38); AddTrack(38, 36); AddTrack(36, 35); break; + case ROAD_T4: pSafePedestrianTrack = AddTrack(35, 36); AddTrack(36, 40); AddTrack(40, 41); AddTrack(7, 8); AddTrack(8, 10); AddTrack(10, 12); AddTrack(12, 13); AddTrack(36, 22); AddTrack(22, 8); AddTrack(8, 1); AddTrack(40, 26); AddTrack(26, 12); AddTrack(12, 5); break; + + case ROAD_X: AddTrack(35, 36); AddTrack(36, 38); AddTrack(38, 40); AddTrack(40, 41); AddTrack(7, 8); AddTrack(8, 10); AddTrack(10, 12); AddTrack(12, 13); AddTrack(36, 22); AddTrack(22, 8); AddTrack(8, 1); AddTrack(40, 26); AddTrack(26, 12); AddTrack(12, 5); pSafePedestrianTrack = AddTrack(36, 43); AddTrack(40, 47); break; + } + + + // Add Chase Tracks + switch (nRoadType) + { + case ROAD_H: AddTrack(21, 27); break; + case ROAD_V: AddTrack(3, 45); break; + + case ROAD_C1: AddTrack(45, 24); AddTrack(24, 27); break; + case ROAD_C2: AddTrack(21, 24); AddTrack(24, 45); break; + case ROAD_C3: AddTrack(3, 24); AddTrack(24, 27); break; + case ROAD_C4: AddTrack(21, 24); AddTrack(24, 3); break; + + case ROAD_T1: AddTrack(21, 24); AddTrack(24, 27); AddTrack(24, 45); break; + case ROAD_T2: AddTrack(3, 24); AddTrack(24, 45); AddTrack(24, 27); break; + case ROAD_T3: AddTrack(3, 24); AddTrack(24, 45); AddTrack(24, 21); break; + case ROAD_T4: AddTrack(21, 24); AddTrack(24, 27); AddTrack(24, 3); break; + + case ROAD_X: AddTrack(3, 24); AddTrack(27, 24); AddTrack(45, 24); AddTrack(21, 24); break; + } + + + //// Road traffic tracks + switch (nRoadType) + { + case ROAD_H: pSafeCarTrack = AddTrack(14, 20); AddTrack(28, 34); break; + case ROAD_V: AddTrack(2, 44); pSafeCarTrack = AddTrack(4, 46); break; + + case ROAD_C1: pSafeCarTrack = AddTrack(44, 16); AddTrack(16, 20); AddTrack(46, 32); AddTrack(32, 34); break; + case ROAD_C2: pSafeCarTrack = AddTrack(14, 18); AddTrack(18, 46); AddTrack(28, 30); AddTrack(30, 44); break; + case ROAD_C3: AddTrack(2, 30); AddTrack(30, 34); pSafeCarTrack = AddTrack(4, 18); AddTrack(18, 20); break; + case ROAD_C4: AddTrack(2, 16); AddTrack(16, 14); pSafeCarTrack = AddTrack(4, 32); AddTrack(32, 28); break; + + + case ROAD_T1: AddTrack(14, 16); AddTrack(16, 18); AddTrack(18, 20); AddTrack(28, 30); AddTrack(30, 32); AddTrack(32, 34); + AddTrack(16, 30); AddTrack(30, 44); AddTrack(18, 32); AddTrack(32, 46); break; + + case ROAD_T4: AddTrack(14, 16); AddTrack(16, 18); AddTrack(18, 20); AddTrack(28, 30); AddTrack(30, 32); AddTrack(32, 34); + AddTrack(16, 30); AddTrack(16, 2); AddTrack(18, 32); AddTrack(18, 4); break; + + case ROAD_T2: AddTrack(2, 16); AddTrack(16, 30); AddTrack(30, 44); AddTrack(4, 18); AddTrack(18, 32); AddTrack(32, 46); + AddTrack(16, 18); AddTrack(18, 20); AddTrack(30, 32); AddTrack(32, 34); break; + + case ROAD_T3: AddTrack(2, 16); AddTrack(16, 30); AddTrack(30, 44); AddTrack(4, 18); AddTrack(18, 32); AddTrack(32, 46); + AddTrack(14, 16); AddTrack(16, 18); AddTrack(28, 30); AddTrack(30, 32); break; + + case ROAD_X: + AddTrack(2, 16); AddTrack(16, 30); AddTrack(30, 44); AddTrack(4, 18); AddTrack(18, 32); AddTrack(32, 46); + AddTrack(14, 16); AddTrack(16, 18); AddTrack(18, 20); AddTrack(28, 30); AddTrack(30, 32); AddTrack(32, 34); break; + } + + + // Stop Patterns, here we go, loads of data... :( + + // .PO.OP. + // PP.P.PP + // O.O.O.O + // .P...P. + // O.O.O.O + // PP.P.PP + // .PO.OP. + + // .PO.OP. + // PP.P.PP + // O.X.X.O + // .P...P. + // O.X.X.O + // PP.P.PP + // .PO.OP. + + // .PO.OP. + // PP.X.PP + // O.X.X.O + // .X...X. + // O.X.X.O + // PP.X.PP + // .PO.OP. + + auto stopmap = [&](const std::string &s) + { + StopPattern p; + for (size_t i = 0; i < s.size(); i++) + p.bStop[i] = (s[i] == 'X'); + return p; + }; + + switch (nRoadType) + { + case ROAD_H: + case ROAD_V: + case ROAD_C1: + case ROAD_C2: + case ROAD_C3: + case ROAD_C4: + // Allow all + /*vStopPattern.push_back( + stopmap( + ".PO.OP." + "PP.P.PP" + "O.O.O.O" + ".P...P." + "O.O.O.O" + "PP.P.PP" + ".PO.OP."));*/ + break; + + case ROAD_X: + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "O.O.O.O" + ".X...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".X...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow North Traffic + vStopPattern.push_back( + stopmap( + ".PX.OP." + "PP.X.PP" + "X.X.O.O" + ".X...X." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain North Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.O.O" + ".X...X." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow EAST Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...X." + "O.O.O.O" + "PP.X.PP" + ".PX.OP.")); + // Drain East Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...X." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".X...X." + "O.O.X.X" + "PP.X.PP" + ".PO.XP.")); + // Drain SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".X...X." + "O.O.X.X" + "PP.X.PP" + ".PX.XP.")); + + break; + + case ROAD_T1: + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow West Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "O.O.O.O" + ".X...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain West Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.O.O.O" + ".X...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow EAST Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".X...X." + "O.O.O.O" + "PP.X.PP" + ".PX.OP.")); + // Drain East Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".X...X." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.O.O.O" + ".X...X." + "O.O.X.X" + "PP.X.PP" + ".PO.XP.")); + // Drain SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.O.O.O" + ".X...X." + "O.O.X.X" + "PP.X.PP" + ".PX.XP.")); + break; + + case ROAD_T2: + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".P...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow North Traffic + vStopPattern.push_back( + stopmap( + ".PX.OP." + "PP.X.PP" + "X.X.O.O" + ".P...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain North Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.O.O" + ".P...X." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow EAST Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".P...X." + "X.O.O.O" + "PP.X.PP" + ".PX.OP.")); + // Drain East Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".P...X." + "X.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".P...X." + "X.O.X.X" + "PP.X.PP" + ".PO.XP.")); + // Drain SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".P...X." + "X.O.X.X" + "PP.X.PP" + ".PX.XP.")); + break; + case ROAD_T3: + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...P." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "O.O.O.X" + ".X...P." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.X" + ".X...P." + "X.X.O.X" + "PP.X.PP" + ".PX.OP.")); + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow North Traffic + vStopPattern.push_back( + stopmap( + ".PX.OP." + "PP.X.PP" + "X.X.O.X" + ".X...P." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + // Drain North Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.O.X" + ".X...P." + "O.O.O.X" + "PP.X.PP" + ".PX.OP.")); + + // Allow SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...P." + "O.O.X.X" + "PP.X.PP" + ".PO.XP.")); + // Drain SOUTH Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...P." + "O.O.X.X" + "PP.X.PP" + ".PX.XP.")); + break; + + case ROAD_T4: + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Allow West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "O.O.O.O" + ".X...X." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain West Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.O.O" + ".X...X." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Allow North Traffic + vStopPattern.push_back( + stopmap( + ".PX.OP." + "PP.X.PP" + "X.X.O.O" + ".X...X." + "O.O.O.X" + "PP.P.PP" + ".PX.XP.")); + // Drain North Traffic + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.O.O" + ".X...X." + "O.O.O.X" + "PP.P.PP" + ".PX.XP.")); + // Allow Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.P.PP" + "X.X.X.X" + ".P...P." + "X.X.X.X" + "PP.P.PP" + ".PX.XP.")); + // Drain Pedestrians + vStopPattern.push_back( + stopmap( + ".PX.XP." + "PP.X.PP" + "X.X.X.X" + ".X...X." + "X.X.X.X" + "PP.X.PP" + ".PX.XP.")); + // Allow EAST Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...X." + "O.O.O.O" + "PP.P.PP" + ".PX.XP.")); + // Drain East Traffic + vStopPattern.push_back( + stopmap( + ".PO.XP." + "PP.X.PP" + "X.O.X.X" + ".X...X." + "O.O.O.X" + "PP.P.PP" + ".PX.XP.")); + break; + + + } + +} + +bool cCell_Road::LinkAssets(std::map& mapTextures, std::map& mapMesh, std::map &mapTransforms) +{ + meshUnitQuad = mapMesh["UnitQuad"]; + sprRoadTex[ROAD_V] = mapTextures["Road_V"]; + sprRoadTex[ROAD_H] = mapTextures["Road_H"]; + sprRoadTex[ROAD_C1] = mapTextures["Road_C1"]; + sprRoadTex[ROAD_T1] = mapTextures["Road_T1"]; + sprRoadTex[ROAD_C2] = mapTextures["Road_C2"]; + sprRoadTex[ROAD_T2] = mapTextures["Road_T2"]; + sprRoadTex[ROAD_X] = mapTextures["Road_X"]; + sprRoadTex[ROAD_T3] = mapTextures["Road_T3"]; + sprRoadTex[ROAD_C3] = mapTextures["Road_C3"]; + sprRoadTex[ROAD_T4] = mapTextures["Road_T4"]; + sprRoadTex[ROAD_C4] = mapTextures["Road_C4"]; + return false; +} + +bool cCell_Road::Update(float fElapsedTime) +{ + if (vStopPattern.empty()) + return false; + + fStopPatternTimer += fElapsedTime; + if (fStopPatternTimer >= 5.0f) + { + fStopPatternTimer -= 5.0f; + nCurrentStopPattern++; + nCurrentStopPattern %= vStopPattern.size(); + for (int i = 0; i < 49; i++) + if(pNaviNodes[i] != nullptr) + pNaviNodes[i]->bBlock = vStopPattern[nCurrentStopPattern].bStop[i]; + } + + + return false; +} + +bool cCell_Road::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(sprRoadTex[nRoadType]); + pipe.Render(meshUnitQuad->tris, olc::GFX3D::RENDER_CULL_CW | olc::GFX3D::RENDER_DEPTH | olc::GFX3D::RENDER_TEXTURED); + return false; +} + +bool cCell_Road::DrawAlpha(olc::PixelGameEngine* pge, olc::GFX3D::PipeLine & pipe) +{ + return false; +} + +bool cCell_Road::DrawDebug(olc::PixelGameEngine* pge, olc::GFX3D::PipeLine & pipe) +{ + + + + // Draw Automata navigation tracks + for (auto &track : listTracks) + { + olc::GFX3D::vec3d p1 = { track.node[0]->pos.x, track.node[0]->pos.y, 0.0f }; + olc::GFX3D::vec3d p2 = { track.node[1]->pos.x, track.node[1]->pos.y, 0.0f }; + pipe.RenderLine(p1, p2, olc::CYAN); + } + + + for (int i = 0; i < 49; i++) + { + if (pNaviNodes[i] != nullptr) + { + olc::GFX3D::vec3d p1 = { pNaviNodes[i]->pos.x, pNaviNodes[i]->pos.y, 0.01f }; + pipe.RenderCircleXZ(p1, 0.03f, pNaviNodes[i]->bBlock ? olc::RED : olc::GREEN); + } + } + + return false; +} diff --git a/Videos/CarCrimeCity/Part2/cCell_Road.h b/Videos/CarCrimeCity/Part2/cCell_Road.h new file mode 100644 index 0000000..d0f183e --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Road.h @@ -0,0 +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 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 &mapTextures, std::map &mapMesh, std::map &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); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCell_Water.cpp b/Videos/CarCrimeCity/Part2/cCell_Water.cpp new file mode 100644 index 0000000..91f0fc3 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Water.cpp @@ -0,0 +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& mapTextures, std::map& mapMesh, std::map &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); +} \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/cCell_Water.h b/Videos/CarCrimeCity/Part2/cCell_Water.h new file mode 100644 index 0000000..4ed0502 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCell_Water.h @@ -0,0 +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 &mapTextures, std::map &mapMesh, std::map &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); +}; + diff --git a/Videos/CarCrimeCity/Part2/cCityMap.cpp b/Videos/CarCrimeCity/Part2/cCityMap.cpp new file mode 100644 index 0000000..712a225 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCityMap.cpp @@ -0,0 +1,202 @@ +#include "cCityMap.h" + +#include + + + +cCityMap::cCityMap(int w, int h, std::map &mapTextures, std::map &mapMesh, std::map &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 &mapTextures, std::map &mapMesh, std::map &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; +} \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/cCityMap.h b/Videos/CarCrimeCity/Part2/cCityMap.h new file mode 100644 index 0000000..43a4456 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cCityMap.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#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 &mapTextures, std::map &mapMesh, std::map &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 &mapTextures, std::map &mapMesh, std::map &mapTransforms); + // Destroy city + void ReleaseCity(); +}; + diff --git a/Videos/CarCrimeCity/Part2/cGameSettings.cpp b/Videos/CarCrimeCity/Part2/cGameSettings.cpp new file mode 100644 index 0000000..17842d3 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cGameSettings.cpp @@ -0,0 +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 &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 cGameSettings::vecAssetTextures; +std::vector cGameSettings::vecAssetBuildings; +std::vector cGameSettings::vecAssetVehicles; \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/cGameSettings.h b/Videos/CarCrimeCity/Part2/cGameSettings.h new file mode 100644 index 0000000..7855461 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/cGameSettings.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +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 vecAssetTextures; + static std::vector vecAssetBuildings; + static std::vector vecAssetVehicles; +}; + diff --git a/Videos/CarCrimeCity/Part2/lua53.dll b/Videos/CarCrimeCity/Part2/lua53.dll new file mode 100644 index 0000000..2ab8168 Binary files /dev/null and b/Videos/CarCrimeCity/Part2/lua53.dll differ diff --git a/Videos/CarCrimeCity/Part2/main.cpp b/Videos/CarCrimeCity/Part2/main.cpp new file mode 100644 index 0000000..7f5a732 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/main.cpp @@ -0,0 +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; +//} \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h b/Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h new file mode 100644 index 0000000..970c021 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h @@ -0,0 +1,1725 @@ +/* + olcPGEX_Graphics3D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | 3D Rendering - v0.3 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + support for software rendering 3D graphics. + + NOTE!!! This file is under development and may change! + + Big Thanks to MaGetzUb for finding an OOB error, and joshinils + for pointing out sampling inaccuracy. + + 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_GFX3D +#define OLC_PGEX_GFX3D + +#include +#include +#include +#include +#include +#include +#undef min +#undef max + +//#include + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX3D : public olc::PGEX + { + + public: + + struct vec2d + { + float x = 0; + float y = 0; + float z = 0; + }; + + struct vec3d + { + float x = 0; + float y = 0; + float z = 0; + float w = 1; // Need a 4th term to perform sensible matrix vector multiplication + }; + + struct triangle + { + vec3d p[3]; + vec2d t[3]; + olc::Pixel col[3]; + }; + + struct mat4x4 + { + float m[4][4] = { 0 }; + }; + + struct mesh + { + std::vector tris; + bool LoadOBJFile(std::string sFilename, bool bHasTexture = false); + }; + + /*class MipMap : public olc::Sprite + { + public: + MipMap(); + MipMap(std::string sImageFile); + MipMap(std::string sImageFile, olc::ResourcePack *pack); + MipMap(int32_t w, int32_t h); + ~MipMap(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + Pixel Sample(float x, float y, float z); + Pixel SampleBL(float u, float v, float z); + + private: + int GenerateMipLevels(); + std::vector vecMipMaps; + + };*/ + + class Math + { + public: + Math(); + public: + static vec3d Mat_MultiplyVector(mat4x4 &m, vec3d &i); + static mat4x4 Mat_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2); + static mat4x4 Mat_MakeIdentity(); + static mat4x4 Mat_MakeRotationX(float fAngleRad); + static mat4x4 Mat_MakeRotationY(float fAngleRad); + static mat4x4 Mat_MakeRotationZ(float fAngleRad); + static mat4x4 Mat_MakeScale(float x, float y, float z); + static mat4x4 Mat_MakeTranslation(float x, float y, float z); + static mat4x4 Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar); + static mat4x4 Mat_PointAt(vec3d &pos, vec3d &target, vec3d &up); + static mat4x4 Mat_QuickInverse(mat4x4 &m); // Only for Rotation/Translation Matrices + static mat4x4 Mat_Inverse(olc::GFX3D::mat4x4 &m); + + static vec3d Vec_Add(vec3d &v1, vec3d &v2); + static vec3d Vec_Sub(vec3d &v1, vec3d &v2); + static vec3d Vec_Mul(vec3d &v1, float k); + static vec3d Vec_Div(vec3d &v1, float k); + static float Vec_DotProduct(vec3d &v1, vec3d &v2); + static float Vec_Length(vec3d &v); + static vec3d Vec_Normalise(vec3d &v); + static vec3d Vec_CrossProduct(vec3d &v1, vec3d &v2); + static vec3d Vec_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd, float &t); + + static int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2); + }; + + enum RENDERFLAGS + { + RENDER_WIRE = 0x01, + RENDER_FLAT = 0x02, + RENDER_TEXTURED = 0x04, + RENDER_CULL_CW = 0x08, + RENDER_CULL_CCW = 0x10, + RENDER_DEPTH = 0x20, + RENDER_LIGHTS = 0x40, + }; + + enum LIGHTS + { + LIGHT_DISABLED, + LIGHT_AMBIENT, + LIGHT_DIRECTIONAL, + LIGHT_POINT + }; + + + class PipeLine + { + public: + PipeLine(); + + public: + void SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight); + void SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up); + void SetTransform(olc::GFX3D::mat4x4 &transform); + void SetTexture(olc::Sprite *texture); + //void SetMipMapTexture(olc::GFX3D::MipMap *texture); + void SetLightSource(uint32_t nSlot, uint32_t nType, olc::Pixel col, olc::GFX3D::vec3d pos, olc::GFX3D::vec3d dir = { 0.0f, 0.0f, 1.0f }, float fParam = 0.0f); + uint32_t Render(std::vector &triangles, uint32_t flags = RENDER_CULL_CW | RENDER_TEXTURED | RENDER_DEPTH); + uint32_t Render(std::vector &triangles, uint32_t flags, int nOffset, int nCount); + uint32_t RenderLine(olc::GFX3D::vec3d &p1, olc::GFX3D::vec3d &p2, olc::Pixel col = olc::WHITE); + uint32_t RenderCircleXZ(olc::GFX3D::vec3d &p1, float r, olc::Pixel col = olc::WHITE); + + private: + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::mat4x4 matView; + olc::GFX3D::mat4x4 matWorld; + olc::Sprite *sprTexture; + //olc::GFX3D::MipMap *sprMipMap; + //bool bUseMipMap; + float fViewX; + float fViewY; + float fViewW; + float fViewH; + + struct sLight + { + uint32_t type; + olc::GFX3D::vec3d pos; + olc::GFX3D::vec3d dir; + olc::Pixel col; + float param; + } lights[4]; + }; + + + + public: + //static const int RF_TEXTURE = 0x00000001; + //static const int RF_ = 0x00000002; + + static void ConfigureDisplay(); + static void ClearDepth(); + static void AddTriangleToScene(olc::GFX3D::triangle &tri); + static void RenderScene(); + + static void DrawTriangleFlat(olc::GFX3D::triangle &tri); + static void DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col = olc::WHITE); + static void DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr); + static void TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr); + + static void RasterTriangle(int x1, int y1, float u1, float v1, float w1, olc::Pixel c1, + int x2, int y2, float u2, float v2, float w2, olc::Pixel c2, + int x3, int y3, float u3, float v3, float w3, olc::Pixel c3, + olc::Sprite* spr, + uint32_t nFlags); + + // Draws a sprite with the transform applied + //inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + + private: + static float* m_DepthBuffer; + }; +} + +#endif + + +#ifdef OLC_PGEX_GRAPHICS3D +#undef OLC_PGEX_GRAPHICS3D + +namespace olc +{ + olc::GFX3D::Math::Math() + { + + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Mat_MultiplyVector(olc::GFX3D::mat4x4 &m, olc::GFX3D::vec3d &i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeIdentity() + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationX(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationY(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationZ(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeScale(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = x; + matrix.m[1][1] = y; + matrix.m[2][2] = z; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeTranslation(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MultiplyMatrix(olc::GFX3D::mat4x4 &m1, olc::GFX3D::mat4x4 &m2) + { + olc::GFX3D::mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_PointAt(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &target, olc::GFX3D::vec3d &up) + { + // Calculate new forward direction + olc::GFX3D::vec3d newForward = Vec_Sub(target, pos); + newForward = Vec_Normalise(newForward); + + // Calculate new Up direction + olc::GFX3D::vec3d a = Vec_Mul(newForward, Vec_DotProduct(up, newForward)); + olc::GFX3D::vec3d newUp = Vec_Sub(up, a); + newUp = Vec_Normalise(newUp); + + // New Right direction is easy, its just cross product + olc::GFX3D::vec3d newRight = Vec_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_QuickInverse(olc::GFX3D::mat4x4 &m) // Only for Rotation/Translation Matrices + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_Inverse(olc::GFX3D::mat4x4 &m) + { + double det; + + + mat4x4 matInv; + + matInv.m[0][0] = m.m[1][1] * m.m[2][2] * m.m[3][3] - m.m[1][1] * m.m[2][3] * m.m[3][2] - m.m[2][1] * m.m[1][2] * m.m[3][3] + m.m[2][1] * m.m[1][3] * m.m[3][2] + m.m[3][1] * m.m[1][2] * m.m[2][3] - m.m[3][1] * m.m[1][3] * m.m[2][2]; + matInv.m[1][0] = -m.m[1][0] * m.m[2][2] * m.m[3][3] + m.m[1][0] * m.m[2][3] * m.m[3][2] + m.m[2][0] * m.m[1][2] * m.m[3][3] - m.m[2][0] * m.m[1][3] * m.m[3][2] - m.m[3][0] * m.m[1][2] * m.m[2][3] + m.m[3][0] * m.m[1][3] * m.m[2][2]; + matInv.m[2][0] = m.m[1][0] * m.m[2][1] * m.m[3][3] - m.m[1][0] * m.m[2][3] * m.m[3][1] - m.m[2][0] * m.m[1][1] * m.m[3][3] + m.m[2][0] * m.m[1][3] * m.m[3][1] + m.m[3][0] * m.m[1][1] * m.m[2][3] - m.m[3][0] * m.m[1][3] * m.m[2][1]; + matInv.m[3][0] = -m.m[1][0] * m.m[2][1] * m.m[3][2] + m.m[1][0] * m.m[2][2] * m.m[3][1] + m.m[2][0] * m.m[1][1] * m.m[3][2] - m.m[2][0] * m.m[1][2] * m.m[3][1] - m.m[3][0] * m.m[1][1] * m.m[2][2] + m.m[3][0] * m.m[1][2] * m.m[2][1]; + matInv.m[0][1] = -m.m[0][1] * m.m[2][2] * m.m[3][3] + m.m[0][1] * m.m[2][3] * m.m[3][2] + m.m[2][1] * m.m[0][2] * m.m[3][3] - m.m[2][1] * m.m[0][3] * m.m[3][2] - m.m[3][1] * m.m[0][2] * m.m[2][3] + m.m[3][1] * m.m[0][3] * m.m[2][2]; + matInv.m[1][1] = m.m[0][0] * m.m[2][2] * m.m[3][3] - m.m[0][0] * m.m[2][3] * m.m[3][2] - m.m[2][0] * m.m[0][2] * m.m[3][3] + m.m[2][0] * m.m[0][3] * m.m[3][2] + m.m[3][0] * m.m[0][2] * m.m[2][3] - m.m[3][0] * m.m[0][3] * m.m[2][2]; + matInv.m[2][1] = -m.m[0][0] * m.m[2][1] * m.m[3][3] + m.m[0][0] * m.m[2][3] * m.m[3][1] + m.m[2][0] * m.m[0][1] * m.m[3][3] - m.m[2][0] * m.m[0][3] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[2][3] + m.m[3][0] * m.m[0][3] * m.m[2][1]; + matInv.m[3][1] = m.m[0][0] * m.m[2][1] * m.m[3][2] - m.m[0][0] * m.m[2][2] * m.m[3][1] - m.m[2][0] * m.m[0][1] * m.m[3][2] + m.m[2][0] * m.m[0][2] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[2][2] - m.m[3][0] * m.m[0][2] * m.m[2][1]; + matInv.m[0][2] = m.m[0][1] * m.m[1][2] * m.m[3][3] - m.m[0][1] * m.m[1][3] * m.m[3][2] - m.m[1][1] * m.m[0][2] * m.m[3][3] + m.m[1][1] * m.m[0][3] * m.m[3][2] + m.m[3][1] * m.m[0][2] * m.m[1][3] - m.m[3][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][2] = -m.m[0][0] * m.m[1][2] * m.m[3][3] + m.m[0][0] * m.m[1][3] * m.m[3][2] + m.m[1][0] * m.m[0][2] * m.m[3][3] - m.m[1][0] * m.m[0][3] * m.m[3][2] - m.m[3][0] * m.m[0][2] * m.m[1][3] + m.m[3][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][2] = m.m[0][0] * m.m[1][1] * m.m[3][3] - m.m[0][0] * m.m[1][3] * m.m[3][1] - m.m[1][0] * m.m[0][1] * m.m[3][3] + m.m[1][0] * m.m[0][3] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[1][3] - m.m[3][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][2] = -m.m[0][0] * m.m[1][1] * m.m[3][2] + m.m[0][0] * m.m[1][2] * m.m[3][1] + m.m[1][0] * m.m[0][1] * m.m[3][2] - m.m[1][0] * m.m[0][2] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[1][2] + m.m[3][0] * m.m[0][2] * m.m[1][1]; + matInv.m[0][3] = -m.m[0][1] * m.m[1][2] * m.m[2][3] + m.m[0][1] * m.m[1][3] * m.m[2][2] + m.m[1][1] * m.m[0][2] * m.m[2][3] - m.m[1][1] * m.m[0][3] * m.m[2][2] - m.m[2][1] * m.m[0][2] * m.m[1][3] + m.m[2][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][3] = m.m[0][0] * m.m[1][2] * m.m[2][3] - m.m[0][0] * m.m[1][3] * m.m[2][2] - m.m[1][0] * m.m[0][2] * m.m[2][3] + m.m[1][0] * m.m[0][3] * m.m[2][2] + m.m[2][0] * m.m[0][2] * m.m[1][3] - m.m[2][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][3] = -m.m[0][0] * m.m[1][1] * m.m[2][3] + m.m[0][0] * m.m[1][3] * m.m[2][1] + m.m[1][0] * m.m[0][1] * m.m[2][3] - m.m[1][0] * m.m[0][3] * m.m[2][1] - m.m[2][0] * m.m[0][1] * m.m[1][3] + m.m[2][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][3] = m.m[0][0] * m.m[1][1] * m.m[2][2] - m.m[0][0] * m.m[1][2] * m.m[2][1] - m.m[1][0] * m.m[0][1] * m.m[2][2] + m.m[1][0] * m.m[0][2] * m.m[2][1] + m.m[2][0] * m.m[0][1] * m.m[1][2] - m.m[2][0] * m.m[0][2] * m.m[1][1]; + + det = m.m[0][0] * matInv.m[0][0] + m.m[0][1] * matInv.m[1][0] + m.m[0][2] * matInv.m[2][0] + m.m[0][3] * matInv.m[3][0]; + // if (det == 0) return false; + + det = 1.0 / det; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + matInv.m[i][j] *= (float)det; + + return matInv; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Add(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Sub(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Mul(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Div(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float olc::GFX3D::Math::Vec_DotProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z; + } + + float olc::GFX3D::Math::Vec_Length(olc::GFX3D::vec3d &v) + { + return sqrtf(Vec_DotProduct(v, v)); + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Normalise(olc::GFX3D::vec3d &v) + { + float l = Vec_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_CrossProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_IntersectPlane(olc::GFX3D::vec3d &plane_p, olc::GFX3D::vec3d &plane_n, olc::GFX3D::vec3d &lineStart, olc::GFX3D::vec3d &lineEnd, float &t) + { + plane_n = Vec_Normalise(plane_n); + float plane_d = -Vec_DotProduct(plane_n, plane_p); + float ad = Vec_DotProduct(lineStart, plane_n); + float bd = Vec_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + olc::GFX3D::vec3d lineStartToEnd = Vec_Sub(lineEnd, lineStart); + olc::GFX3D::vec3d lineToIntersect = Vec_Mul(lineStartToEnd, t); + return Vec_Add(lineStart, lineToIntersect); + } + + + int olc::GFX3D::Math::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Math::Vec_Normalise(plane_n); + + out_tri1.t[0] = in_tri.t[0]; + out_tri2.t[0] = in_tri.t[0]; + out_tri1.t[1] = in_tri.t[1]; + out_tri2.t[1] = in_tri.t[1]; + out_tri1.t[2] = in_tri.t[2]; + out_tri2.t[2] = in_tri.t[2]; + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d &p) + { + vec3d n = Math::Vec_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Math::Vec_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.t[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.t[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.t[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.t[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.t[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.t[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col[0] = in_tri.col[0]; + out_tri1.col[1] = in_tri.col[1]; + out_tri1.col[2] = in_tri.col[2]; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[1].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[1].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[1].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.t[2].x = t * (outside_tex[1]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[1]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[1]->z - inside_tex[0]->z) + inside_tex[0]->z; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col[0] = in_tri.col[0]; + out_tri2.col[0] = in_tri.col[0]; + out_tri1.col[1] = in_tri.col[1]; + out_tri2.col[1] = in_tri.col[1]; + out_tri1.col[2] = in_tri.col[2]; + out_tri2.col[2] = in_tri.col[2]; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + out_tri1.p[1] = *inside_points[1]; + out_tri1.t[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[2].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[1] = *inside_points[1]; + out_tri2.t[1] = *inside_tex[1]; + out_tri2.p[0] = out_tri1.p[2]; + out_tri2.t[0] = out_tri1.t[2]; + out_tri2.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.t[2].x = t * (outside_tex[0]->x - inside_tex[1]->x) + inside_tex[1]->x; + out_tri2.t[2].y = t * (outside_tex[0]->y - inside_tex[1]->y) + inside_tex[1]->y; + out_tri2.t[2].z = t * (outside_tex[0]->z - inside_tex[1]->z) + inside_tex[1]->z; + return 2; // Return two newly formed triangles which form a quad + } + + return 0; + } + + void GFX3D::DrawTriangleFlat(olc::GFX3D::triangle &tri) + { + pge->FillTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, tri.col[0]); + } + + void GFX3D::DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col) + { + pge->DrawTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, col); + } + + void GFX3D::TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr) + + { + if (y2 < y1) + { + std::swap(y1, y2); + std::swap(x1, x2); + std::swap(u1, u2); + std::swap(v1, v2); + std::swap(w1, w2); + } + + if (y3 < y1) + { + std::swap(y1, y3); + std::swap(x1, x3); + std::swap(u1, u3); + std::swap(v1, v3); + std::swap(w1, w3); + } + + if (y3 < y2) + { + std::swap(y2, y3); + std::swap(x2, x3); + std::swap(u2, u3); + std::swap(v2, v3); + std::swap(w2, w3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + + float tex_u, tex_v, tex_w; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + /*if (bMipMap) + pge->Draw(j, i, ((olc::GFX3D::MipMap*)spr)->Sample(tex_u / tex_w, tex_v / tex_w, tex_w)); + else*/ + if(pge->Draw(j, i, spr != nullptr ? spr->Sample(tex_u / tex_w, tex_v / tex_w) : olc::GREY)) + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0, dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + /*if(bMipMap) + pge->Draw(j, i, ((olc::GFX3D::MipMap*)spr)->Sample(tex_u / tex_w, tex_v / tex_w, tex_w)); + else*/ + if(pge->Draw(j, i, spr != nullptr ? spr->Sample(tex_u / tex_w, tex_v / tex_w) : olc::GREY)) + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + } + } + } + + + void GFX3D::DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr) + { + + } + + float* GFX3D::m_DepthBuffer = nullptr; + + void GFX3D::ConfigureDisplay() + { + m_DepthBuffer = new float[pge->ScreenWidth() * pge->ScreenHeight()]{ 0 }; + } + + + void GFX3D::ClearDepth() + { + memset(m_DepthBuffer, 0, pge->ScreenWidth() * pge->ScreenHeight() * sizeof(float)); + } + + bool GFX3D::mesh::LoadOBJFile(std::string sFilename, bool bHasTexture) + { + std::ifstream f(sFilename); + if (!f.is_open()) return false; + + // Local cache of verts + std::vector verts; + std::vector norms; + std::vector texs; + + while (!f.eof()) + { + char line[128]; + f.getline(line, 128); + + std::strstream s; + s << line; + + char junk; + + if (line[0] == 'v') + { + if (line[1] == 't') + { + vec2d v; + s >> junk >> junk >> v.x >> v.y; + //v.x = 1.0f - v.x; + v.y = 1.0f - v.y; + texs.push_back(v); + } + else if (line[1] == 'n') + { + vec3d v; + s >> junk >> junk >> v.x >> v.y >> v.z; + norms.push_back(v); + } + else + { + vec3d v; + s >> junk >> v.x >> v.y >> v.z; + verts.push_back(v); + } + } + + + /*if (!bHasTexture) + { + if (line[0] == 'f') + { + int f[3]; + s >> junk >> f[0] >> f[1] >> f[2]; + tris.push_back({ verts[f[0] - 1], verts[f[1] - 1], verts[f[2] - 1] }); + } + } + else*/ + { + if (line[0] == 'f') + { + s >> junk; + + std::string tokens[9]; + int nTokenCount = -1; + while (!s.eof()) + { + char c = s.get(); + if (c == ' ' || c == '/') + { + if (tokens[nTokenCount].size() > 0) + { + nTokenCount++; + } + } + else + tokens[nTokenCount].append(1, c); + } + + tokens[nTokenCount].pop_back(); + + int stride = 1; + if (!texs.empty()) stride++; + if (!norms.empty()) stride++; + + if (!texs.empty()) + { + tris.push_back({ + verts[stoi(tokens[0 * stride]) - 1], + verts[stoi(tokens[1 * stride]) - 1], + verts[stoi(tokens[2 * stride]) - 1], + texs[stoi(tokens[0 * stride + 1]) - 1], + texs[stoi(tokens[1 * stride + 1]) - 1], + texs[stoi(tokens[2 * stride + 1]) - 1], + olc::WHITE, olc::WHITE, olc::WHITE}); + } + else + { + tris.push_back({ + verts[stoi(tokens[0 * stride]) - 1], + verts[stoi(tokens[1 * stride]) - 1], + verts[stoi(tokens[2 * stride]) - 1], + olc::GFX3D::vec2d{0,0,0}, + olc::GFX3D::vec2d{0,0,0}, + olc::GFX3D::vec2d{0,0,0}, + olc::WHITE, olc::WHITE, olc::WHITE }); + + } + } + } + } + return true; + } + + + GFX3D::PipeLine::PipeLine() + { + //bUseMipMap = false; + } + + void GFX3D::PipeLine::SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight) + { + matProj = GFX3D::Math::Mat_MakeProjection(fFovDegrees, fAspectRatio, fNear, fFar); + fViewX = fLeft; + fViewY = fTop; + fViewW = fWidth; + fViewH = fHeight; + } + + void GFX3D::PipeLine::SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up) + { + matView = GFX3D::Math::Mat_PointAt(pos, lookat, up); + matView = GFX3D::Math::Mat_QuickInverse(matView); + } + + void GFX3D::PipeLine::SetTransform(olc::GFX3D::mat4x4 &transform) + { + matWorld = transform; + } + + void GFX3D::PipeLine::SetTexture(olc::Sprite *texture) + { + sprTexture = texture; + //bUseMipMap = false; + } + + /*void GFX3D::PipeLine::SetMipMapTexture(olc::GFX3D::MipMap *texture) + { + sprMipMap = texture; + bUseMipMap = true; + }*/ + + void GFX3D::PipeLine::SetLightSource(uint32_t nSlot, uint32_t nType, olc::Pixel col, olc::GFX3D::vec3d pos, olc::GFX3D::vec3d dir, float fParam) + { + if (nSlot < 4) + { + lights[nSlot].type = nType; + lights[nSlot].pos = pos; + lights[nSlot].dir = dir; + lights[nSlot].col = col; + lights[nSlot].param = fParam; + } + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags) + { + return Render(triangles, flags, 0, triangles.size()); + } + + uint32_t GFX3D::PipeLine::RenderLine(olc::GFX3D::vec3d &p1, olc::GFX3D::vec3d &p2, olc::Pixel col) + { + // Coordinates are assumed to be in world space + olc::GFX3D::vec3d t1, t2; + + // Transform into view + t1 = GFX3D::Math::Mat_MultiplyVector(matView, p1); + t2 = GFX3D::Math::Mat_MultiplyVector(matView, p2); + + // Project onto screen + t1 = GFX3D::Math::Mat_MultiplyVector(matProj, t1); + t2 = GFX3D::Math::Mat_MultiplyVector(matProj, t2); + + // Project + t1.x = t1.x / t1.w; + t1.y = t1.y / t1.w; + t1.z = t1.z / t1.w; + + t2.x = t2.x / t2.w; + t2.y = t2.y / t2.w; + t2.z = t2.z / t2.w; + + vec3d vOffsetView = { 1,1,0 }; + t1 = Math::Vec_Add(t1, vOffsetView); + t2 = Math::Vec_Add(t2, vOffsetView); + + t1.x *= 0.5f * fViewW; + t1.y *= 0.5f * fViewH; + t2.x *= 0.5f * fViewW; + t2.y *= 0.5f * fViewH; + + vOffsetView = { fViewX,fViewY,0 }; + t1 = Math::Vec_Add(t1, vOffsetView); + t2 = Math::Vec_Add(t2, vOffsetView); + + pge->DrawLine(t1.x, t1.y, t2.x, t2.y, col); + + return 0; + } + + uint32_t GFX3D::PipeLine::RenderCircleXZ(olc::GFX3D::vec3d &p1, float r, olc::Pixel col) + { + // Coordinates are assumed to be in world space + olc::GFX3D::vec3d t1; + olc::GFX3D::vec3d t2 = { p1.x + r, p1.y, p1.z }; + + // Transform into view + t1 = GFX3D::Math::Mat_MultiplyVector(matView, p1); + t2 = GFX3D::Math::Mat_MultiplyVector(matView, t2); + + // Project onto screen + t1 = GFX3D::Math::Mat_MultiplyVector(matProj, t1); + t2 = GFX3D::Math::Mat_MultiplyVector(matProj, t2); + + // Project + t1.x = t1.x / t1.w; + t1.y = t1.y / t1.w; + t1.z = t1.z / t1.w; + + t2.x = t2.x / t2.w; + t2.y = t2.y / t2.w; + t2.z = t2.z / t2.w; + + vec3d vOffsetView = { 1,1,0 }; + t1 = Math::Vec_Add(t1, vOffsetView); + t2 = Math::Vec_Add(t2, vOffsetView); + + t1.x *= 0.5f * fViewW; + t1.y *= 0.5f * fViewH; + t2.x *= 0.5f * fViewW; + t2.y *= 0.5f * fViewH; + + vOffsetView = { fViewX,fViewY,0 }; + t1 = Math::Vec_Add(t1, vOffsetView); + t2 = Math::Vec_Add(t2, vOffsetView); + + pge->FillCircle(t1.x, t1.y, fabs(t2.x - t1.x), col); + + return 0; + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags, int nOffset, int nCount) + { + // Calculate Transformation Matrix + mat4x4 matWorldView = Math::Mat_MultiplyMatrix(matWorld, matView); + //matWorldViewProj = Math::Mat_MultiplyMatrix(matWorldView, matProj); + + // Store triangles for rastering later + std::vector vecTrianglesToRaster; + + int nTriangleDrawnCount = 0; + + // Process Triangles + //for (auto &tri : triangles) +// omp_set_dynamic(0); +// omp_set_num_threads(4); +//#pragma omp parallel for schedule(static) + for(int tx = nOffset; tx < nOffset+nCount; tx++) + { + GFX3D::triangle &tri = triangles[tx]; + GFX3D::triangle triTransformed; + + // Just copy through texture coordinates + triTransformed.t[0] = { tri.t[0].x, tri.t[0].y, tri.t[0].z }; + triTransformed.t[1] = { tri.t[1].x, tri.t[1].y, tri.t[1].z }; + triTransformed.t[2] = { tri.t[2].x, tri.t[2].y, tri.t[2].z }; // Think! + + // Dont forget vertex colours + triTransformed.col[0] = tri.col[0]; + triTransformed.col[1] = tri.col[1]; + triTransformed.col[2] = tri.col[2]; + + // Transform Triangle from object into projected space + triTransformed.p[0] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[0]); + triTransformed.p[1] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[1]); + triTransformed.p[2] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[2]); + + // Calculate Triangle Normal in WorldView Space + GFX3D::vec3d normal, line1, line2; + line1 = GFX3D::Math::Vec_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = GFX3D::Math::Vec_Sub(triTransformed.p[2], triTransformed.p[0]); + normal = GFX3D::Math::Vec_CrossProduct(line1, line2); + normal = GFX3D::Math::Vec_Normalise(normal); + + // Cull triangles that face away from viewer + if (flags & RENDER_CULL_CW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) > 0.0f) continue; + if (flags & RENDER_CULL_CCW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) < 0.0f) continue; + + // If Lighting, calculate shading + if (flags & RENDER_LIGHTS) + { + olc::Pixel ambient_clamp = { 0,0,0 }; + olc::Pixel light_combined = { 0,0,0 }; + uint32_t nLightSources = 0; + float nLightR = 0, nLightG = 0, nLightB = 0; + + for (int i = 0; i < 4; i++) + { + switch (lights[i].type) + { + case LIGHT_DISABLED: + break; + case LIGHT_AMBIENT: + ambient_clamp = lights[i].col; + break; + case LIGHT_DIRECTIONAL: + { + nLightSources++; + GFX3D::vec3d light_dir = GFX3D::Math::Vec_Normalise(lights[i].dir); + float light = GFX3D::Math::Vec_DotProduct(light_dir, normal); + if (light > 0) + { + int j = 0; + } + + light = std::max(light, 0.0f); + nLightR += light * (lights[i].col.r/255.0f); + nLightG += light * (lights[i].col.g/255.0f); + nLightB += light * (lights[i].col.b/255.0f); + } + break; + case LIGHT_POINT: + break; + } + } + + //nLightR /= nLightSources; + //nLightG /= nLightSources; + //nLightB /= nLightSources; + + nLightR = std::max(nLightR, ambient_clamp.r / 255.0f); + nLightG = std::max(nLightG, ambient_clamp.g / 255.0f); + nLightB = std::max(nLightB, ambient_clamp.b / 255.0f); + + triTransformed.col[0] = olc::Pixel(nLightR * triTransformed.col[0].r, nLightG * triTransformed.col[0].g, nLightB * triTransformed.col[0].b); + triTransformed.col[1] = olc::Pixel(nLightR * triTransformed.col[1].r, nLightG * triTransformed.col[1].g, nLightB * triTransformed.col[1].b); + triTransformed.col[2] = olc::Pixel(nLightR * triTransformed.col[2].r, nLightG * triTransformed.col[2].g, nLightB * triTransformed.col[2].b); + + + + /*GFX3D::vec3d light_dir = { 1,1,1 }; + light_dir = GFX3D::Math::Vec_Normalise(light_dir); + float light = GFX3D::Math::Vec_DotProduct(light_dir, normal); + if (light < 0) light = 0; + triTransformed.col[0] = olc::Pixel(light * 255.0f, light * 255.0f, light * 255.0f); + triTransformed.col[1] = olc::Pixel(light * 255.0f, light * 255.0f, light * 255.0f); + triTransformed.col[2] = olc::Pixel(light * 255.0f, light * 255.0f, light * 255.0f);*/ + } + //else + // triTransformed.col = olc::WHITE; + + // Clip triangle against near plane + int nClippedTriangles = 0; + GFX3D::triangle clipped[2]; + nClippedTriangles = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triTransformed, clipped[0], clipped[1]); + + // This may yield two new triangles + for (int n = 0; n < nClippedTriangles; n++) + { + triangle triProjected = clipped[n]; + + // Project new triangle + triProjected.p[0] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[2]); + + // Apply Projection to Verts + triProjected.p[0].x = triProjected.p[0].x / triProjected.p[0].w; + triProjected.p[1].x = triProjected.p[1].x / triProjected.p[1].w; + triProjected.p[2].x = triProjected.p[2].x / triProjected.p[2].w; + + triProjected.p[0].y = triProjected.p[0].y / triProjected.p[0].w; + triProjected.p[1].y = triProjected.p[1].y / triProjected.p[1].w; + triProjected.p[2].y = triProjected.p[2].y / triProjected.p[2].w; + + triProjected.p[0].z = triProjected.p[0].z / triProjected.p[0].w; + triProjected.p[1].z = triProjected.p[1].z / triProjected.p[1].w; + triProjected.p[2].z = triProjected.p[2].z / triProjected.p[2].w; + + // Apply Projection to Tex coords + triProjected.t[0].x = triProjected.t[0].x / triProjected.p[0].w; + triProjected.t[1].x = triProjected.t[1].x / triProjected.p[1].w; + triProjected.t[2].x = triProjected.t[2].x / triProjected.p[2].w; + + triProjected.t[0].y = triProjected.t[0].y / triProjected.p[0].w; + triProjected.t[1].y = triProjected.t[1].y / triProjected.p[1].w; + triProjected.t[2].y = triProjected.t[2].y / triProjected.p[2].w; + + triProjected.t[0].z = 1.0f / triProjected.p[0].w; + triProjected.t[1].z = 1.0f / triProjected.p[1].w; + triProjected.t[2].z = 1.0f / triProjected.p[2].w; + + // Clip against viewport in screen space + // Clip triangles against all four screen edges, this could yield + // a bunch of triangles, so create a queue that we traverse to + // ensure we only test new triangles generated against planes + GFX3D::triangle sclipped[2]; + std::list listTriangles; + + + // Add initial triangle + listTriangles.push_back(triProjected); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 1: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, +1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 2: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 3: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ +1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + } + + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(sclipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto &triRaster : listTriangles) + { + // Scale to viewport + /*triRaster.p[0].x *= -1.0f; + triRaster.p[1].x *= -1.0f; + triRaster.p[2].x *= -1.0f; + triRaster.p[0].y *= -1.0f; + triRaster.p[1].y *= -1.0f; + triRaster.p[2].y *= -1.0f;*/ + vec3d vOffsetView = { 1,1,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + triRaster.p[0].x *= 0.5f * fViewW; + triRaster.p[0].y *= 0.5f * fViewH; + triRaster.p[1].x *= 0.5f * fViewW; + triRaster.p[1].y *= 0.5f * fViewH; + triRaster.p[2].x *= 0.5f * fViewW; + triRaster.p[2].y *= 0.5f * fViewH; + vOffsetView = { fViewX,fViewY,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + + // For now, just draw triangle + + //if (flags & RENDER_TEXTURED) + //{/* + // TexturedTriangle( + // triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, + // triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, + // triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, + // sprTexture);*/ + + // RasterTriangle( + // triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, triRaster.col, + // triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, triRaster.col, + // triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, triRaster.col, + // sprTexture, nFlags); + + //} + + if (flags & RENDER_WIRE) + { + DrawTriangleWire(triRaster, olc::RED); + } + else + { + RasterTriangle( + triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, triRaster.col[0], + triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, triRaster.col[1], + triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, triRaster.col[2], + sprTexture, flags); + + } + + + + + nTriangleDrawnCount++; + } + } + } + + return nTriangleDrawnCount; + } + + void GFX3D::RasterTriangle(int x1, int y1, float u1, float v1, float w1, olc::Pixel c1, + int x2, int y2, float u2, float v2, float w2, olc::Pixel c2, + int x3, int y3, float u3, float v3, float w3, olc::Pixel c3, + olc::Sprite* spr, + uint32_t nFlags) + + { + if (y2 < y1) + { + std::swap(y1, y2); std::swap(x1, x2); std::swap(u1, u2); std::swap(v1, v2); std::swap(w1, w2); std::swap(c1, c2); + } + + if (y3 < y1) + { + std::swap(y1, y3); std::swap(x1, x3); std::swap(u1, u3); std::swap(v1, v3); std::swap(w1, w3); std::swap(c1, c3); + } + + if (y3 < y2) + { + std::swap(y2, y3); std::swap(x2, x3); std::swap(u2, u3); std::swap(v2, v3); std::swap(w2, w3); std::swap(c2, c3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + int dcr1 = c2.r - c1.r; + int dcg1 = c2.g - c1.g; + int dcb1 = c2.b - c1.b; + int dca1 = c2.a - c1.a; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + int dcr2 = c3.r - c1.r; + int dcg2 = c3.g - c1.g; + int dcb2 = c3.b - c1.b; + int dca2 = c3.a - c1.a; + + float tex_u, tex_v, tex_w; + float col_r, col_g, col_b, col_a; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0, + dcr1_step = 0, dcr2_step = 0, + dcg1_step = 0, dcg2_step = 0, + dcb1_step = 0, dcb2_step = 0, + dca1_step = 0, dca2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) dcr1_step = dcr1 / (float)abs(dy1); + if (dy1) dcg1_step = dcg1 / (float)abs(dy1); + if (dy1) dcb1_step = dcb1 / (float)abs(dy1); + if (dy1) dca1_step = dca1 / (float)abs(dy1); + + if (dy2) dcr2_step = dcr2 / (float)abs(dy2); + if (dy2) dcg2_step = dcg2 / (float)abs(dy2); + if (dy2) dcb2_step = dcb2 / (float)abs(dy2); + if (dy2) dca2_step = dca2 / (float)abs(dy2); + + float pixel_r = 0.0f; + float pixel_g = 0.0f; + float pixel_b = 0.0f; + float pixel_a = 1.0f; + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + float col_sr = c1.r + (float)(i - y1) * dcr1_step; + float col_sg = c1.g + (float)(i - y1) * dcg1_step; + float col_sb = c1.b + (float)(i - y1) * dcb1_step; + float col_sa = c1.a + (float)(i - y1) * dca1_step; + + float col_er = c1.r + (float)(i - y1) * dcr2_step; + float col_eg = c1.g + (float)(i - y1) * dcg2_step; + float col_eb = c1.b + (float)(i - y1) * dcb2_step; + float col_ea = c1.a + (float)(i - y1) * dca2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + std::swap(col_sr, col_er); + std::swap(col_sg, col_eg); + std::swap(col_sb, col_eb); + std::swap(col_sa, col_ea); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + col_r = col_sr; + col_g = col_sg; + col_b = col_sb; + col_a = col_sa; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + col_r = (1.0f - t) * col_sr + t * col_er; + col_g = (1.0f - t) * col_sg + t * col_eg; + col_b = (1.0f - t) * col_sb + t * col_eb; + col_a = (1.0f - t) * col_sa + t * col_ea; + + pixel_r = col_r; + pixel_g = col_g; + pixel_b = col_b; + pixel_a = col_a; + + if (nFlags & GFX3D::RENDER_TEXTURED) + { + if (spr != nullptr) + { + olc::Pixel sample = spr->Sample(tex_u / tex_w, tex_v / tex_w); + pixel_r *= sample.r / 255.0f; + pixel_g *= sample.g / 255.0f; + pixel_b *= sample.b / 255.0f; + pixel_a *= sample.a / 255.0f; + } + } + + if (nFlags & GFX3D::RENDER_DEPTH) + { + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + if (pge->Draw(j, i, olc::Pixel(pixel_r * 1.0f, pixel_g * 1.0f, pixel_b * 1.0f, pixel_a * 1.0f))) + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + else + { + pge->Draw(j, i, olc::Pixel(pixel_r * 1.0f, pixel_g * 1.0f, pixel_b * 1.0f, pixel_a * 1.0f)); + } + + t += tstep; + } + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + dcr1 = c3.r - c2.r; + dcg1 = c3.g - c2.g; + dcb1 = c3.b - c2.b; + dca1 = c3.a - c2.a; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0; dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + dcr1_step = 0; dcg1_step = 0; dcb1_step = 0; dca1_step = 0; + if (dy1) dcr1_step = dcr1 / (float)abs(dy1); + if (dy1) dcg1_step = dcg1 / (float)abs(dy1); + if (dy1) dcb1_step = dcb1 / (float)abs(dy1); + if (dy1) dca1_step = dca1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + float col_sr = c2.r + (float)(i - y2) * dcr1_step; + float col_sg = c2.g + (float)(i - y2) * dcg1_step; + float col_sb = c2.b + (float)(i - y2) * dcb1_step; + float col_sa = c2.a + (float)(i - y2) * dca1_step; + + float col_er = c1.r + (float)(i - y1) * dcr2_step; + float col_eg = c1.g + (float)(i - y1) * dcg2_step; + float col_eb = c1.b + (float)(i - y1) * dcb2_step; + float col_ea = c1.a + (float)(i - y1) * dca2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + std::swap(col_sr, col_er); + std::swap(col_sg, col_eg); + std::swap(col_sb, col_eb); + std::swap(col_sa, col_ea); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + col_r = col_sr; + col_g = col_sg; + col_b = col_sb; + col_a = col_sa; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + col_r = (1.0f - t) * col_sr + t * col_er; + col_g = (1.0f - t) * col_sg + t * col_eg; + col_b = (1.0f - t) * col_sb + t * col_eb; + col_a = (1.0f - t) * col_sa + t * col_ea; + + pixel_r = col_r; + pixel_g = col_g; + pixel_b = col_b; + pixel_a = col_a; + + if (nFlags & GFX3D::RENDER_TEXTURED) + { + if (spr != nullptr) + { + olc::Pixel sample = spr->Sample(tex_u / tex_w, tex_v / tex_w); + pixel_r *= sample.r / 255.0f; + pixel_g *= sample.g / 255.0f; + pixel_b *= sample.b / 255.0f; + pixel_a *= sample.a / 255.0f; + } + } + + if (nFlags & GFX3D::RENDER_DEPTH) + { + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + if (pge->Draw(j, i, olc::Pixel(pixel_r * 1.0f, pixel_g * 1.0f, pixel_b * 1.0f, pixel_a * 1.0f))) + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + else + { + pge->Draw(j, i, olc::Pixel(pixel_r * 1.0f, pixel_g * 1.0f, pixel_b * 1.0f, pixel_a * 1.0f)); + } + + t += tstep; + } + } + } + } + + + + //GFX3D::MipMap::MipMap() : olc::Sprite(){} + //GFX3D::MipMap::MipMap(std::string sImageFile) : olc::Sprite(sImageFile) + //{ + // GenerateMipLevels(); + //} + //GFX3D::MipMap::MipMap(std::string sImageFile, olc::ResourcePack *pack) : olc::Sprite(sImageFile, pack){} + //GFX3D::MipMap::MipMap(int32_t w, int32_t h) : olc::Sprite(w, h) {} + + //int GFX3D::MipMap::GenerateMipLevels() + //{ + // int nLevelsW = 0; + // int nLevelsH = 0; + // int w = width; + // int h = height; + // while (w > 1) { w >>= 1; nLevelsW++; } + // while (h > 1) { h >>= 1; nLevelsH++; } + + // int nLevels = std::min(nLevelsW, nLevelsH); + + // w = width >> 1; + // h = height >> 1; + + // vecMipMaps.emplace_back(w, h); // Level 0 + // memcpy(vecMipMaps[0].GetData(), GetData(), w*h*sizeof(uint32_t)); + + // for (int i = 1; i < nLevels; i++) + // { + // vecMipMaps.emplace_back(w, h); + // pge->SetDrawTarget(&vecMipMaps[i]); + // for (int x = 0; x < w; x++) + // for (int y = 0; y < h; y++) + // pge->Draw(x, y, vecMipMaps[i-1].SampleBL((float)x / (float)w, (float)y / (float)h)); + // w >>= 1; h >>= 1; + // } + + // pge->SetDrawTarget(nullptr); + // return nLevels; + //} + // + //olc::rcode GFX3D::MipMap::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + //{ + // olc::rcode r = olc::Sprite::LoadFromFile(sImageFile, pack); + // if (r == olc::FAIL) return r; + // GenerateMipLevels(); + // return r; + //} + + //olc::rcode GFX3D::MipMap::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + //{ + // olc::rcode r = olc::Sprite::LoadFromPGESprFile(sImageFile, pack); + // if (r == olc::FAIL) return r; + // GenerateMipLevels(); + // return r; + //} + // + //olc::Pixel GFX3D::MipMap::Sample(float x, float y, float z) + //{ + // int nLevel = (int)(z * (float)vecMipMaps.size()); + // return vecMipMaps[nLevel].Sample(x, y); + //} + + //olc::Pixel GFX3D::MipMap::SampleBL(float u, float v, float z); + +} + +#endif \ No newline at end of file diff --git a/Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp new file mode 100644 index 0000000..6ad6ab0 --- /dev/null +++ b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp @@ -0,0 +1,5 @@ +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#define OLC_PGEX_GRAPHICS3D +#include "olcPGEX_Graphics3D.h" diff --git a/Videos/CarCrimeCity/Part2/olcPixelGameEngine.h b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.h new file mode 100644 index 0000000..4d13e6d --- /dev/null +++ b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.h @@ -0,0 +1,2317 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.17 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprising and wonderful success for me, + and I'm delighted how people have reacted so positively towards it, so thanks + for that. + + However, there are limitations that I simply cannot avoid. Firstly, I need to + maintain several different versions of it to accommodate users on Windows7, + 8, 10, Linux, Mac, Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities and the effect + is becoming underwhelming. The engine itself is not slow at all, but the process + that Windows uses to draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours and glyphs. Sadly + I have no control over this, and recent videos that are extremely graphical + (for a command prompt :P ) have been dipping to unacceptable framerates. As + the channel has been popular with aspiring game developers, I'm concerned that + the visual appeal of the command prompt is perhaps limited to us oldies, and I + dont want to alienate younger learners. Finally, I'd like to demonstrate many + more algorithms and image processing that exist in the graphical domain, for + which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look and feel to the + programmer is almost identical, so all of my existing code from the videos is + easily portable, and the programmer uses this file in exactly the same way. But + I've decided that rather than just build a command prompt emulator, that I + would at least harness some modern(ish) portable technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is written in a + cross-platform style, uses modern(ish) C++ conventions and most importantly, + renders much much faster. I will use this version when my applications are + predominantly graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command prompt silliness to + come yet, but evolution is important!! + + 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 + 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 + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Rapberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + Thanks + ~~~~~~ + 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 40K YouTube followers, + 22 Patreons and 2.6K Discord server members who give me the motivation to keep + going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest + Marti Morta........Gris + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus + #if !defined _WIN32_WINNT + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //================================================================================== + + template + struct v2d_generic + { + T x = 0; + T y = 0; + + inline v2d_generic() : x(0), y(0) { } + inline v2d_generic(T _x, T _y) : x(_x), y(_y) { } + inline v2d_generic(const v2d_generic& v) : x(v.x), y(v.y){ } + inline T mag() { return sqrt(x * x + y * y); } + inline v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); } + inline v2d_generic perp() { return v2d_generic(-y, x); } + inline T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; } + inline T cross(const v2d_generic& rhs) { return this->x * rhs.y - this->y * rhs.x; } + inline v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y);} + inline v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y);} + inline v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + inline v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + inline v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + inline v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + 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 :( */ } + }; + + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + + typedef v2d_generic vi2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + //============================================================= + + + class ResourcePack + { + public: + 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)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + bool SetPixel(int32_t x, int32_t y, Pixel p); + + Pixel Sample(float x, float y); + Pixel SampleBL(float u, float v); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen = false); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + // Get Mouse Wheel Delta + int32_t GetMouseWheel(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + int32_t nMouseWheelDelta = 0; + int32_t nMousePosXcache = 0; + int32_t nMousePosYcache = 0; + int32_t nMouseWheelDeltaCache = 0; + int32_t nWindowWidth = 0; + int32_t nWindowHeight = 0; + int32_t nViewX = 0; + int32_t nViewY = 0; + int32_t nViewW = 0; + int32_t nViewH = 0; + bool bFullScreen = false; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + + +#ifdef _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 + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + If all else fails, create a file called "olcPixelGameEngine.cpp" with the following + two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying + about defining things. Dont forget to include that cpp file as part of your build! + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + + std::wstring ConvertS2W(std::string s) + { +#ifdef _WIN32 + 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); + std::wstring w(buffer); + delete[] buffer; + return w; +#else + return L"SVN FTW!"; +#endif + } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _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 + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y*width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + size_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(size_t)); + for (size_t i = 0; i < nMapEntries; i++) + { + size_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(size_t)); + + std::string sFileName(nFilePathSize, ' '); + for (size_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + bFullScreen = full_screen; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + 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 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::GetMouseWheel() + { + return nMouseWheelDelta; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) + { + pattern = (pattern << 1) | (pattern >> 31); + return pattern & 1; + }; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + if (mask & 0x01) Draw(x + x0, y - y0, p); + if (mask & 0x02) Draw(x + y0, y - x0, p); + if (mask & 0x04) Draw(x + y0, y + x0, p); + if (mask & 0x08) Draw(x + x0, y + y0, p); + if (mask & 0x10) Draw(x - x0, y + y0, p); + if (mask & 0x20) Draw(x - y0, y + x0, p); + if (mask & 0x40) Draw(x - y0, y - x0, p); + if (mask & 0x80) Draw(x - x0, y - y0, p); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = nScreenWidth * nPixelWidth; + int32_t wh = nScreenHeight * nPixelHeight; + float wasp = (float)ww / (float)wh; + + nViewW = (int32_t)nWindowWidth; + nViewH = (int32_t)((float)nViewW / wasp); + + if (nViewH > nWindowHeight) + { + nViewH = nWindowHeight; + nViewW = (int32_t)((float)nViewH * wasp); + } + + nViewX = (nWindowWidth - nViewW) / 2; + nViewY = (nWindowHeight - nViewH) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + nWindowWidth = x; + nWindowHeight = y; + olc_UpdateViewport(); + + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { + nMouseWheelDeltaCache += delta; + } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // 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; + } + + nMousePosXcache = (int32_t)(((float)x / (float)(nWindowWidth - (nViewX * 2)) * (float)nScreenWidth)); + nMousePosYcache = (int32_t)(((float)y / (float)(nWindowHeight - (nViewY * 2)) * (float)nScreenHeight)); + + if (nMousePosXcache >= (int32_t)nScreenWidth) + nMousePosXcache = nScreenWidth - 1; + if (nMousePosYcache >= (int32_t)nScreenHeight) + nMousePosYcache = nScreenHeight - 1; + + if (nMousePosXcache < 0) + nMousePosXcache = 0; + if (nMousePosYcache < 0) + nMousePosYcache = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // 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 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + glClear(GL_COLOR_BUFFER_BIT); // Thanks Benedani! + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + nWindowWidth = xce.width; + nWindowHeight = xce.height; + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + XKeyEvent *e = (XKeyEvent *)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + XKeyEvent *e = (XKeyEvent *)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = true; break; + case 2: pMouseNewState[2] = true; break; + case 3: pMouseNewState[1] = true; break; + case 4: olc_UpdateMouseWheel(120); break; + case 5: olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = false; break; + case 2: pMouseNewState[2] = false; break; + case 3: pMouseNewState[1] = false; break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + + // Cache mouse coordinates so they remain + // consistent during frame + nMousePosX = nMousePosXcache; + nMousePosY = nMousePosYcache; + + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + glViewport(nViewX, nViewY, nViewW, nViewH); + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + +#ifdef _WIN32 + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); + }; + } gdistartup; +#endif + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + nWindowWidth = (LONG)nScreenWidth * (LONG)nPixelWidth; + nWindowHeight = (LONG)nScreenHeight * (LONG)nPixelHeight; + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE;// | WS_THICKFRAME; + + int nCosmeticOffset = 30; + nViewW = nWindowWidth; + nViewH = nWindowHeight; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + nCosmeticOffset = 0; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return NULL; + nWindowWidth = mi.rcMonitor.right; + nWindowHeight = mi.rcMonitor.bottom; + + + } + + olc_UpdateViewport(); + + // Keep client size as requested + RECT rWndRect = { 0, 0, nWindowWidth, nWindowHeight }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + glViewport(nViewX, nViewY, nViewW, nViewH); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval) wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: + { + sge->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); + return 0; + } + case WM_MOUSEWHEEL: + { + sge->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; return 0; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + if (glSwapIntervalEXT) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + 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"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif diff --git a/Videos/DemoBinaries/OLC_8BitsImProc.zip b/Videos/DemoBinaries/OLC_8BitsImProc.zip new file mode 100644 index 0000000..cb9f6c5 Binary files /dev/null and b/Videos/DemoBinaries/OLC_8BitsImProc.zip differ diff --git a/Videos/OneLoneCoder_PGE_8BitsImProc.cpp b/Videos/OneLoneCoder_PGE_8BitsImProc.cpp new file mode 100644 index 0000000..3bd6ea7 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_8BitsImProc.cpp @@ -0,0 +1,504 @@ +/* + 8-Bits Of Image Processing You Should Know + "Colin, at least you'll always get 700s now..." - 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: + ~~~~~~~~~~~~~ + Choose algorithm 1-8, instructions on screen + + + Relevant Video: https://youtu.be/mRM5Js3VLCk + + 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 "escapi.h" + +int nFrameWidth = 320; +int nFrameHeight = 240; + +struct frame +{ + float *pixels = nullptr; + + frame() + { + pixels = new float[nFrameWidth * nFrameHeight]; + } + + ~frame() + { + delete[] pixels; + } + + + float get(int x, int y) + { + if (x >= 0 && x < nFrameWidth && y >= 0 && y < nFrameHeight) + { + return pixels[y*nFrameWidth + x]; + } + else + return 0.0f; + } + + void set(int x, int y, float p) + { + if (x >= 0 && x < nFrameWidth && y >= 0 && y < nFrameHeight) + { + pixels[y*nFrameWidth + x] = p; + } + } + + + void operator=(const frame &f) + { + memcpy(this->pixels, f.pixels, nFrameWidth * nFrameHeight * sizeof(float)); + } +}; + + + +class WIP_ImageProcessing : public olc::PixelGameEngine +{ +public: + WIP_ImageProcessing() + { + sAppName = "WIP_ImageProcessing"; + } + + union RGBint + { + int rgb; + unsigned char c[4]; + }; + + int nCameras = 0; + SimpleCapParams capture; + +public: + bool OnUserCreate() override + { + // Initialise webcam to screen dimensions + nCameras = setupESCAPI(); + if (nCameras == 0) return false; + capture.mWidth = nFrameWidth; + capture.mHeight = nFrameHeight; + capture.mTargetBuf = new int[nFrameWidth * nFrameHeight]; + if (initCapture(0, &capture) == 0) return false; + return true; + } + + void DrawFrame(frame &f, int x, int y) + { + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + int c = (int)std::min(std::max(0.0f, f.pixels[j*nFrameWidth + i] * 255.0f), 255.0f); + Draw(x + i, y + j, olc::Pixel(c, c, c)); + } + } + + enum ALGORITHM + { + THRESHOLD, MOTION, LOWPASS, CONVOLUTION, + SOBEL, MORPHO, MEDIAN, ADAPTIVE, + }; + + enum MORPHOP + { + DILATION, + EROSION, + EDGE + }; + + frame input, output, prev_input, activity, threshold; + + // Algorithm Currently Running + ALGORITHM algo = THRESHOLD; + MORPHOP morph = DILATION; + int nMorphCount = 1; + + float fThresholdValue = 0.5f; + float fLowPassRC = 0.1f; + float fAdaptiveBias = 1.1f; + + float *pConvoKernel = kernel_blur; + + float kernel_blur[9] = + { + 0.0f, 0.125, 0.0f, + 0.125f, 0.5f, 0.125f, + 0.0f, 0.125f, 0.0f, + }; + + float kernel_sharpen[9] = + { + 0.0f, -1.0f, 0.0f, + -1.0f, 5.0f, -1.0f, + 0.0f, -1.0f, 0.0f, + }; + + float kernel_sobel_v[9] = + { + -1.0f, 0.0f, +1.0f, + -2.0f, 0.0f, +2.0f, + -1.0f, 0.0f, +1.0f, + }; + + float kernel_sobel_h[9] = + { + -1.0f, -2.0f, -1.0f, + 0.0f, 0.0f, 0.0f, + +1.0f, +2.0f, +1.0f, + }; + + bool OnUserUpdate(float fElapsedTime) override + { + // CAPTURING WEBCAM IMAGE + prev_input = input; + doCapture(0); while (isCaptureDone(0) == 0) {} + for (int y = 0; y < capture.mHeight; y++) + for (int x = 0; x < capture.mWidth; x++) + { + RGBint col; + int id = y * capture.mWidth + x; + col.rgb = capture.mTargetBuf[id]; + input.pixels[y*nFrameWidth + x] = (float)col.c[1] / 255.0f; + } + + if (GetKey(olc::Key::K1).bReleased) algo = THRESHOLD; + if (GetKey(olc::Key::K2).bReleased) algo = MOTION; + if (GetKey(olc::Key::K3).bReleased) algo = LOWPASS; + if (GetKey(olc::Key::K4).bReleased) algo = CONVOLUTION; + if (GetKey(olc::Key::K5).bReleased) algo = SOBEL; + if (GetKey(olc::Key::K6).bReleased) algo = MORPHO; + if (GetKey(olc::Key::K7).bReleased) algo = MEDIAN; + if (GetKey(olc::Key::K8).bReleased) algo = ADAPTIVE; + + + switch (algo) + { + case THRESHOLD: + + // Respond to user input + if (GetKey(olc::Key::Z).bHeld) fThresholdValue -= 0.1f * fElapsedTime; + if (GetKey(olc::Key::X).bHeld) fThresholdValue += 0.1f * fElapsedTime; + if (fThresholdValue > 1.0f) fThresholdValue = 1.0f; + if (fThresholdValue < 0.0f) fThresholdValue = 0.0f; + + // Perform threshold per pixel + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + output.set(i, j, input.get(i, j) >= fThresholdValue ? 1.0f : 0.0f); + break; + + case MOTION: + + // Returns the absolute difference between successive frames per pixel + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + output.set(i, j, fabs(input.get(i, j) - prev_input.get(i, j))); + break; + + + case LOWPASS: + + // Respond to user input + if (GetKey(olc::Key::Z).bHeld) fLowPassRC -= 0.1f * fElapsedTime; + if (GetKey(olc::Key::X).bHeld) fLowPassRC += 0.1f * fElapsedTime; + if (fLowPassRC > 1.0f) fLowPassRC = 1.0f; + if (fLowPassRC < 0.0f) fLowPassRC = 0.0f; + + // Pass each pixel through a temporal RC filter + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + float dPixel = input.get(i, j) - output.get(i, j); + dPixel *= fLowPassRC; + output.set(i, j, dPixel + output.get(i, j)); + } + break; + + case CONVOLUTION: + // Respond to user input + if (GetKey(olc::Key::Z).bHeld) pConvoKernel = kernel_blur; + if (GetKey(olc::Key::X).bHeld) pConvoKernel = kernel_sharpen; + + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + float fSum = 0.0f; + for (int n = -1; n < +2; n++) + for (int m = -1; m < +2; m++) + fSum += input.get(i + n, j + m) * pConvoKernel[(m + 1) * 3 + (n + 1)]; + + output.set(i, j, fSum); + } + break; + + case SOBEL: + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + float fKernelSumH = 0.0f; + float fKernelSumV = 0.0f; + + for (int n = -1; n < +2; n++) + for (int m = -1; m < +2; m++) + { + fKernelSumH += input.get(i + n, j + m) * kernel_sobel_h[(m + 1) * 3 + (n + 1)]; + fKernelSumV += input.get(i + n, j + m) * kernel_sobel_v[(m + 1) * 3 + (n + 1)]; + } + + output.set(i, j, fabs((fKernelSumH + fKernelSumV) / 2.0f)); + } + break; + + case MORPHO: + + // Respond to user input + if (GetKey(olc::Key::Z).bHeld) morph = DILATION; + if (GetKey(olc::Key::X).bHeld) morph = EROSION; + if (GetKey(olc::Key::C).bHeld) morph = EDGE; + + if (GetKey(olc::Key::A).bReleased) nMorphCount--; + if (GetKey(olc::Key::S).bReleased) nMorphCount++; + if (nMorphCount > 10.0f) nMorphCount = 10.0f; + if (nMorphCount < 1.0f) nMorphCount = 1.0f; + + // Threshold First to binarise image + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + activity.set(i, j, input.get(i, j) > fThresholdValue ? 1.0f : 0.0f); + } + + threshold = activity; + + switch (morph) + { + case DILATION: + for (int n = 0; n < nMorphCount; n++) + { + output = activity; + + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + if (activity.get(i, j) == 1.0f) + { + output.set(i, j, 1.0f); + output.set(i - 1, j, 1.0f); + output.set(i + 1, j, 1.0f); + output.set(i, j - 1, 1.0f); + output.set(i, j + 1, 1.0f); + output.set(i - 1, j - 1, 1.0f); + output.set(i + 1, j + 1, 1.0f); + output.set(i + 1, j - 1, 1.0f); + output.set(i - 1, j + 1, 1.0f); + } + } + + activity = output; + } + break; + + case EROSION: + for (int n = 0; n < nMorphCount; n++) + { + output = activity; + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + + float sum = activity.get(i - 1, j) + activity.get(i + 1, j) + activity.get(i, j - 1) + activity.get(i, j + 1) + + activity.get(i - 1, j - 1) + activity.get(i + 1, j + 1) + activity.get(i + 1, j - 1) + activity.get(i - 1, j + 1); + + if (activity.get(i, j) == 1.0f && sum < 8.0f) + { + output.set(i, j, 0.0f); + } + } + activity = output; + } + break; + + case EDGE: + output = activity; + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + + float sum = activity.get(i - 1, j) + activity.get(i + 1, j) + activity.get(i, j - 1) + activity.get(i, j + 1) + + activity.get(i - 1, j - 1) + activity.get(i + 1, j + 1) + activity.get(i + 1, j - 1) + activity.get(i - 1, j + 1); + + if (activity.get(i, j) == 1.0f && sum == 8.0f) + { + output.set(i, j, 0.0f); + } + } + break; + + } + break; + + case MEDIAN: + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + std::vector v; + + for (int n = -2; n < +3; n++) + for (int m = -2; m < +3; m++) + v.push_back(input.get(i + n, j + m)); + + std::sort(v.begin(), v.end(), std::greater()); + output.set(i, j, v[12]); + } + break; + + case ADAPTIVE: + // Respond to user input + if (GetKey(olc::Key::Z).bHeld) fAdaptiveBias -= 0.1f * fElapsedTime; + if (GetKey(olc::Key::X).bHeld) fAdaptiveBias += 0.1f * fElapsedTime; + if (fAdaptiveBias > 1.5f) fAdaptiveBias = 1.5f; + if (fAdaptiveBias < 0.5f) fAdaptiveBias = 0.5f; + + + for (int i = 0; i < nFrameWidth; i++) + for (int j = 0; j < nFrameHeight; j++) + { + float fRegionSum = 0.0f; + + for (int n = -2; n < +3; n++) + for (int m = -2; m < +3; m++) + fRegionSum += input.get(i + n, j + m); + + fRegionSum /= 25.0f; + output.set(i, j, input.get(i, j) > (fRegionSum * fAdaptiveBias) ? 1.0f : 0.0f); + } + break; + } + + // DRAW STUFF ONLY HERE + Clear(olc::DARK_BLUE); + DrawFrame(algo == MORPHO ? threshold : input, 10, 10); + DrawFrame(output, 340, 10); + + DrawString(150, 255, "INPUT"); + DrawString(480, 255, "OUTPUT"); + + DrawString(10, 275, "1) Threshold"); + DrawString(10, 285, "2) Absolute Motion"); + DrawString(10, 295, "3) Low-Pass Temporal Filtering"); + DrawString(10, 305, "4) Convolution (Blurring/Sharpening)"); + DrawString(10, 315, "5) Sobel Edge Detection"); + DrawString(10, 325, "6) Binary Morphological Operations (Erosion/Dilation)"); + DrawString(10, 335, "7) Median Filter"); + DrawString(10, 345, "8) Adaptive Threshold"); + + + switch (algo) + { + case THRESHOLD: + DrawString(10, 375, "Change threshold value with Z and X keys"); + DrawString(10, 385, "Current value = " + std::to_string(fThresholdValue)); + break; + + case LOWPASS: + DrawString(10, 375, "Change RC constant value with Z and X keys"); + DrawString(10, 385, "Current value = " + std::to_string(fLowPassRC)); + break; + + case CONVOLUTION: + DrawString(10, 375, "Change convolution kernel with Z and X keys"); + DrawString(10, 385, "Current kernel = " + std::string((pConvoKernel == kernel_blur) ? "Blur" : "Sharpen")); + break; + + case MORPHO: + DrawString(10, 375, "Change operation with Z and X and C keys"); + if (morph == DILATION) DrawString(10, 385, "Current operation = DILATION"); + if (morph == EROSION) DrawString(10, 385, "Current operation = EROSION"); + if (morph == EDGE) DrawString(10, 385, "Current operation = EDGE"); + DrawString(10, 395, "Change Iterations with A and S keys"); + DrawString(10, 405, "Current iteration count = " + std::to_string(nMorphCount)); + + + break; + + case ADAPTIVE: + DrawString(10, 375, "Change adaptive threshold bias with Z and X keys"); + DrawString(10, 385, "Current value = " + std::to_string(fAdaptiveBias)); + break; + + default: + break; + } + + if (GetKey(olc::Key::ESCAPE).bPressed) return false; + return true; + } +}; + + +int main() +{ + WIP_ImageProcessing demo; + if (demo.Construct(670, 460, 2, 2)) + demo.Start(); + + return 0; +} + + diff --git a/Videos/OneLoneCoder_PGE_Balls2.cpp b/Videos/OneLoneCoder_PGE_Balls2.cpp new file mode 100644 index 0000000..c1e3d6f --- /dev/null +++ b/Videos/OneLoneCoder_PGE_Balls2.cpp @@ -0,0 +1,500 @@ +/* +OneLoneCoder.com - Programming Balls! #2 Circle Vs Edge Collisions +"...totally overkill for pong..." - @Javidx9 + + +Background +~~~~~~~~~~ +Collision detection engines can get quite complicated. This program shows the interactions +between circular objects of different sizes and masses. Use Left mouse button to select +and drag a ball to examin static collisions, and use Right mouse button to apply velocity +to the balls as if using a pool/snooker/billiards cue. + +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2018 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above +copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce +the above copyright notice. This list of conditions and the following +disclaimer must be reproduced in the documentation and/or other +materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Links +~~~~~ +YouTube: https://www.youtube.com/javidx9 +Discord: https://discord.gg/WhwHUMV +Twitter: https://www.twitter.com/javidx9 +Twitch: https://www.twitch.tv/javidx9 +GitHub: https://www.github.com/onelonecoder +Homepage: https://www.onelonecoder.com + +Relevant Videos +~~~~~~~~~~~~~~~ +Part #1 https://youtu.be/LPzyNOHY3A4 +Part #2 https://youtu.be/ebq7L2Wtbl4 + +Author +~~~~~~ +David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + + +#include +#include +#include +using namespace std; + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +struct sBall +{ + float px, py; + float vx, vy; + float ax, ay; + float ox, oy; + float radius; + float mass; + float friction; + int score; + int id; + float fSimTimeRemaining; + olc::Pixel col; +}; + +struct sLineSegment +{ + float sx, sy; + float ex, ey; + float radius; +}; + + + +class CirclePhysics : public olc::PixelGameEngine +{ +public: + CirclePhysics() + { + sAppName = "Circles V Edges"; + } + +private: + vector vecBalls; + vector vecLines; + vector> modelCircle; + sBall* pSelectedBall = nullptr; + olc::Sprite *spriteBalls = nullptr; + sLineSegment* pSelectedLine = nullptr; + bool bSelectedLineStart = false; + + void AddBall(float x, float y, float r = 5.0f, int s = 0) + { + sBall b; + b.px = x; b.py = y; + b.vx = 0; b.vy = 0; + b.ax = 0; b.ay = 0; + b.ox = 0; b.oy = 0; + b.radius = r; + b.mass = r * 10.0f; + b.friction = 0.0f; + b.score = s; + b.fSimTimeRemaining = 0.0f; + b.id = vecBalls.size(); + b.col = olc::Pixel(rand() % 200 + 55, rand() % 200 + 55, rand() % 200 + 55); + vecBalls.emplace_back(b); + } + + olc::Sprite spr; + +public: + bool OnUserCreate() + { + + float fBallRadius = 4.0f; + for (int i = 0; i <100; i++) + AddBall(((float)rand()/(float)RAND_MAX) * ScreenWidth(), ((float)rand() / (float)RAND_MAX) * ScreenHeight(), fBallRadius); + + AddBall(28.0f, 33.0, fBallRadius * 3); + AddBall(28.0f, 35.0, fBallRadius * 2); + + float fLineRadius = 4.0f; + vecLines.push_back({ 12.0f, 4.0f, 64.0f, 4.0f, fLineRadius }); + vecLines.push_back({ 76.0f, 4.0f, 132.0f, 4.0f, fLineRadius }); + vecLines.push_back({ 12.0f, 68.0f, 64.0f, 68.0f, fLineRadius }); + vecLines.push_back({ 76.0f, 68.0f, 132.0f, 68.0f, fLineRadius }); + vecLines.push_back({ 4.0f, 12.0f, 4.0f, 60.0f, fLineRadius }); + vecLines.push_back({ 140.0f, 12.0f, 140.0f, 60.0f, fLineRadius }); + return true; + } + + bool OnUserUpdate(float fElapsedTime) + { + + auto DoCirclesOverlap = [](float x1, float y1, float r1, float x2, float y2, float r2) + { + return fabs((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= ((r1 + r2) * (r1 + r2)); + }; + + auto IsPointInCircle = [](float x1, float y1, float r1, float px, float py) + { + return fabs((x1 - px)*(x1 - px) + (y1 - py)*(y1 - py)) < (r1 * r1); + }; + + if (GetMouse(0).bPressed) + { + // Check for selected ball + pSelectedBall = nullptr; + for (auto &ball : vecBalls) + { + if (IsPointInCircle(ball.px, ball.py, ball.radius, GetMouseX(), GetMouseY())) + { + pSelectedBall = &ball; + break; + } + } + + // Check for selected line segment end + pSelectedLine = nullptr; + for (auto &line : vecLines) + { + if (IsPointInCircle(line.sx, line.sy, line.radius, GetMouseX(), GetMouseY())) + { + pSelectedLine = &line; + bSelectedLineStart = true; + break; + } + + if (IsPointInCircle(line.ex, line.ey, line.radius, GetMouseX(), GetMouseY())) + { + pSelectedLine = &line; + bSelectedLineStart = false; + break; + } + } + } + + if (GetMouse(0).bHeld) + { + if (pSelectedLine != nullptr) + { + if (bSelectedLineStart) + { + pSelectedLine->sx = GetMouseX(); + pSelectedLine->sy = GetMouseY(); + } + else + { + pSelectedLine->ex = GetMouseX(); + pSelectedLine->ey = GetMouseY(); + } + } + } + + if (GetMouse(0).bReleased) + { + if (pSelectedBall != nullptr) + { + // Apply velocity + pSelectedBall->vx = 5.0f * ((pSelectedBall->px) - GetMouseX()); + pSelectedBall->vy = 5.0f * ((pSelectedBall->py) - GetMouseY()); + } + + pSelectedBall = nullptr; + pSelectedLine = nullptr; + } + + if (GetMouse(1).bHeld) + { + for (auto &ball : vecBalls) + { + ball.vx += (GetMouseX() - ball.px) * 0.01f; + ball.vy += (GetMouseY() - ball.py) * 0.01f; + } + } + + + + vector> vecCollidingPairs; + vector vecFakeBalls; + + // Threshold indicating stability of object + float fStable = 0.005f; + + // Multiple simulation updates with small time steps permit more accurate physics + // and realistic results at the expense of CPU time of course + int nSimulationUpdates = 4; + + // Multiple collision trees require more steps to resolve. Normally we would + // continue simulation until the object has no simulation time left for this + // epoch, however this is risky as the system may never find stability, so we + // can clamp it here + int nMaxSimulationSteps = 15; + + // Break up the frame elapsed time into smaller deltas for each simulation update + float fSimElapsedTime = fElapsedTime / (float)nSimulationUpdates; + + // Main simulation loop + for (int i = 0; i < nSimulationUpdates; i++) + { + // Set all balls time to maximum for this epoch + for (auto &ball : vecBalls) + ball.fSimTimeRemaining = fSimElapsedTime; + + // Erode simulation time on a per objec tbasis, depending upon what happens + // to it during its journey through this epoch + for (int j = 0; j < nMaxSimulationSteps; j++) + { + // Update Ball Positions + for (auto &ball : vecBalls) + { + if (ball.fSimTimeRemaining > 0.0f) + { + ball.ox = ball.px; // Store original position this epoch + ball.oy = ball.py; + + ball.ax = -ball.vx * 0.8f; // Apply drag and gravity + ball.ay = -ball.vy * 0.8f + 100.0f; + + ball.vx += ball.ax * ball.fSimTimeRemaining; // Update Velocity + ball.vy += ball.ay * ball.fSimTimeRemaining; + + ball.px += ball.vx * ball.fSimTimeRemaining; // Update position + ball.py += ball.vy * ball.fSimTimeRemaining; + + // Crudely wrap balls to screen - note this cause issues when collisions occur on screen boundaries + if (ball.px < 0) ball.px += (float)ScreenWidth(); + if (ball.px >= ScreenWidth()) ball.px -= (float)ScreenWidth(); + if (ball.py < 0) ball.py += (float)ScreenHeight(); + if (ball.py >= ScreenHeight()) ball.py -= (float)ScreenHeight(); + + // Stop ball when velocity is neglible + if (fabs(ball.vx*ball.vx + ball.vy*ball.vy) < fStable) + { + ball.vx = 0; + ball.vy = 0; + } + } + } + + + // Work out static collisions with walls and displace balls so no overlaps + for (auto &ball : vecBalls) + { + float fDeltaTime = ball.fSimTimeRemaining; + + // Against Edges + for (auto &edge : vecLines) + { + // Check that line formed by velocity vector, intersects with line segment + float fLineX1 = edge.ex - edge.sx; + float fLineY1 = edge.ey - edge.sy; + + float fLineX2 = ball.px - edge.sx; + float fLineY2 = ball.py - edge.sy; + + float fEdgeLength = fLineX1 * fLineX1 + fLineY1 * fLineY1; + + // This is nifty - It uses the DP of the line segment vs the line to the object, to work out + // how much of the segment is in the "shadow" of the object vector. The min and max clamp + // this to lie between 0 and the line segment length, which is then normalised. We can + // use this to calculate the closest point on the line segment + float t = std::max(0.0f, std::min(fEdgeLength, (fLineX1 * fLineX2 + fLineY1 * fLineY2))) / fEdgeLength; + + // Which we do here + float fClosestPointX = edge.sx + t * fLineX1; + float fClosestPointY = edge.sy + t * fLineY1; + + // And once we know the closest point, we can check if the ball has collided with the segment in the + // same way we check if two balls have collided + float fDistance = sqrtf((ball.px - fClosestPointX)*(ball.px - fClosestPointX) + (ball.py - fClosestPointY)*(ball.py - fClosestPointY)); + + if (fDistance <= (ball.radius + edge.radius)) + { + // Collision has occurred - treat collision point as a ball that cannot move. To make this + // compatible with the dynamic resolution code below, we add a fake ball with an infinite mass + // so it behaves like a solid object when the momentum calculations are performed + sBall *fakeball = new sBall(); + fakeball->radius = edge.radius; + fakeball->mass = ball.mass * 0.8f; + fakeball->px = fClosestPointX; + fakeball->py = fClosestPointY; + fakeball->vx = -ball.vx; // We will use these later to allow the lines to impart energy into ball + fakeball->vy = -ball.vy; // if the lines are moving, i.e. like pinball flippers + + // Store Fake Ball + vecFakeBalls.push_back(fakeball); + + // Add collision to vector of collisions for dynamic resolution + vecCollidingPairs.push_back({ &ball, fakeball }); + + // Calculate displacement required + float fOverlap = 1.0f * (fDistance - ball.radius - fakeball->radius); + + // Displace Current Ball away from collision + ball.px -= fOverlap * (ball.px - fakeball->px) / fDistance; + ball.py -= fOverlap * (ball.py - fakeball->py) / fDistance; + } + } + + // Against other balls + for (auto &target : vecBalls) + { + if (ball.id != target.id) // Do not check against self + { + if (DoCirclesOverlap(ball.px, ball.py, ball.radius, target.px, target.py, target.radius)) + { + // Collision has occured + vecCollidingPairs.push_back({ &ball, &target }); + + // Distance between ball centers + float fDistance = sqrtf((ball.px - target.px)*(ball.px - target.px) + (ball.py - target.py)*(ball.py - target.py)); + + // Calculate displacement required + float fOverlap = 0.5f * (fDistance - ball.radius - target.radius); + + // Displace Current Ball away from collision + ball.px -= fOverlap * (ball.px - target.px) / fDistance; + ball.py -= fOverlap * (ball.py - target.py) / fDistance; + + // Displace Target Ball away from collision - Note, this should affect the timing of the target ball + // and it does, but this is absorbed by the target ball calculating its own time delta later on + target.px += fOverlap * (ball.px - target.px) / fDistance; + target.py += fOverlap * (ball.py - target.py) / fDistance; + } + } + } + + // Time displacement - we knew the velocity of the ball, so we can estimate the distance it should have covered + // however due to collisions it could not do the full distance, so we look at the actual distance to the collision + // point and calculate how much time that journey would have taken using the speed of the object. Therefore + // we can now work out how much time remains in that timestep. + float fIntendedSpeed = sqrtf(ball.vx * ball.vx + ball.vy * ball.vy); + float fIntendedDistance = fIntendedSpeed * ball.fSimTimeRemaining; + float fActualDistance = sqrtf((ball.px - ball.ox)*(ball.px - ball.ox) + (ball.py - ball.oy)*(ball.py - ball.oy)); + float fActualTime = fActualDistance / fIntendedSpeed; + + // After static resolution, there may be some time still left for this epoch, so allow simulation to continue + ball.fSimTimeRemaining = ball.fSimTimeRemaining - fActualTime; + } + + // Now work out dynamic collisions + float fEfficiency = 1.00f; + for (auto c : vecCollidingPairs) + { + sBall *b1 = c.first, *b2 = c.second; + + // Distance between balls + float fDistance = sqrtf((b1->px - b2->px)*(b1->px - b2->px) + (b1->py - b2->py)*(b1->py - b2->py)); + + // Normal + float nx = (b2->px - b1->px) / fDistance; + float ny = (b2->py - b1->py) / fDistance; + + // Tangent + float tx = -ny; + float ty = nx; + + // Dot Product Tangent + float dpTan1 = b1->vx * tx + b1->vy * ty; + float dpTan2 = b2->vx * tx + b2->vy * ty; + + // Dot Product Normal + float dpNorm1 = b1->vx * nx + b1->vy * ny; + float dpNorm2 = b2->vx * nx + b2->vy * ny; + + // Conservation of momentum in 1D + float m1 = fEfficiency * (dpNorm1 * (b1->mass - b2->mass) + 2.0f * b2->mass * dpNorm2) / (b1->mass + b2->mass); + float m2 = fEfficiency * (dpNorm2 * (b2->mass - b1->mass) + 2.0f * b1->mass * dpNorm1) / (b1->mass + b2->mass); + + // Update ball velocities + b1->vx = tx * dpTan1 + nx * m1; + b1->vy = ty * dpTan1 + ny * m1; + b2->vx = tx * dpTan2 + nx * m2; + b2->vy = ty * dpTan2 + ny * m2; + } + + // Remove collisions + vecCollidingPairs.clear(); + + // Remove fake balls + for (auto &b : vecFakeBalls) delete b; + vecFakeBalls.clear(); + } + } + + // Clear Screen + FillRect(0, 0, ScreenWidth(), ScreenHeight(), olc::Pixel(0, 0, 0)); + + // Draw Lines + for (auto line : vecLines) + { + FillCircle(line.sx, line.sy, line.radius, olc::Pixel(255,255,255)); + FillCircle(line.ex, line.ey, line.radius, olc::Pixel(128, 128, 128)); + + float nx = -(line.ey - line.sy); + float ny = (line.ex - line.sx); + float d = sqrt(nx*nx + ny * ny); + nx /= d; + ny /= d; + + DrawLine((line.sx + nx * line.radius), (line.sy + ny * line.radius), (line.ex + nx * line.radius), (line.ey + ny * line.radius), olc::Pixel(255, 255, 255)); + DrawLine((line.sx - nx * line.radius), (line.sy - ny * line.radius), (line.ex - nx * line.radius), (line.ey - ny * line.radius), olc::Pixel(255, 255, 255)); + } + + // Draw Balls + for (auto ball : vecBalls) + { + FillCircle(ball.px, ball.py, ball.radius, ball.col); + + } + + // Draw Cue + if (pSelectedBall != nullptr) + DrawLine(pSelectedBall->px, pSelectedBall->py, GetMouseX(), GetMouseY(), olc::Pixel(0, 0, 255)); + + + + + return true; + } + +}; + + +int main() +{ + + CirclePhysics game; + if (game.Construct(320, 240, 4, 4)) + game.Start(); + else + wcout << L"Could not construct console" << endl; + + return 0; +}; + diff --git a/Videos/OneLoneCoder_PGE_DungeonWarping.cpp b/Videos/OneLoneCoder_PGE_DungeonWarping.cpp new file mode 100644 index 0000000..61d1314 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_DungeonWarping.cpp @@ -0,0 +1,431 @@ +/* + Dungeon Warping via Orthographic Projections + "For my Mother-In-Law, you will be missed..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2020 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. + + Relevant Video: https://youtu.be/Ql5VZGkL23o + + 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 + + Community Blog: https://community.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +/* + + NOTE! This program requires a tile spritesheet NOT + provided in this github. You only need a few tiles, + see video for details. + +*/ + +class olcDungeon : public olc::PixelGameEngine +{ +public: + olcDungeon() + { + sAppName = "Dungeon Explorer"; + } + + struct Renderable + { + Renderable() {} + + void Load(const std::string& sFile) + { + sprite = new olc::Sprite(sFile); + decal = new olc::Decal(sprite); + } + + ~Renderable() + { + delete decal; + delete sprite; + } + + olc::Sprite* sprite = nullptr; + olc::Decal* decal = nullptr; + }; + + struct vec3d + { + float x, y, z; + }; + + struct sQuad + { + vec3d points[4]; + olc::vf2d tile; + }; + + struct sCell + { + bool wall = false; + olc::vi2d id[6]{ }; + }; + + class World + { + public: + World() + { + + } + + void Create(int w, int h) + { + size = { w, h }; + vCells.resize(w * h); + } + + sCell& GetCell(const olc::vi2d& v) + { + if (v.x >= 0 && v.x < size.x && v.y >= 0 && v.y < size.y) + return vCells[v.y * size.x + v.x]; + else + return NullCell; + } + + public: + olc::vi2d size; + + private: + std::vector vCells; + sCell NullCell; + }; + + World world; + Renderable rendSelect; + Renderable rendAllWalls; + + olc::vf2d vCameraPos = { 0.0f, 0.0f }; + float fCameraAngle = 0.0f; + float fCameraAngleTarget = fCameraAngle; + float fCameraPitch = 5.5f; + float fCameraZoom = 16.0f; + + bool bVisible[6]; + + olc::vi2d vCursor = { 0, 0 }; + olc::vi2d vTileCursor = { 0,0 }; + olc::vi2d vTileSize = { 32, 32 }; + + enum Face + { + Floor = 0, + North = 1, + East = 2, + South = 3, + West = 4, + Top = 5 + }; + +public: + bool OnUserCreate() override + { + rendSelect.Load("./gfx/dng_select.png"); + rendAllWalls.Load("./gfx/oldDungeon.png"); + + world.Create(64, 64); + + for (int y=0; y CreateCube(const olc::vi2d& vCell, const float fAngle, const float fPitch, const float fScale, const vec3d& vCamera) + { + // Unit Cube + std::array unitCube, rotCube, worldCube, projCube; + unitCube[0] = { 0.0f, 0.0f, 0.0f }; + unitCube[1] = { fScale, 0.0f, 0.0f }; + unitCube[2] = { fScale, -fScale, 0.0f }; + unitCube[3] = { 0.0f, -fScale, 0.0f }; + unitCube[4] = { 0.0f, 0.0f, fScale }; + unitCube[5] = { fScale, 0.0f, fScale }; + unitCube[6] = { fScale, -fScale, fScale }; + unitCube[7] = { 0.0f, -fScale, fScale }; + + // Translate Cube in X-Z Plane + for (int i = 0; i < 8; i++) + { + unitCube[i].x += (vCell.x * fScale - vCamera.x); + unitCube[i].y += -vCamera.y; + unitCube[i].z += (vCell.y * fScale - vCamera.z); + } + + // Rotate Cube in Y-Axis around origin + float s = sin(fAngle); + float c = cos(fAngle); + for (int i = 0; i < 8; i++) + { + rotCube[i].x = unitCube[i].x * c + unitCube[i].z * s; + rotCube[i].y = unitCube[i].y; + rotCube[i].z = unitCube[i].x * -s + unitCube[i].z * c; + } + + // Rotate Cube in X-Axis around origin (tilt slighly overhead) + s = sin(fPitch); + c = cos(fPitch); + for (int i = 0; i < 8; i++) + { + worldCube[i].x = rotCube[i].x; + worldCube[i].y = rotCube[i].y * c - rotCube[i].z * s; + worldCube[i].z = rotCube[i].y * s + rotCube[i].z * c; + } + + // Project Cube Orthographically - Unit Cube Viewport + //float fLeft = -ScreenWidth() * 0.5f; + //float fRight = ScreenWidth() * 0.5f; + //float fTop = ScreenHeight() * 0.5f; + //float fBottom = -ScreenHeight() * 0.5f; + //float fNear = 0.1f; + //float fFar = 100.0f;*/ + //for (int i = 0; i < 8; i++) + //{ + // projCube[i].x = (2.0f / (fRight - fLeft)) * worldCube[i].x - ((fRight + fLeft) / (fRight - fLeft)); + // projCube[i].y = (2.0f / (fTop - fBottom)) * worldCube[i].y - ((fTop + fBottom) / (fTop - fBottom)); + // projCube[i].z = (2.0f / (fFar - fNear)) * worldCube[i].z - ((fFar + fNear) / (fFar - fNear)); + // projCube[i].x *= -fRight; + // projCube[i].y *= -fTop; + // projCube[i].x += fRight; + // projCube[i].y += fTop; + //} + + // Project Cube Orthographically - Full Screen Centered + for (int i = 0; i < 8; i++) + { + projCube[i].x = worldCube[i].x + ScreenWidth() * 0.5f; + projCube[i].y = worldCube[i].y + ScreenHeight() * 0.5f; + projCube[i].z = worldCube[i].z; + } + + return projCube; + } + + + + void CalculateVisibleFaces(std::array& cube) + { + auto CheckNormal = [&](int v1, int v2, int v3) + { + olc::vf2d a = { cube[v1].x, cube[v1].y }; + olc::vf2d b = { cube[v2].x, cube[v2].y }; + olc::vf2d c = { cube[v3].x, cube[v3].y }; + return (b - a).cross(c - a) > 0; + }; + + bVisible[Face::Floor] = CheckNormal(4, 0, 1); + bVisible[Face::South] = CheckNormal(3, 0, 1); + bVisible[Face::North] = CheckNormal(6, 5, 4); + bVisible[Face::East] = CheckNormal(7, 4, 0); + bVisible[Face::West] = CheckNormal(2, 1, 5); + bVisible[Face::Top] = CheckNormal(7, 3, 2); + } + + void GetFaceQuads(const olc::vi2d& vCell, const float fAngle, const float fPitch, const float fScale, const vec3d& vCamera, std::vector &render) + { + std::array projCube = CreateCube(vCell, fAngle, fPitch, fScale, vCamera); + + auto& cell = world.GetCell(vCell); + + auto MakeFace = [&](int v1, int v2, int v3, int v4, Face f) + { + render.push_back({ projCube[v1], projCube[v2], projCube[v3], projCube[v4], cell.id[f] }); + }; + + if (!cell.wall) + { + if(bVisible[Face::Floor]) MakeFace(4, 0, 1, 5, Face::Floor); + } + else + { + if (bVisible[Face::South]) MakeFace(3, 0, 1, 2, Face::South); + if (bVisible[Face::North]) MakeFace(6, 5, 4, 7, Face::North); + if (bVisible[Face::East]) MakeFace(7, 4, 0, 3, Face::East); + if (bVisible[Face::West]) MakeFace(2, 1, 5, 6, Face::West); + if (bVisible[Face::Top]) MakeFace(7, 3, 2, 6, Face::Top); + } + } + + + bool OnUserUpdate(float fElapsedTime) override + { + // Grab mouse for convenience + olc::vi2d vMouse = { GetMouseX(), GetMouseY() }; + + // Edit mode - Selection from tile sprite sheet + if (GetKey(olc::Key::TAB).bHeld) + { + DrawSprite({ 0, 0 }, rendAllWalls.sprite); + DrawRect(vTileCursor * vTileSize, vTileSize); + if (GetMouse(0).bPressed) vTileCursor = vMouse / vTileSize; + return true; + } + + // WS keys to tilt camera + if (GetKey(olc::Key::W).bHeld) fCameraPitch += 1.0f * fElapsedTime; + if (GetKey(olc::Key::S).bHeld) fCameraPitch -= 1.0f * fElapsedTime; + + // DA Keys to manually rotate camera + if (GetKey(olc::Key::D).bHeld) fCameraAngleTarget += 1.0f * fElapsedTime; + if (GetKey(olc::Key::A).bHeld) fCameraAngleTarget -= 1.0f * fElapsedTime; + + // QZ Keys to zoom in or out + if (GetKey(olc::Key::Q).bHeld) fCameraZoom += 5.0f * fElapsedTime; + if (GetKey(olc::Key::Z).bHeld) fCameraZoom -= 5.0f * fElapsedTime; + + // Numpad keys used to rotate camera to fixed angles + if (GetKey(olc::Key::NP2).bPressed) fCameraAngleTarget = 3.14159f * 0.0f; + if (GetKey(olc::Key::NP1).bPressed) fCameraAngleTarget = 3.14159f * 0.25f; + if (GetKey(olc::Key::NP4).bPressed) fCameraAngleTarget = 3.14159f * 0.5f; + if (GetKey(olc::Key::NP7).bPressed) fCameraAngleTarget = 3.14159f * 0.75f; + if (GetKey(olc::Key::NP8).bPressed) fCameraAngleTarget = 3.14159f * 1.0f; + if (GetKey(olc::Key::NP9).bPressed) fCameraAngleTarget = 3.14159f * 1.25f; + if (GetKey(olc::Key::NP6).bPressed) fCameraAngleTarget = 3.14159f * 1.5f; + if (GetKey(olc::Key::NP3).bPressed) fCameraAngleTarget = 3.14159f * 1.75f; + + // Numeric keys apply selected tile to specific face + if (GetKey(olc::Key::K1).bPressed) world.GetCell(vCursor).id[Face::North] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K2).bPressed) world.GetCell(vCursor).id[Face::East] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K3).bPressed) world.GetCell(vCursor).id[Face::South] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K4).bPressed) world.GetCell(vCursor).id[Face::West] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K5).bPressed) world.GetCell(vCursor).id[Face::Floor] = vTileCursor * vTileSize; + if (GetKey(olc::Key::K6).bPressed) world.GetCell(vCursor).id[Face::Top] = vTileCursor * vTileSize; + + // Smooth camera + fCameraAngle += (fCameraAngleTarget - fCameraAngle) * 10.0f * fElapsedTime; + + // Arrow keys to move the selection cursor around map (boundary checked) + if (GetKey(olc::Key::LEFT).bPressed) vCursor.x--; + if (GetKey(olc::Key::RIGHT).bPressed) vCursor.x++; + if (GetKey(olc::Key::UP).bPressed) vCursor.y--; + if (GetKey(olc::Key::DOWN).bPressed) vCursor.y++; + if (vCursor.x < 0) vCursor.x = 0; + if (vCursor.y < 0) vCursor.y = 0; + if (vCursor.x >= world.size.x) vCursor.x = world.size.x - 1; + if (vCursor.y >= world.size.y) vCursor.y = world.size.y - 1; + + // Place block with space + if (GetKey(olc::Key::SPACE).bPressed) + { + world.GetCell(vCursor).wall = !world.GetCell(vCursor).wall; + } + + // Position camera in world + vCameraPos = { vCursor.x + 0.5f, vCursor.y + 0.5f }; + vCameraPos *= fCameraZoom; + + // Rendering + + // 1) Create dummy cube to extract visible face information + // Cull faces that cannot be seen + std::array cullCube = CreateCube({ 0, 0 }, fCameraAngle, fCameraPitch, fCameraZoom, { vCameraPos.x, 0.0f, vCameraPos.y }); + CalculateVisibleFaces(cullCube); + + // 2) Get all visible sides of all visible "tile cubes" + std::vector vQuads; + for(int y = 0; y 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; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_IsometricTiles.cpp b/Videos/OneLoneCoder_PGE_IsometricTiles.cpp new file mode 100644 index 0000000..4854ef3 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_IsometricTiles.cpp @@ -0,0 +1,214 @@ +/* + Coding Quickie: Isometric Tiles + "Owww... My insides hurt :(" - 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. + + Relevant Video: https://youtu.be/ukkbNKTgf5U + + 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" + +// Override base class with your custom functionality +class IsometricDemo : public olc::PixelGameEngine +{ +public: + IsometricDemo() + { + sAppName = "Coding Quickie: Isometric Tiles"; + } + +private: + // Number of tiles in world + olc::vi2d vWorldSize = { 14, 10 }; + + // Size of single tile graphic + olc::vi2d vTileSize = { 40, 20 }; + + // Where to place tile (0,0) on screen (in tile size steps) + olc::vi2d vOrigin = { 5, 1 }; + + // Sprite that holds all imagery + olc::Sprite *sprIsom = nullptr; + + // Pointer to create 2D world array + int *pWorld = nullptr; + +public: + bool OnUserCreate() override + { + // Load sprites used in demonstration + sprIsom = new olc::Sprite("isometric_demo.png"); + + // Create empty world + pWorld = new int[vWorldSize.x * vWorldSize.y]{ 0 }; + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + Clear(olc::WHITE); + + // Get Mouse in world + olc::vi2d vMouse = { GetMouseX(), GetMouseY() }; + + // Work out active cell + olc::vi2d vCell = { vMouse.x / vTileSize.x, vMouse.y / vTileSize.y }; + + // Work out mouse offset into cell + olc::vi2d vOffset = { vMouse.x % vTileSize.x, vMouse.y % vTileSize.y }; + + // Sample into cell offset colour + olc::Pixel col = sprIsom->GetPixel(3 * vTileSize.x + vOffset.x, vOffset.y); + + // Work out selected cell by transforming screen cell + olc::vi2d vSelected = + { + (vCell.y - vOrigin.y) + (vCell.x - vOrigin.x), + (vCell.y - vOrigin.y) - (vCell.x - vOrigin.x) + }; + + // "Bodge" selected cell by sampling corners + if (col == olc::RED) vSelected += {-1, +0}; + if (col == olc::BLUE) vSelected += {+0, -1}; + if (col == olc::GREEN) vSelected += {+0, +1}; + if (col == olc::YELLOW) vSelected += {+1, +0}; + + // Handle mouse click to toggle if a tile is visible or not + if (GetMouse(0).bPressed) + { + // Guard array boundary + if (vSelected.x >= 0 && vSelected.x < vWorldSize.x && vSelected.y >= 0 && vSelected.y < vWorldSize.y) + ++pWorld[vSelected.y * vWorldSize.x + vSelected.x] %= 6; + } + + // Labmda function to convert "world" coordinate into screen space + auto ToScreen = [&](int x, int y) + { + return olc::vi2d + { + (vOrigin.x * vTileSize.x) + (x - y) * (vTileSize.x / 2), + (vOrigin.y * vTileSize.y) + (x + y) * (vTileSize.y / 2) + }; + }; + + // Draw World - has binary transparancy so enable masking + SetPixelMode(olc::Pixel::MASK); + + // (0,0) is at top, defined by vOrigin, so draw from top to bottom + // to ensure tiles closest to camera are drawn last + for (int y = 0; y < vWorldSize.y; y++) + { + for (int x = 0; x < vWorldSize.x; x++) + { + // Convert cell coordinate to world space + olc::vi2d vWorld = ToScreen(x, y); + + switch(pWorld[y*vWorldSize.x + x]) + { + case 0: + // Invisble Tile + DrawPartialSprite(vWorld.x, vWorld.y, sprIsom, 1 * vTileSize.x, 0, vTileSize.x, vTileSize.y); + break; + case 1: + // Visible Tile + DrawPartialSprite(vWorld.x, vWorld.y, sprIsom, 2 * vTileSize.x, 0, vTileSize.x, vTileSize.y); + break; + case 2: + // Tree + DrawPartialSprite(vWorld.x, vWorld.y - vTileSize.y, sprIsom, 0 * vTileSize.x, 1 * vTileSize.y, vTileSize.x, vTileSize.y * 2); + break; + case 3: + // Spooky Tree + DrawPartialSprite(vWorld.x, vWorld.y - vTileSize.y, sprIsom, 1 * vTileSize.x, 1 * vTileSize.y, vTileSize.x, vTileSize.y * 2); + break; + case 4: + // Beach + DrawPartialSprite(vWorld.x, vWorld.y - vTileSize.y, sprIsom, 2 * vTileSize.x, 1 * vTileSize.y, vTileSize.x, vTileSize.y * 2); + break; + case 5: + // Water + DrawPartialSprite(vWorld.x, vWorld.y - vTileSize.y, sprIsom, 3 * vTileSize.x, 1 * vTileSize.y, vTileSize.x, vTileSize.y * 2); + break; + } + } + } + + // Draw Selected Cell - Has varying alpha components + SetPixelMode(olc::Pixel::ALPHA); + + // Convert selected cell coordinate to world space + olc::vi2d vSelectedWorld = ToScreen(vSelected.x, vSelected.y); + + // Draw "highlight" tile + DrawPartialSprite(vSelectedWorld.x, vSelectedWorld.y, sprIsom, 0 * vTileSize.x, 0, vTileSize.x, vTileSize.y); + + // Go back to normal drawing with no expected transparency + SetPixelMode(olc::Pixel::NORMAL); + + // Draw Hovered Cell Boundary + //DrawRect(vCell.x * vTileSize.x, vCell.y * vTileSize.y, vTileSize.x, vTileSize.y, olc::RED); + + // Draw Debug Info + DrawString(4, 4, "Mouse : " + std::to_string(vMouse.x) + ", " + std::to_string(vMouse.y), olc::BLACK); + DrawString(4, 14, "Cell : " + std::to_string(vCell.x) + ", " + std::to_string(vCell.y), olc::BLACK); + DrawString(4, 24, "Selected: " + std::to_string(vSelected.x) + ", " + std::to_string(vSelected.y), olc::BLACK); + return true; + } +}; + + +int main() +{ + IsometricDemo demo; + if (demo.Construct(512, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_MIDI.cpp b/Videos/OneLoneCoder_PGE_MIDI.cpp new file mode 100644 index 0000000..4a2e1d6 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_MIDI.cpp @@ -0,0 +1,626 @@ +/* + Programming MIDI: Parsing, Displaying (& Playing) MIDI Files + "Better get these done before im virused..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2020 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. + + Relevant Video: https://youtu.be/040BKtnDdg0 + + 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 + + Community: https://community.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 +*/ + + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include +#include + +//#pragma comment(lib, "winmm.lib") + + +struct MidiEvent +{ + enum class Type + { + NoteOff, + NoteOn, + Other + } event; + + uint8_t nKey = 0; + uint8_t nVelocity = 0; + uint32_t nDeltaTick = 0; +}; + + +struct MidiNote +{ + uint8_t nKey = 0; + uint8_t nVelocity = 0; + uint32_t nStartTime = 0; + uint32_t nDuration = 0; +}; + +struct MidiTrack +{ + std::string sName; + std::string sInstrument; + std::vector vecEvents; + std::vector vecNotes; + uint8_t nMaxNote = 64; + uint8_t nMinNote = 64; +}; + + +class MidiFile +{ +public: + enum EventName : uint8_t + { + VoiceNoteOff = 0x80, + VoiceNoteOn = 0x90, + VoiceAftertouch = 0xA0, + VoiceControlChange = 0xB0, + VoiceProgramChange = 0xC0, + VoiceChannelPressure = 0xD0, + VoicePitchBend = 0xE0, + SystemExclusive = 0xF0, + }; + + enum MetaEventName : uint8_t + { + MetaSequence = 0x00, + MetaText = 0x01, + MetaCopyright = 0x02, + MetaTrackName = 0x03, + MetaInstrumentName = 0x04, + MetaLyrics = 0x05, + MetaMarker = 0x06, + MetaCuePoint = 0x07, + MetaChannelPrefix = 0x20, + MetaEndOfTrack = 0x2F, + MetaSetTempo = 0x51, + MetaSMPTEOffset = 0x54, + MetaTimeSignature = 0x58, + MetaKeySignature = 0x59, + MetaSequencerSpecific = 0x7F, + }; + +public: + MidiFile() + { + } + + MidiFile(const std::string& sFileName) + { + ParseFile(sFileName); + } + + void Clear() + { + + } + + bool ParseFile(const std::string& sFileName) + { + // Open the MIDI File as a stream + std::ifstream ifs; + ifs.open(sFileName, std::fstream::in | std::ios::binary); + if (!ifs.is_open()) + return false; + + + // Helper Utilities ==================== + + // Swaps byte order of 32-bit integer + auto Swap32 = [](uint32_t n) + { + return (((n >> 24) & 0xff) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | ((n << 24) & 0xff000000)); + }; + + // Swaps byte order of 16-bit integer + auto Swap16 = [](uint16_t n) + { + return ((n >> 8) | (n << 8)); + }; + + // Reads nLength bytes form file stream, and constructs a text string + auto ReadString = [&ifs](uint32_t nLength) + { + std::string s; + for (uint32_t i = 0; i < nLength; i++) s += ifs.get(); + return s; + }; + + // Reads a compressed MIDI value. This can be up to 32 bits long. Essentially if the first byte, first + // bit is set to 1, that indicates that the next byte is required to construct the full word. Only + // the bottom 7 bits of each byte are used to construct the final word value. Each successive byte + // that has MSB set, indicates a further byte needs to be read. + auto ReadValue = [&ifs]() + { + uint32_t nValue = 0; + uint8_t nByte = 0; + + // Read byte + nValue = ifs.get(); + + // Check MSB, if set, more bytes need reading + if (nValue & 0x80) + { + // Extract bottom 7 bits of read byte + nValue &= 0x7F; + do + { + // Read next byte + nByte = ifs.get(); + + // Construct value by setting bottom 7 bits, then shifting 7 bits + nValue = (nValue << 7) | (nByte & 0x7F); + } + while (nByte & 0x80); // Loop whilst read byte MSB is 1 + } + + // Return final construction (always 32-bit unsigned integer internally) + return nValue; + }; + + uint32_t n32 = 0; + uint16_t n16 = 0; + + // Read MIDI Header (Fixed Size) + ifs.read((char*)&n32, sizeof(uint32_t)); + uint32_t nFileID = Swap32(n32); + ifs.read((char*)&n32, sizeof(uint32_t)); + uint32_t nHeaderLength = Swap32(n32); + ifs.read((char*)&n16, sizeof(uint16_t)); + uint16_t nFormat = Swap16(n16); + ifs.read((char*)&n16, sizeof(uint16_t)); + uint16_t nTrackChunks = Swap16(n16); + ifs.read((char*)&n16, sizeof(uint16_t)); + uint16_t nDivision = Swap16(n16); + + for (uint16_t nChunk = 0; nChunk < nTrackChunks; nChunk++) + { + std::cout << "===== NEW TRACK" << std::endl; + // Read Track Header + ifs.read((char*)&n32, sizeof(uint32_t)); + uint32_t nTrackID = Swap32(n32); + ifs.read((char*)&n32, sizeof(uint32_t)); + uint32_t nTrackLength = Swap32(n32); + + bool bEndOfTrack = false; + + vecTracks.push_back(MidiTrack()); + + uint32_t nWallTime = 0; + + uint8_t nPreviousStatus = 0; + + while (!ifs.eof() && !bEndOfTrack) + { + // Fundamentally all MIDI Events contain a timecode, and a status byte* + uint32_t nStatusTimeDelta = 0; + uint8_t nStatus = 0; + + + // Read Timecode from MIDI stream. This could be variable in length + // and is the delta in "ticks" from the previous event. Of course this value + // could be 0 if two events happen simultaneously. + nStatusTimeDelta = ReadValue(); + + // Read first byte of message, this could be the status byte, or it could not... + nStatus = ifs.get(); + + // All MIDI Status events have the MSB set. The data within a standard MIDI event + // does not. A crude yet utilised form of compression is to omit sending status + // bytes if the following sequence of events all refer to the same MIDI Status. + // This is called MIDI Running Status, and is essential to succesful decoding of + // MIDI streams and files. + // + // If the MSB of the read byte was not set, and on the whole we were expecting a + // status byte, then Running Status is in effect, so we refer to the previous + // confirmed status byte. + if (nStatus < 0x80) + { + // MIDI Running Status is happening, so refer to previous valid MIDI Status byte + nStatus = nPreviousStatus; + + // We had to read the byte to assess if MIDI Running Status is in effect. But! + // that read removed the byte form the stream, and that will desync all of the + // following code because normally we would have read a status byte, but instead + // we have read the data contained within a MIDI message. The simple solution is + // to put the byte back :P + ifs.seekg(-1, std::ios_base::cur); + } + + + + if ((nStatus & 0xF0) == EventName::VoiceNoteOff) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nNoteID = ifs.get(); + uint8_t nNoteVelocity = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOff, nNoteID, nNoteVelocity, nStatusTimeDelta }); + } + + else if ((nStatus & 0xF0) == EventName::VoiceNoteOn) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nNoteID = ifs.get(); + uint8_t nNoteVelocity = ifs.get(); + if(nNoteVelocity == 0) + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOff, nNoteID, nNoteVelocity, nStatusTimeDelta }); + else + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::NoteOn, nNoteID, nNoteVelocity, nStatusTimeDelta }); + } + + else if ((nStatus & 0xF0) == EventName::VoiceAftertouch) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nNoteID = ifs.get(); + uint8_t nNoteVelocity = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); + } + + else if ((nStatus & 0xF0) == EventName::VoiceControlChange) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nControlID = ifs.get(); + uint8_t nControlValue = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); + } + + else if ((nStatus & 0xF0) == EventName::VoiceProgramChange) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nProgramID = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); + } + + else if ((nStatus & 0xF0) == EventName::VoiceChannelPressure) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nChannelPressure = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); + } + + else if ((nStatus & 0xF0) == EventName::VoicePitchBend) + { + nPreviousStatus = nStatus; + uint8_t nChannel = nStatus & 0x0F; + uint8_t nLS7B = ifs.get(); + uint8_t nMS7B = ifs.get(); + vecTracks[nChunk].vecEvents.push_back({ MidiEvent::Type::Other }); + + } + + else if ((nStatus & 0xF0) == EventName::SystemExclusive) + { + nPreviousStatus = 0; + + if (nStatus == 0xFF) + { + // Meta Message + uint8_t nType = ifs.get(); + uint8_t nLength = ReadValue(); + + switch (nType) + { + case MetaSequence: + std::cout << "Sequence Number: " << ifs.get() << ifs.get() << std::endl; + break; + case MetaText: + std::cout << "Text: " << ReadString(nLength) << std::endl; + break; + case MetaCopyright: + std::cout << "Copyright: " << ReadString(nLength) << std::endl; + break; + case MetaTrackName: + vecTracks[nChunk].sName = ReadString(nLength); + std::cout << "Track Name: " << vecTracks[nChunk].sName << std::endl; + break; + case MetaInstrumentName: + vecTracks[nChunk].sInstrument = ReadString(nLength); + std::cout << "Instrument Name: " << vecTracks[nChunk].sInstrument << std::endl; + break; + case MetaLyrics: + std::cout << "Lyrics: " << ReadString(nLength) << std::endl; + break; + case MetaMarker: + std::cout << "Marker: " << ReadString(nLength) << std::endl; + break; + case MetaCuePoint: + std::cout << "Cue: " << ReadString(nLength) << std::endl; + break; + case MetaChannelPrefix: + std::cout << "Prefix: " << ifs.get() << std::endl; + break; + case MetaEndOfTrack: + bEndOfTrack = true; + break; + case MetaSetTempo: + // Tempo is in microseconds per quarter note + if (m_nTempo == 0) + { + (m_nTempo |= (ifs.get() << 16)); + (m_nTempo |= (ifs.get() << 8)); + (m_nTempo |= (ifs.get() << 0)); + m_nBPM = (60000000 / m_nTempo); + std::cout << "Tempo: " << m_nTempo << " (" << m_nBPM << "bpm)" << std::endl; + } + break; + case MetaSMPTEOffset: + std::cout << "SMPTE: H:" << ifs.get() << " M:" << ifs.get() << " S:" << ifs.get() << " FR:" << ifs.get() << " FF:" << ifs.get() << std::endl; + break; + case MetaTimeSignature: + std::cout << "Time Signature: " << ifs.get() << "/" << (2 << ifs.get()) << std::endl; + std::cout << "ClocksPerTick: " << ifs.get() << std::endl; + + // A MIDI "Beat" is 24 ticks, so specify how many 32nd notes constitute a beat + std::cout << "32per24Clocks: " << ifs.get() << std::endl; + break; + case MetaKeySignature: + std::cout << "Key Signature: " << ifs.get() << std::endl; + std::cout << "Minor Key: " << ifs.get() << std::endl; + break; + case MetaSequencerSpecific: + std::cout << "Sequencer Specific: " << ReadString(nLength) << std::endl; + break; + default: + std::cout << "Unrecognised MetaEvent: " << nType << std::endl; + } + } + + if (nStatus == 0xF0) + { + // System Exclusive Message Begin + std::cout << "System Exclusive Begin: " << ReadString(ReadValue()) << std::endl; + } + + if (nStatus == 0xF7) + { + // System Exclusive Message Begin + std::cout << "System Exclusive End: " << ReadString(ReadValue()) << std::endl; + } + } + else + { + std::cout << "Unrecognised Status Byte: " << nStatus << std::endl; + } + } + } + + + // Convert Time Events to Notes + for (auto& track : vecTracks) + { + uint32_t nWallTime = 0; + + std::list listNotesBeingProcessed; + + for (auto& event : track.vecEvents) + { + nWallTime += event.nDeltaTick; + + if (event.event == MidiEvent::Type::NoteOn) + { + // New Note + listNotesBeingProcessed.push_back({ event.nKey, event.nVelocity, nWallTime, 0 }); + } + + if (event.event == MidiEvent::Type::NoteOff) + { + auto note = std::find_if(listNotesBeingProcessed.begin(), listNotesBeingProcessed.end(), [&](const MidiNote& n) { return n.nKey == event.nKey; }); + if (note != listNotesBeingProcessed.end()) + { + note->nDuration = nWallTime - note->nStartTime; + track.vecNotes.push_back(*note); + track.nMinNote = std::min(track.nMinNote, note->nKey); + track.nMaxNote = std::max(track.nMaxNote, note->nKey); + listNotesBeingProcessed.erase(note); + } + } + } + } + + return true; + } + +public: + std::vector vecTracks; + uint32_t m_nTempo = 0; + uint32_t m_nBPM = 0; + +}; + + +class olcMIDIViewer : public olc::PixelGameEngine +{ +public: + olcMIDIViewer() + { + sAppName = "MIDI File Viewer"; + } + + + MidiFile midi; + + //HMIDIOUT hInstrument; + size_t nCurrentNote[16]{ 0 }; + + double dSongTime = 0.0; + double dRunTime = 0.0; + uint32_t nMidiClock = 0; + + +public: + bool OnUserCreate() override + { + + midi.ParseFile("ff7_battle.mid"); + + /* + int nMidiDevices = midiOutGetNumDevs(); + if (nMidiDevices > 0) + { + if (midiOutOpen(&hInstrument, 2, NULL, 0, NULL) == MMSYSERR_NOERROR) + { + std::cout << "Opened midi" << std::endl; + } + } + */ + + + return true; + } + + float nTrackOffset = 1000; + + bool OnUserUpdate(float fElapsedTime) override + { + Clear(olc::BLACK); + uint32_t nTimePerColumn = 50; + uint32_t nNoteHeight = 2; + uint32_t nOffsetY = 0; + + if (GetKey(olc::Key::LEFT).bHeld) nTrackOffset -= 10000.0f * fElapsedTime; + if (GetKey(olc::Key::RIGHT).bHeld) nTrackOffset += 10000.0f * fElapsedTime; + + + for (auto& track : midi.vecTracks) + { + if (!track.vecNotes.empty()) + { + uint32_t nNoteRange = track.nMaxNote - track.nMinNote; + + FillRect(0, nOffsetY, ScreenWidth(), (nNoteRange + 1) * nNoteHeight, olc::DARK_GREY); + DrawString(1, nOffsetY + 1, track.sName); + + for (auto& note : track.vecNotes) + { + FillRect((note.nStartTime - nTrackOffset) / nTimePerColumn, (nNoteRange - (note.nKey - track.nMinNote)) * nNoteHeight + nOffsetY, note.nDuration / nTimePerColumn, nNoteHeight, olc::WHITE); + } + + nOffsetY += (nNoteRange + 1) * nNoteHeight + 4; + } + } + + // BELOW - ABSOLUTELY HORRIBLE BODGE TO PLAY SOUND + // DO NOT USE THIS CODE... + + /* + dRunTime += fElapsedTime; + uint32_t nTempo = 4; + int nTrack = 1; + while (dRunTime >= 1.0 / double(midi.m_nBPM * 8)) + { + dRunTime -= 1.0 / double(midi.m_nBPM * 8); + + // Single MIDI Clock + nMidiClock++; + + int i = 0; + int nTrack = 1; + //for (nTrack = 1; nTrack < 3; nTrack++) + { + if (nCurrentNote[nTrack] < midi.vecTracks[nTrack].vecEvents.size()) + { + if (midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nDeltaTick == 0) + { + uint32_t nStatus = 0; + uint32_t nNote = midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nKey; + uint32_t nVelocity = midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nVelocity; + + if (midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].event == MidiEvent::Type::NoteOn) + nStatus = 0x90; + else + nStatus = 0x80; + + midiOutShortMsg(hInstrument, (nVelocity << 16) | (nNote << 8) | nStatus); + nCurrentNote[nTrack]++; + } + else + midi.vecTracks[nTrack].vecEvents[nCurrentNote[nTrack]].nDeltaTick--; + } + } + } + + if (GetKey(olc::Key::SPACE).bPressed) + { + midiOutShortMsg(hInstrument, 0x00403C90); + } + + if (GetKey(olc::Key::SPACE).bReleased) + { + midiOutShortMsg(hInstrument, 0x00003C80); + } + */ + + + return true; + } + + +}; + +int main() +{ + olcMIDIViewer demo; + if (demo.Construct(1280, 960, 1, 1)) + demo.Start(); + return 0; +} + diff --git a/Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp b/Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp new file mode 100644 index 0000000..0648983 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp @@ -0,0 +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 +#include +#include +#include + + +// 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> 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> 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 &n1, const std::tuple &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 &n1, const std::tuple &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> path; + path.push_back({ nStartX, nStartY }); + int nLocX = nStartX; + int nLocY = nStartY; + bool bNoPath = false; + + while (!(nLocX == nEndX && nLocY == nEndY) && !bNoPath) + { + std::list> 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 &n1, const std::tuple &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; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp b/Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp new file mode 100644 index 0000000..e3b95c8 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp @@ -0,0 +1,434 @@ +/* + Convex Polygon Collision Detection + "Don't you dare try concave ones..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Instructions: + ~~~~~~~~~~~~~ + Use arrow keys to control pentagon + Use WASD to control triangle + F1..F4 selects algorithm + + Relevant Video: https://youtu.be/7Ik2vowGcU0 + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include +#include + +// 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 p; // Transformed Points + vec2d pos; // Position of shape + float angle; // Direction of shape + std::vector o; // "Model" of shape + bool overlap = false; // Flag to indicate if overlap has occurred + }; + + std::vector 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; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_Polymorphism.cpp b/Videos/OneLoneCoder_PGE_Polymorphism.cpp new file mode 100644 index 0000000..6a55761 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_Polymorphism.cpp @@ -0,0 +1,542 @@ +/* + OLC::CAD - A practical example of Polymorphism + "Damn Gorbette, you made us giggle..." - 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: + ~~~~~~~~~~~~~ + Press & Hold middle mouse mutton to PAN + Use Scroll wheel (or Q & A) to zoom in & out + Press L to start drawing a line + Press C to start drawing a circle + Press B to start drawing a box + Press S to start drawing a curve + Press M to move node under cursor + + Relevant Video: https://youtu.be/kxKKHKSMGIg + + 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" + +// Forward declare shape, since we use it in sNode +struct sShape; + +// Define a node +struct sNode +{ + sShape *parent; + olc::vf2d pos; +}; + +// Our BASE class, defines the interface for all shapes +struct sShape +{ + // Shapes are defined by the placment of nodes + std::vector vecNodes; + uint32_t nMaxNodes = 0; + + // The colour of the shape + olc::Pixel col = olc::GREEN; + + // All shapes share word to screen transformation + // coefficients, so share them staically + static float fWorldScale; + static olc::vf2d vWorldOffset; + + // Convert coordinates from World Space --> Screen Space + void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) + { + nScreenX = (int)((v.x - vWorldOffset.x) * fWorldScale); + nScreenY = (int)((v.y - vWorldOffset.y) * fWorldScale); + } + + // This is a PURE function, which makes this class abstract. A sub-class + // of this class must provide an implementation of this function by + // overriding it + virtual void DrawYourself(olc::PixelGameEngine *pge) = 0; + + // Shapes are defined by nodes, the shape is responsible + // for issuing nodes that get placed by the user. The shape may + // change depending on how many nodes have been placed. Once the + // maximum number of nodes for a shape have been placed, it returns + // nullptr + sNode* GetNextNode(const olc::vf2d &p) + { + if (vecNodes.size() == nMaxNodes) + return nullptr; // Shape is complete so no new nodes to be issued + + // else create new node and add to shapes node vector + sNode n; + n.parent = this; + n.pos = p; + vecNodes.push_back(n); + + // Beware! - This normally is bad! But see sub classes + return &vecNodes[vecNodes.size() - 1]; + } + + // Test to see if supplied coordinate exists at same location + // as any of the nodes for this shape. Return a pointer to that + // node if it does + sNode* HitNode(olc::vf2d &p) + { + for (auto &n : vecNodes) + { + if ((p - n.pos).mag() < 0.01f) + return &n; + } + + return nullptr; + } + + // Draw all of the nodes that define this shape so far + void DrawNodes(olc::PixelGameEngine *pge) + { + for (auto &n : vecNodes) + { + int sx, sy; + WorldToScreen(n.pos, sx, sy); + pge->FillCircle(sx, sy, 2, olc::RED); + } + } +}; + +// We must provide an implementation of our static variables +float sShape::fWorldScale = 1.0f; +olc::vf2d sShape::vWorldOffset = { 0,0 }; + + + +// LINE sub class, inherits from sShape +struct sLine : public sShape +{ + sLine() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); // We're gonna be getting pointers to vector elements + // though we have defined already how much capacity our vector will have. This makes + // it safe to do this as we know the vector will not be maniupulated as we add nodes + // to it. Is this bad practice? Possibly, but as with all thing programming, if you + // know what you are doing, it's ok :D + } + + // Implements custom DrawYourself Function, meaning the shape + // is no longer abstract + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col); + } +}; + + +// BOX +struct sBox : public sShape +{ + sBox() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawRect(sx, sy, ex - sx, ey - sy, col); + } +}; + + +// CIRCLE +struct sCircle : public sShape +{ + sCircle() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + float fRadius = (vecNodes[0].pos - vecNodes[1].pos).mag(); + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // Note the radius is also scaled so it is drawn appropriately + pge->DrawCircle(sx, sy, (int32_t)(fRadius * fWorldScale), col); + } +}; + +// BEZIER SPLINE - requires 3 nodes to be defined fully +struct sCurve : public sShape +{ + sCurve() + { + nMaxNodes = 3; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + + if (vecNodes.size() < 3) + { + // Can only draw line from first to second + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + } + + if (vecNodes.size() == 3) + { + // Can draw line from first to second + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // Can draw second structural line + WorldToScreen(vecNodes[1].pos, sx, sy); + WorldToScreen(vecNodes[2].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // And bezier curve + olc::vf2d op = vecNodes[0].pos; + olc::vf2d np = op; + for (float t = 0; t < 1.0f; t += 0.01f) + { + np = (1 - t)*(1 - t)*vecNodes[0].pos + 2 * (1 - t)*t*vecNodes[1].pos + t * t * vecNodes[2].pos; + WorldToScreen(op, sx, sy); + WorldToScreen(np, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col); + op = np; + } + } + + } +}; + + + +// APPLICATION STARTS HERE + +class Polymorphism : public olc::PixelGameEngine +{ +public: + Polymorphism() + { + sAppName = "Polymorphism"; + } + +private: + // Pan & Zoom variables + olc::vf2d vOffset = { 0.0f, 0.0f }; + olc::vf2d vStartPan = { 0.0f, 0.0f }; + float fScale = 10.0f; + float fGrid = 1.0f; + + // Convert coordinates from World Space --> Screen Space + void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) + { + nScreenX = (int)((v.x - vOffset.x) * fScale); + nScreenY = (int)((v.y - vOffset.y) * fScale); + } + + // Convert coordinates from Screen Space --> World Space + void ScreenToWorld(int nScreenX, int nScreenY, olc::vf2d &v) + { + v.x = (float)(nScreenX) / fScale + vOffset.x; + v.y = (float)(nScreenY) / fScale + vOffset.y; + } + + + // A pointer to a shape that is currently being defined + // by the placment of nodes + sShape* tempShape = nullptr; + + // A list of pointers to all shapes which have been drawn + // so far + std::list listShapes; + + // A pointer to a node that is currently selected. Selected + // nodes follow the mouse cursor + sNode *selectedNode = nullptr; + + // "Snapped" mouse location + olc::vf2d vCursor = { 0, 0 }; + + // NOTE! No direct instances of lines, circles, boxes or curves, + // the application is only aware of the existence of shapes! + // THIS IS THE POWER OF POLYMORPHISM! + +public: + bool OnUserCreate() override + { + // Configure world space (0,0) to be middle of screen space + vOffset = { (float)(-ScreenWidth() / 2) / fScale, (float)(-ScreenHeight() / 2) / fScale }; + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Get mouse location this frame + olc::vf2d vMouse = { (float)GetMouseX(), (float)GetMouseY() }; + + + // Handle Pan & Zoom + if (GetMouse(2).bPressed) + { + vStartPan = vMouse; + } + + if (GetMouse(2).bHeld) + { + vOffset -= (vMouse - vStartPan) / fScale; + vStartPan = vMouse; + } + + olc::vf2d vMouseBeforeZoom; + ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseBeforeZoom); + + if (GetKey(olc::Key::Q).bHeld || GetMouseWheel() > 0) + { + fScale *= 1.1f; + } + + if (GetKey(olc::Key::A).bHeld || GetMouseWheel() < 0) + { + fScale *= 0.9f; + } + + olc::vf2d vMouseAfterZoom; + ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseAfterZoom); + vOffset += (vMouseBeforeZoom - vMouseAfterZoom); + + + // Snap mouse cursor to nearest grid interval + vCursor.x = floorf((vMouseAfterZoom.x + 0.5f) * fGrid); + vCursor.y = floorf((vMouseAfterZoom.y + 0.5f) * fGrid); + + + if (GetKey(olc::Key::L).bPressed) + { + tempShape = new sLine(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + + if (GetKey(olc::Key::B).bPressed) + { + tempShape = new sBox(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + if (GetKey(olc::Key::C).bPressed) + { + // Create new shape as a temporary + tempShape = new sCircle(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + if (GetKey(olc::Key::S).bPressed) + { + // Create new shape as a temporary + tempShape = new sCurve(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + // Search for any node that exists under the cursor, if one + // is found then select it + if (GetKey(olc::Key::M).bPressed) + { + selectedNode = nullptr; + for (auto &shape : listShapes) + { + selectedNode = shape->HitNode(vCursor); + if (selectedNode != nullptr) + break; + } + } + + + // If a node is selected, make it follow the mouse cursor + // by updating its position + if (selectedNode != nullptr) + { + selectedNode->pos = vCursor; + } + + + // As the user left clicks to place nodes, the shape can grow + // until it requires no more nodes, at which point it is completed + // and added to the list of completed shapes. + if (GetMouse(0).bReleased) + { + if (tempShape != nullptr) + { + selectedNode = tempShape->GetNextNode(vCursor); + if (selectedNode == nullptr) + { + tempShape->col = olc::WHITE; + listShapes.push_back(tempShape); + tempShape = nullptr; // Thanks @howlevergreen /Disord + } + + } + else + { + selectedNode = nullptr; + } + } + + + + // Clear Screen + Clear(olc::VERY_DARK_BLUE); + + int sx, sy; + int ex, ey; + + // Get visible world + olc::vf2d vWorldTopLeft, vWorldBottomRight; + ScreenToWorld(0, 0, vWorldTopLeft); + ScreenToWorld(ScreenWidth(), ScreenHeight(), vWorldBottomRight); + + // Get values just beyond screen boundaries + vWorldTopLeft.x = floor(vWorldTopLeft.x); + vWorldTopLeft.y = floor(vWorldTopLeft.y); + vWorldBottomRight.x = ceil(vWorldBottomRight.x); + vWorldBottomRight.y = ceil(vWorldBottomRight.y); + + // Draw Grid dots + for (float x = vWorldTopLeft.x; x < vWorldBottomRight.x; x += fGrid) + { + for (float y = vWorldTopLeft.y; y < vWorldBottomRight.y; y += fGrid) + { + WorldToScreen({ x, y }, sx, sy); + Draw(sx, sy, olc::BLUE); + } + } + + // Draw World Axis + WorldToScreen({ 0,vWorldTopLeft.y }, sx, sy); + WorldToScreen({ 0,vWorldBottomRight.y }, ex, ey); + DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); + WorldToScreen({ vWorldTopLeft.x,0 }, sx, sy); + WorldToScreen({ vWorldBottomRight.x,0 }, ex, ey); + DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); + + // Update shape translation coefficients + sShape::fWorldScale = fScale; + sShape::vWorldOffset = vOffset; + + // Draw All Existing Shapes + for (auto &shape : listShapes) + { + shape->DrawYourself(this); + shape->DrawNodes(this); + } + + // Draw shape currently being defined + if (tempShape != nullptr) + { + tempShape->DrawYourself(this); + tempShape->DrawNodes(this); + } + + // Draw "Snapped" Cursor + WorldToScreen(vCursor, sx, sy); + DrawCircle(sx, sy, 3, olc::YELLOW); + + // Draw Cursor Position + DrawString(10, 10, "X=" + std::to_string(vCursor.x) + ", Y=" + std::to_string(vCursor.x), olc::YELLOW, 2); + return true; + } +}; + + +int main() +{ + Polymorphism demo; + if (demo.Construct(800, 480, 1, 1, false)) + demo.Start(); + return 0; +} + + + + diff --git a/Videos/OneLoneCoder_PGE_ProcGen_Universe.cpp b/Videos/OneLoneCoder_PGE_ProcGen_Universe.cpp new file mode 100644 index 0000000..95f58c7 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_ProcGen_Universe.cpp @@ -0,0 +1,372 @@ +/* + Procedural Generation: Programming The Universe + "Here we go again! Year 4 begins now..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2020 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. + + Relevant Video: https://youtu.be/ZZY9YE7rZJw + + 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 2018, 2019, 2020 +*/ + + + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include + +constexpr uint32_t g_starColours[8] = +{ + 0xFFFFFFFF, 0xFFD9FFFF, 0xFFA3FFFF, 0xFFFFC8C8, + 0xFFFFCB9D, 0xFF9F9FFF, 0xFF415EFF, 0xFF28199D +}; + + +struct sPlanet +{ + double distance = 0.0; + double diameter = 0.0; + double foliage = 0.0; + double minerals = 0.0; + double water = 0.0; + double gases = 0.0; + double temperature = 0.0; + double population = 0.0; + bool ring = false; + std::vector vMoons; +}; + +class cStarSystem +{ +public: + cStarSystem(uint32_t x, uint32_t y, bool bGenerateFullSystem = false) + { + // Set seed based on location of star system + nProcGen = (x & 0xFFFF) << 16 | (y & 0xFFFF); + + // Not all locations contain a system + starExists = (rndInt(0, 20) == 1); + if (!starExists) return; + + // Generate Star + starDiameter = rndDouble(10.0, 40.0); + starColour.n = g_starColours[rndInt(0, 8)]; + + // When viewing the galaxy map, we only care about the star + // so abort early + if (!bGenerateFullSystem) return; + + // If we are viewing the system map, we need to generate the + // full system + + // Generate Planets + double dDistanceFromStar = rndDouble(60.0, 200.0); + int nPlanets = rndInt(0, 10); + for (int i = 0; i < nPlanets; i++) + { + sPlanet p; + p.distance = dDistanceFromStar; + dDistanceFromStar += rndDouble(20.0, 200.0); + p.diameter = rndDouble(4.0, 20.0); + + // Could make temeprature a function of distance from star + p.temperature = rndDouble(-200.0, 300.0); + + // Composition of planet + p.foliage = rndDouble(0.0, 1.0); + p.minerals = rndDouble(0.0, 1.0); + p.gases = rndDouble(0.0, 1.0); + p.water = rndDouble(0.0, 1.0); + + // Normalise to 100% + double dSum = 1.0 / (p.foliage + p.minerals + p.gases + p.water); + p.foliage *= dSum; + p.minerals *= dSum; + p.gases *= dSum; + p.water *= dSum; + + // Population could be a function of other habitat encouraging + // properties, such as temperature and water + p.population = std::max(rndInt(-5000000, 20000000), 0); + + // 10% of planets have a ring + p.ring = rndInt(0, 10) == 1; + + // Satellites (Moons) + int nMoons = std::max(rndInt(-5, 5), 0); + for (int n = 0; n < nMoons; n++) + { + // A moon is just a diameter for now, but it could be + // whatever you want! + p.vMoons.push_back(rndDouble(1.0, 5.0)); + } + + // Add planet to vector + vPlanets.push_back(p); + } + } + + ~cStarSystem() + { + + } + +public: + std::vector vPlanets; + +public: + bool starExists = false; + double starDiameter = 0.0f; + olc::Pixel starColour = olc::WHITE; + +private: + uint32_t nProcGen = 0; + + double rndDouble(double min, double max) + { + return ((double)rnd() / (double)(0x7FFFFFFF)) * (max - min) + min; + } + + int rndInt(int min, int max) + { + return (rnd() % (max - min)) + min; + } + + uint32_t rnd() + { + nProcGen += 0xe120fc15; + uint64_t tmp; + tmp = (uint64_t)nProcGen * 0x4a39b70d; + uint32_t m1 = (tmp >> 32) ^ tmp; + tmp = (uint64_t)m1 * 0x12fad5c9; + uint32_t m2 = (tmp >> 32) ^ tmp; + return m2; + } +}; + +class olcGalaxy : public olc::PixelGameEngine +{ +public: + olcGalaxy() + { + sAppName = "olcGalaxy"; + } + +public: + bool OnUserCreate() override + { + return true; + } + + olc::vf2d vGalaxyOffset = { 0,0 }; + bool bStarSelected = false; + uint32_t nSelectedStarSeed1 = 0; + uint32_t nSelectedStarSeed2 = 0; + + + /*uint32_t nLehmer = 0; + uint32_t Lehmer32() + { + nLehmer += 0xe120fc15; + uint64_t tmp; + tmp = (uint64_t)nLehmer * 0x4a39b70d; + uint32_t m1 = (tmp >> 32) ^ tmp; + tmp = (uint64_t)m1 * 0x12fad5c9; + uint32_t m2 = (tmp >> 32) ^ tmp; + return m2; + }*/ + + bool OnUserUpdate(float fElapsedTime) override + { + if (fElapsedTime <= 0.0001f) return true; + Clear(olc::BLACK); + + //if (GetKey(olc::SPACE).bReleased) + //{ + + // //srand(1000); + + // std::random_device rd; + // std::mt19937 mt(1000); + // std::uniform_int_distribution dist(0, 256); + + // auto tp1 = std::chrono::system_clock::now(); + // // Ranomness Tests + // for (int x = 0; x < ScreenWidth(); x++) + // { + // for (int y = 0; y < ScreenHeight(); y++) + // { + // bool bIsStar = false; + // int nSeed = y << 16 | x; + // + // // Standard C++ rand() + // //srand(nSeed); + // //bIsStar = rand() % 256 < 32; + + // // std::random + // //mt.seed(nSeed); + // //bIsStar = dist(mt) < 32; + + // // Lehmer32 + // nLehmer = nSeed; + // bIsStar = Lehmer32() % 256 < 32; + + // Draw(x, y, bIsStar ? olc::WHITE : olc::BLACK); + // } + // } + // auto tp2 = std::chrono::system_clock::now(); + // std::chrono::duration elapsedTime = tp2 - tp1; + // DrawString(3, 3, "Time: " + std::to_string(elapsedTime.count()), olc::RED, 2); + //} + + + //return true; + + + if (GetKey(olc::W).bHeld) vGalaxyOffset.y -= 50.0f * fElapsedTime; + if (GetKey(olc::S).bHeld) vGalaxyOffset.y += 50.0f * fElapsedTime; + if (GetKey(olc::A).bHeld) vGalaxyOffset.x -= 50.0f * fElapsedTime; + if (GetKey(olc::D).bHeld) vGalaxyOffset.x += 50.0f * fElapsedTime; + + int nSectorsX = ScreenWidth() / 16; + int nSectorsY = ScreenHeight() / 16; + + olc::vi2d mouse = { GetMouseX() / 16, GetMouseY() / 16 }; + olc::vi2d galaxy_mouse = mouse + vGalaxyOffset; + olc::vi2d screen_sector = { 0,0 }; + + for (screen_sector.x = 0; screen_sector.x < nSectorsX; screen_sector.x++) + for (screen_sector.y = 0; screen_sector.y < nSectorsY; screen_sector.y++) + { + uint32_t seed1 = (uint32_t)vGalaxyOffset.x + (uint32_t)screen_sector.x; + uint32_t seed2 = (uint32_t)vGalaxyOffset.y + (uint32_t)screen_sector.y; + + cStarSystem star(seed1, seed2); + if (star.starExists) + { + FillCircle(screen_sector.x * 16 + 8, screen_sector.y * 16 + 8, + (int)star.starDiameter / 8, star.starColour); + + // For convenience highlight hovered star + if (mouse.x == screen_sector.x && mouse.y == screen_sector.y) + { + DrawCircle(screen_sector.x * 16 + 8, screen_sector.y * 16 + 8, 12, olc::YELLOW); + } + } + } + + // Handle Mouse Click + if (GetMouse(0).bPressed) + { + uint32_t seed1 = (uint32_t)vGalaxyOffset.x + (uint32_t)mouse.x; + uint32_t seed2 = (uint32_t)vGalaxyOffset.y + (uint32_t)mouse.y; + + cStarSystem star(seed1, seed2); + if (star.starExists) + { + bStarSelected = true; + nSelectedStarSeed1 = seed1; + nSelectedStarSeed2 = seed2; + } + else + bStarSelected = false; + } + + // Draw Details of selected star system + if (bStarSelected) + { + // Generate full star system + cStarSystem star(nSelectedStarSeed1, nSelectedStarSeed2, true); + + // Draw Window + FillRect(8, 240, 496, 232, olc::DARK_BLUE); + DrawRect(8, 240, 496, 232, olc::WHITE); + + // Draw Star + olc::vi2d vBody = { 14, 356 }; + + vBody.x += star.starDiameter * 1.375; + FillCircle(vBody, (int)(star.starDiameter * 1.375), star.starColour); + vBody.x += (star.starDiameter * 1.375) + 8; + + + + // Draw Planets + for (auto& planet : star.vPlanets) + { + if (vBody.x + planet.diameter >= 496) break; + + vBody.x += planet.diameter; + FillCircle(vBody, (int)(planet.diameter * 1.0), olc::RED); + + olc::vi2d vMoon = vBody; + vMoon.y += planet.diameter + 10; + + // Draw Moons + for (auto& moon : planet.vMoons) + { + vMoon.y += moon; + FillCircle(vMoon, (int)(moon * 1.0), olc::GREY); + vMoon.y += moon + 10; + } + + vBody.x += planet.diameter + 8; + } + } + + return true; + } +}; + +int main() +{ + olcGalaxy demo; + if (demo.Construct(512, 480, 2, 2, false, false)) + demo.Start(); + return 0; +} diff --git a/Videos/OneLoneCoder_PGE_RetroMenu.cpp b/Videos/OneLoneCoder_PGE_RetroMenu.cpp new file mode 100644 index 0000000..85816ac --- /dev/null +++ b/Videos/OneLoneCoder_PGE_RetroMenu.cpp @@ -0,0 +1,482 @@ +/* + Easy To Use "Retro Pop-Up Menu System" + "There's a storm comin'......" - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2020 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. + + Relevant Video: https://youtu.be/jde1Jq5dF0E + + 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 + + Community Blog: https://community.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 +*/ + + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include + + +constexpr int32_t nPatch = 8; + + +class menuobject +{ +public: + menuobject() + { sName = "root"; } + + menuobject(const std::string& name) + { sName = name; } + + menuobject& SetTable(int nColumns, int nRows) + { vCellTable = { nColumns, nRows }; return *this; } + + menuobject& SetID(int32_t id) + { nID = id; return *this; } + + int32_t GetID() + { return nID; } + + std::string& GetName() + { return sName; } + + menuobject& Enable(bool b) + { bEnabled = b; return *this; } + + bool Enabled() + { return bEnabled; } + + bool HasChildren() + { return !items.empty(); } + + // For now, cells are simply one line strings + olc::vi2d GetSize() + { return { int32_t(sName.size()), 1 }; } + + olc::vi2d& GetCursorPosition() + { return vCursorPos; } + + menuobject& operator[](const std::string& name) + { + if (itemPointer.count(name) == 0) + { + itemPointer[name] = items.size(); + items.push_back(menuobject(name)); + } + + return items[itemPointer[name]]; + } + + void Build() + { + // Recursively build all children, so they can determine their size, use + // that size to indicate cell sizes if this object contains more than + // one item + for (auto& m : items) + { + if (m.HasChildren()) + { + m.Build(); + } + + // Longest child name determines cell width + vCellSize.x = std::max(m.GetSize().x, vCellSize.x); + vCellSize.y = std::max(m.GetSize().y, vCellSize.y); + } + + // Adjust size of this object (in patches) if it were rendered as a panel + vSizeInPatches.x = vCellTable.x * vCellSize.x + (vCellTable.x - 1) * vCellPadding.x + 2; + vSizeInPatches.y = vCellTable.y * vCellSize.y + (vCellTable.y - 1) * vCellPadding.y + 2; + + // Calculate how many rows this item has to hold + nTotalRows = (items.size() / vCellTable.x) + (((items.size() % vCellTable.x) > 0) ? 1 : 0); + } + + void DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset) + { + // === Draw Panel + + // Record current pixel mode user is using + olc::Pixel::Mode currentPixelMode = pge.GetPixelMode(); + pge.SetPixelMode(olc::Pixel::MASK); + + // Draw Panel & Border + olc::vi2d vPatchPos = { 0,0 }; + for (vPatchPos.x = 0; vPatchPos.x < vSizeInPatches.x; vPatchPos.x++) + { + for (vPatchPos.y = 0; vPatchPos.y < vSizeInPatches.y; vPatchPos.y++) + { + // Determine position in screen space + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + + // Calculate which patch is needed + olc::vi2d vSourcePatch = { 0, 0 }; + if (vPatchPos.x > 0) vSourcePatch.x = 1; + if (vPatchPos.x == vSizeInPatches.x - 1) vSourcePatch.x = 2; + if (vPatchPos.y > 0) vSourcePatch.y = 1; + if (vPatchPos.y == vSizeInPatches.y - 1) vSourcePatch.y = 2; + + // Draw Actual Patch + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + } + + // === Draw Panel Contents + olc::vi2d vCell = { 0,0 }; + vPatchPos = { 1,1 }; + + // Work out visible items + int32_t nTopLeftItem = nTopVisibleRow * vCellTable.x; + int32_t nBottomRightItem = vCellTable.y * vCellTable.x + nTopLeftItem; + + // Clamp to size of child item vector + nBottomRightItem = std::min(int32_t(items.size()), nBottomRightItem); + int32_t nVisibleItems = nBottomRightItem - nTopLeftItem; + + // Draw Scroll Markers (if required) + if (nTopVisibleRow > 0) + { + vPatchPos = { vSizeInPatches.x - 2, 0 }; + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + olc::vi2d vSourcePatch = { 3, 0 }; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + + if ((nTotalRows - nTopVisibleRow) > vCellTable.y) + { + vPatchPos = { vSizeInPatches.x - 2, vSizeInPatches.y - 1 }; + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + olc::vi2d vSourcePatch = { 3, 2 }; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + + // Draw Visible Items + for (int32_t i = 0; i < nVisibleItems; i++) + { + // Cell location + vCell.x = i % vCellTable.x; + vCell.y = i / vCellTable.x; + + // Patch location (including border offset and padding) + vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1; + vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; + + // Actual screen location in pixels + olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; + + // Display Item Header + pge.DrawString(vScreenLocation, items[nTopLeftItem + i].sName, items[nTopLeftItem + i].bEnabled ? olc::WHITE : olc::DARK_GREY); + + if (items[nTopLeftItem + i].HasChildren()) + { + // Display Indicator that panel has a sub panel + vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1 + vCellSize.x; + vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; + olc::vi2d vSourcePatch = { 3, 1 }; + vScreenLocation = vPatchPos * nPatch + vScreenOffset; + pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); + } + } + + // Calculate cursor position in screen space in case system draws it + vCursorPos.x = (vCellCursor.x * (vCellSize.x + vCellPadding.x)) * nPatch + vScreenOffset.x - nPatch; + vCursorPos.y = ((vCellCursor.y - nTopVisibleRow) * (vCellSize.y + vCellPadding.y)) * nPatch + vScreenOffset.y + nPatch; + } + + void ClampCursor() + { + // Find item in children + nCursorItem = vCellCursor.y * vCellTable.x + vCellCursor.x; + + // Clamp Cursor + if (nCursorItem >= int32_t(items.size())) + { + vCellCursor.y = (items.size() / vCellTable.x); + vCellCursor.x = (items.size() % vCellTable.x) - 1; + nCursorItem = items.size() - 1; + } + } + + void OnUp() + { + vCellCursor.y--; + if (vCellCursor.y < 0) vCellCursor.y = 0; + + if (vCellCursor.y < nTopVisibleRow) + { + nTopVisibleRow--; + if (nTopVisibleRow < 0) nTopVisibleRow = 0; + } + + ClampCursor(); + } + + void OnDown() + { + vCellCursor.y++; + if (vCellCursor.y == nTotalRows) vCellCursor.y = nTotalRows - 1; + + if (vCellCursor.y > (nTopVisibleRow + vCellTable.y - 1)) + { + nTopVisibleRow++; + if (nTopVisibleRow > (nTotalRows - vCellTable.y)) + nTopVisibleRow = nTotalRows - vCellTable.y; + } + + ClampCursor(); + } + + void OnLeft() + { + vCellCursor.x--; + if (vCellCursor.x < 0) vCellCursor.x = 0; + ClampCursor(); + } + + void OnRight() + { + vCellCursor.x++; + if (vCellCursor.x == vCellTable.x) vCellCursor.x = vCellTable.x - 1; + ClampCursor(); + } + + menuobject* OnConfirm() + { + // Check if selected item has children + if (items[nCursorItem].HasChildren()) + { + return &items[nCursorItem]; + } + else + return this; + } + + menuobject* GetSelectedItem() + { + return &items[nCursorItem]; + } + + +protected: + int32_t nID = -1; + olc::vi2d vCellTable = { 1, 0 }; + std::unordered_map itemPointer; + std::vector items; + olc::vi2d vSizeInPatches = { 0, 0 }; + olc::vi2d vCellSize = { 0, 0 }; + olc::vi2d vCellPadding = { 2, 0 }; + olc::vi2d vCellCursor = { 0, 0 }; + int32_t nCursorItem = 0; + int32_t nTopVisibleRow = 0; + int32_t nTotalRows = 0; + const olc::vi2d vPatchSize = { nPatch, nPatch }; + std::string sName; + olc::vi2d vCursorPos = { 0, 0 }; + bool bEnabled = true; +}; + + +class menumanager +{ +public: + menumanager() { } + + void Open(menuobject* mo) { Close(); panels.push_back(mo); } + void Close() { panels.clear(); } + + void OnUp() { if (!panels.empty()) panels.back()->OnUp(); } + void OnDown() { if (!panels.empty()) panels.back()->OnDown(); } + void OnLeft() { if (!panels.empty()) panels.back()->OnLeft(); } + void OnRight() { if (!panels.empty()) panels.back()->OnRight(); } + void OnBack() { if (!panels.empty()) panels.pop_back(); } + + menuobject* OnConfirm() + { + if (panels.empty()) return nullptr; + + menuobject* next = panels.back()->OnConfirm(); + if (next == panels.back()) + { + if(panels.back()->GetSelectedItem()->Enabled()) + return panels.back()->GetSelectedItem(); + } + else + { + if(next->Enabled()) + panels.push_back(next); + } + + return nullptr; + } + + void Draw(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset) + { + if (panels.empty()) return; + + // Draw Visible Menu System + for (auto& p : panels) + { + p->DrawSelf(pge, sprGFX, vScreenOffset); + vScreenOffset += {10, 10}; + } + + // Draw Cursor + olc::Pixel::Mode currentPixelMode = pge.GetPixelMode(); + pge.SetPixelMode(olc::Pixel::ALPHA); + pge.DrawPartialSprite(panels.back()->GetCursorPosition(), sprGFX, olc::vi2d(4, 0) * nPatch, { nPatch * 2, nPatch * 2 }); + pge.SetPixelMode(currentPixelMode); + } + +private: + std::list panels; +}; + +// Override base class with your custom functionality +class olcRetroPopUpMenu : public olc::PixelGameEngine +{ +public: + olcRetroPopUpMenu() + { + sAppName = "Retro Pop-Up Menu"; + } + + olc::Sprite* sprGFX = nullptr; + + menuobject mo; + menumanager mm; + +public: + bool OnUserCreate() override + { + sprGFX = new olc::Sprite("./RetroMenu.png"); + + mo["main"].SetTable(1, 4); + mo["main"]["Attack"].SetID(101); + + mo["main"]["Magic"].SetTable(1, 2); + + mo["main"]["Magic"]["White"].SetTable(3, 6); + auto& menuWhiteMagic = mo["main"]["Magic"]["White"]; + menuWhiteMagic["Cure"].SetID(401); + menuWhiteMagic["Cura"].SetID(402); + menuWhiteMagic["Curaga"].SetID(403); + menuWhiteMagic["Esuna"].SetID(404); + + mo["main"]["Magic"]["Black"].SetTable(3, 4); + auto& menuBlackMagic = mo["main"]["Magic"]["Black"]; + menuBlackMagic["Fire"].SetID(201); + menuBlackMagic["Fira"].SetID(202); + menuBlackMagic["Firaga"].SetID(203); + menuBlackMagic["Blizzard"].SetID(204); + menuBlackMagic["Blizzara"].SetID(205).Enable(false); + menuBlackMagic["Blizzaga"].SetID(206).Enable(false); + menuBlackMagic["Thunder"].SetID(207); + menuBlackMagic["Thundara"].SetID(208); + menuBlackMagic["Thundaga"].SetID(209); + menuBlackMagic["Quake"].SetID(210); + menuBlackMagic["Quake2"].SetID(211); + menuBlackMagic["Quake3"].SetID(212); + menuBlackMagic["Bio"].SetID(213); + menuBlackMagic["Bio1"].SetID(214); + menuBlackMagic["Bio2"].SetID(215); + menuBlackMagic["Demi"].SetID(216); + menuBlackMagic["Demi1"].SetID(217); + menuBlackMagic["Demi2"].SetID(218); + + mo["main"]["Defend"].SetID(102); + + mo["main"]["Items"].SetTable(2, 4).Enable(false); + mo["main"]["Items"]["Potion"].SetID(301); + mo["main"]["Items"]["Ether"].SetID(302); + mo["main"]["Items"]["Elixir"].SetID(303); + + mo["main"]["Escape"].SetID(103); + + mo.Build(); + + mm.Open(&mo["main"]); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + menuobject* command = nullptr; + + if (GetKey(olc::Key::M).bPressed) mm.Open(&mo["main"]); + + if (GetKey(olc::Key::UP).bPressed) mm.OnUp(); + if (GetKey(olc::Key::DOWN).bPressed) mm.OnDown(); + if (GetKey(olc::Key::LEFT).bPressed) mm.OnLeft(); + if (GetKey(olc::Key::RIGHT).bPressed) mm.OnRight(); + if (GetKey(olc::Key::SPACE).bPressed) command = mm.OnConfirm(); + if (GetKey(olc::Key::Z).bPressed) mm.OnBack(); + + if (command != nullptr) + { + sLastAction = "Selected: " + command->GetName() + " ID: " + std::to_string(command->GetID()); + mm.Close(); + } + + Clear(olc::BLACK); + mm.Draw(*this, sprGFX, { 30,30 }); + DrawString(10, 200, sLastAction); + return true; + } + + std::string sLastAction; +}; + +int main() +{ + olcRetroPopUpMenu demo; + if (demo.Construct(384, 240, 4, 4)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_RobotArm1.cpp b/Videos/OneLoneCoder_PGE_RobotArm1.cpp new file mode 100644 index 0000000..8c1bcd4 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_RobotArm1.cpp @@ -0,0 +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()); + + } +} + +*/ \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp b/Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp new file mode 100644 index 0000000..4399472 Binary files /dev/null and b/Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp differ diff --git a/Videos/OneLoneCoder_PGE_ShootEmUp.cpp b/Videos/OneLoneCoder_PGE_ShootEmUp.cpp new file mode 100644 index 0000000..0d070ee --- /dev/null +++ b/Videos/OneLoneCoder_PGE_ShootEmUp.cpp @@ -0,0 +1,484 @@ +/* + Live 100K Code Party! Code-It-Yourself: SHMUP + "It's done... 2019 is done..." - 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. + + Relevant Video: https://youtu.be/CqDfZEX0Yhc + + 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 + + Community Blog: http://community.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// IMPORTANT!! Requires sprites not provided in repo! +// Sprites are 48x48 pixels. + + +#include +#include +#include + +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + sAppName = "100K Live Special - SHMUP"; + } + + olc::Sprite* sprPlayer = nullptr; + olc::Sprite* sprEnemy[3]; + + olc::vf2d vPlayerPos; + float fPlayerSpeed = 100.0f; + float fScrollSpeed = 60.0f; + float fPlayerShipRad = 24 * 24; + float fPlayerHealth = 1000.0f; + float fPlayerGunTemp = 0.0f; + float fPlayerGunReload = 0.2f; + float fPlayerGunReloadTime = 0.0f; + + double dWorldPos = 0.0f; + + std::array aryStars; + + struct sBullet + { + olc::vf2d pos; + olc::vf2d vel; + bool remove = false; + }; + + struct sEnemy; + + struct sEnemyDefinition + { + double dTriggerTime; + uint32_t nSpriteID = 0; + float fHealth = 0.0f; + std::function funcMove; + std::function& bullets)> funcFire; + float offset = 0.0f; + }; + + struct sEnemy + { + olc::vf2d pos; + sEnemyDefinition def; + + std::array dataMove{ 0 }; + std::array dataFire{ 0 }; + + void Update(float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + def.funcMove(*this, fElapsedTime, fScrollSpeed); + def.funcFire(*this, fElapsedTime, fScrollSpeed, bullets); + } + }; + + + olc::vf2d GetMiddle(const olc::Sprite *s) + { + return { (float)s->width / 2.0f, (float)s->height / 2.0f }; + } + + + std::list listSpawns; + std::list listEnemies; + std::list listEnemyBullets; + std::list listPlayerBullets; + std::list listStars; + std::list listFragments; + +public: + bool OnUserCreate() override + { + // Load resources + sprPlayer = new olc::Sprite("gfx//100k_player.png"); + sprEnemy[0] = new olc::Sprite("gfx//100k_enemy1.png"); + sprEnemy[1] = new olc::Sprite("gfx//100k_enemy2.png"); + sprEnemy[2] = new olc::Sprite("gfx//100k_enemy3.png"); + + // Generate Star Map + for (auto& s : aryStars) s = { (float)(rand() % ScreenWidth()), (float)(rand() % ScreenHeight()) }; + + // MOVEMENT PATTERN FUNCTIONS + auto Move_None = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed) + { + e.pos.y += fScrollSpeed * fElapsedTime; + }; + + auto Move_StraightFast = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed) + { + e.pos.y += 3.0f * fScrollSpeed * fElapsedTime; + }; + + auto Move_StraightSlow = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed) + { + e.pos.y += 0.5f * fScrollSpeed * fElapsedTime; + }; + + auto Move_SinusoidNarrow = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed) + { + e.dataMove[0] += fElapsedTime; + e.pos.y += 0.5f * fScrollSpeed * fElapsedTime; + e.pos.x += 50.0f * cosf(e.dataMove[0]) * fElapsedTime; + }; + + auto Move_SinusoidWide = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed) + { + e.dataMove[0] += fElapsedTime; + e.pos.y += 0.5f * fScrollSpeed * fElapsedTime; + e.pos.x += 150.0f * cosf(e.dataMove[0]) * fElapsedTime; + }; + + // FIRING PATTERN FUNCTIONS + auto Fire_None = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + + }; + + auto Fire_StraightDelay2 = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + constexpr float fDelay = 0.2f; + e.dataFire[0] += fElapsedTime; + if (e.dataFire[0] >= fDelay) + { + e.dataFire[0] -= fDelay; + sBullet b; + b.pos = e.pos + olc::vf2d((float)sprEnemy[e.def.nSpriteID]->width / 2.0f, (float)sprEnemy[e.def.nSpriteID]->height); + b.vel = { 0.0f, 180.0f }; + bullets.push_back(b); + } + }; + + auto Fire_CirclePulse2 = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + constexpr float fDelay = 0.2f; + constexpr int nBullets = 10; + constexpr float fTheta = 2.0f * 3.14159f / (float)nBullets; + e.dataFire[0] += fElapsedTime; + if (e.dataFire[0] >= fDelay) + { + e.dataFire[0] -= fDelay; + for (int i = 0; i < nBullets; i++) + { + sBullet b; + b.pos = e.pos + GetMiddle(sprEnemy[e.def.nSpriteID]); + b.vel = { 180.0f * cosf(fTheta * i), 180.0f * sinf(fTheta * i)}; + bullets.push_back(b); + } + } + }; + + auto Fire_DeathSpiral = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + constexpr float fDelay = 0.01f; + e.dataFire[0] += fElapsedTime; + if (e.dataFire[0] >= fDelay) + { + e.dataFire[1] += 0.1f; + e.dataFire[0] -= fDelay; + sBullet b; + b.pos = e.pos + GetMiddle(sprEnemy[e.def.nSpriteID]); + b.vel = { 180.0f * cosf(e.dataFire[1]), 180.0f * sinf(e.dataFire[1]) }; + bullets.push_back(b); + + } + }; + + auto Fire_DeathSpiralCircle = [&](sEnemy& e, float fElapsedTime, float fScrollSpeed, std::list& bullets) + { + constexpr float fDelay = 0.2f; + constexpr int nBullets = 100; + constexpr float fTheta = 2.0f * 3.14159f / (float)nBullets; + e.dataFire[0] += fElapsedTime; + if (e.dataFire[0] >= fDelay) + { + e.dataFire[0] -= fDelay; + e.dataFire[1] += 0.1f; + for (int i = 0; i < nBullets; i++) + { + sBullet b; + b.pos = e.pos + GetMiddle(sprEnemy[e.def.nSpriteID]); + b.vel = { 180.0f * cosf(fTheta * i + e.dataFire[1]), 180.0f * sinf(fTheta * i + e.dataFire[1]) }; + bullets.push_back(b); + } + } + }; + + // Construct level + listSpawns = + { + {100.0, 0, 3.0f, Move_None, Fire_CirclePulse2, 0.25f}, + {100.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.75f}, + {120.0, 1, 3.0f, Move_SinusoidNarrow, Fire_CirclePulse2, 0.50f}, + + {200.0, 2, 3.0f, Move_SinusoidWide, Fire_CirclePulse2, 0.30f}, + {200.0, 2, 3.0f, Move_SinusoidWide, Fire_CirclePulse2, 0.70f}, + + {500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.2f}, + {500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.4f}, + {500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.6f}, + {500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.8f}, + + {550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.1f}, + {550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.3f}, + {550.0, 0, 3.0f, Move_StraightSlow, Fire_DeathSpiralCircle, 0.5f}, + {550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.7f}, + {550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.9f}, + + {600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.2f}, + {600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.4f}, + {600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.6f}, + {600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.8f}, + + {1100.0, 0, 3.0f, Move_None, Fire_CirclePulse2, 0.25f}, + {1100.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.75f}, + {1120.0, 1, 3.0f, Move_SinusoidNarrow, Fire_CirclePulse2, 0.50f}, + + {1200.0, 2, 3.0f, Move_SinusoidWide, Fire_CirclePulse2, 0.30f}, + {1200.0, 2, 3.0f, Move_SinusoidWide, Fire_CirclePulse2, 0.70f}, + + {1500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.2f}, + {1500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.4f}, + {1500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.6f}, + {1500.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.8f}, + + {1550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.1f}, + {1550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.3f}, + {1550.0, 0, 3.0f, Move_StraightSlow, Fire_DeathSpiralCircle, 0.5f}, + {1550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.7f}, + {1550.0, 0, 3.0f, Move_StraightFast, Fire_DeathSpiral, 0.9f}, + + {1600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.2f}, + {1600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.4f}, + {1600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.6f}, + {1600.0, 0, 3.0f, Move_StraightFast, Fire_StraightDelay2, 0.8f}, + }; + + + vPlayerPos = { (float)ScreenWidth() / 2, (float)ScreenHeight() / 2 }; + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // AutoScroll World + dWorldPos += fScrollSpeed * fElapsedTime; + + // Scroll Player Object + vPlayerPos.y += fScrollSpeed * fElapsedTime; + + // Handle Player Input + if (GetKey(olc::W).bHeld) vPlayerPos.y -= (fPlayerSpeed + fScrollSpeed) * fElapsedTime; + if (GetKey(olc::S).bHeld) vPlayerPos.y += (fPlayerSpeed - fScrollSpeed) * fElapsedTime; + if (GetKey(olc::A).bHeld) vPlayerPos.x -= fPlayerSpeed * fElapsedTime * 2.0f; + if (GetKey(olc::D).bHeld) vPlayerPos.x += fPlayerSpeed * fElapsedTime * 2.0f; + + // Clamp Player to screen + if (vPlayerPos.x <= 0) vPlayerPos.x = 0; + if (vPlayerPos.x + (float)sprPlayer->width >= ScreenWidth()) vPlayerPos.x = (float)ScreenWidth() - sprPlayer->width; + if (vPlayerPos.y <= 0) vPlayerPos.y = 0; + if (vPlayerPos.y + (float)sprPlayer->height >= ScreenHeight()) vPlayerPos.y = (float)ScreenHeight() - sprPlayer->height; + + // Player Weapon Fire + bool bCanFire = false; + fPlayerGunReloadTime -= fElapsedTime; + if (fPlayerGunReloadTime <= 0.0f) + { + bCanFire = true; + } + + fPlayerGunTemp -= fElapsedTime * 10.0f; + if (fPlayerGunTemp < 0) fPlayerGunTemp = 0; + if (GetMouse(0).bHeld) + { + if (bCanFire && fPlayerGunTemp < 80.0f) + { + fPlayerGunReloadTime = fPlayerGunReload; + fPlayerGunTemp += 5.0f; + if (fPlayerGunTemp > 100.0f) fPlayerGunTemp = 100.0f; + listPlayerBullets.push_back({vPlayerPos + olc::vf2d((float)sprPlayer->width / 2.0f, 0.0f), {0.0f, -200.0f} }); + } + } + + // Update Player Bullets + for (auto& b : listPlayerBullets) + { + // Position Bullet + b.pos += (b.vel + olc::vf2d(0.0f, fScrollSpeed)) * fElapsedTime; + + // Check Enemies Vs Player Bullets + for (auto& e : listEnemies) + { + if ((b.pos - (e.pos + olc::vf2d(24.0f, 24.0f))).mag2() < fPlayerShipRad) + { + // Enemy has been hit + b.remove = true; + e.def.fHealth -= 1.0f; + + // Trigger explosion + if (e.def.fHealth <= 0) + { + for (int i = 0; i < 500; i++) + { + float angle = ((float)rand() / (float)RAND_MAX) * 2.0f * 3.14159f; + float speed = ((float)rand() / (float)RAND_MAX) * 200.0f + 50.0f; + listFragments.push_back({ + e.pos + GetMiddle(sprEnemy[e.def.nSpriteID]), + { cosf(angle)*speed, sinf(angle)*speed }}); + } + } + } + } + } + + + // Perform spawn check + while(!listSpawns.empty() && dWorldPos >= listSpawns.front().dTriggerTime) + { + sEnemy e; + e.def = listSpawns.front(); + e.pos = + { + listSpawns.front().offset * (float)(ScreenWidth() - sprEnemy[e.def.nSpriteID]->width), + 0.0f - sprEnemy[e.def.nSpriteID]->height + }; + listSpawns.pop_front(); + listEnemies.push_back(e); + } + + // Update Enemies + for (auto& e : listEnemies) e.Update(fElapsedTime, fScrollSpeed, listEnemyBullets); + + // Update Enemy Bullets + for (auto& b : listEnemyBullets) + { + // Position Bullet + b.pos += (b.vel + olc::vf2d(0.0f, fScrollSpeed)) * fElapsedTime; + + // Check Player Vs Enemy Bullets + if ((b.pos - (vPlayerPos + olc::vf2d(24.0f, 24.0f))).mag2() < fPlayerShipRad) + { + b.remove = true; + fPlayerHealth -= 10.0f; + } + } + + // Update Fragments + for(auto& f : listFragments) f.pos += (f.vel + olc::vf2d(0.0f, fScrollSpeed)) * fElapsedTime; + + // Remove Offscreen Enemies + listEnemies.remove_if([&](const sEnemy& e) {return (e.pos.y >= (float)ScreenHeight()) || e.def.fHealth <= 0.0f; }); + + // Remove finished enemy bullets + listEnemyBullets.remove_if([&](const sBullet& b) {return b.pos.x<0 || b.pos.x>ScreenWidth() || b.pos.y <0 || b.pos.y>ScreenHeight() || b.remove; }); + + // Remove finished player bullets + listPlayerBullets.remove_if([&](const sBullet& b) {return b.pos.x<0 || b.pos.x>ScreenWidth() || b.pos.y <0 || b.pos.y>ScreenHeight() || b.remove; }); + + // Remove finished fragments + listFragments.remove_if([&](const sBullet& b) {return b.pos.x<0 || b.pos.x>ScreenWidth() || b.pos.y <0 || b.pos.y>ScreenHeight() || b.remove; }); + + // GRAPHICS + Clear(olc::BLACK); + + // Update & Draw Stars + for (size_t i=0; i> 2) ? 0.8f : 1.0f); + if (s.y >= (float)ScreenHeight()) + s = { (float)(rand() % ScreenWidth()), 0.0f }; + + Draw(s, (i < aryStars.size() >> 2) ? olc::DARK_GREY : olc::WHITE); + } + + SetPixelMode(olc::Pixel::MASK); + + // Draw Enemies + for (auto& e : listEnemies) DrawSprite(e.pos, sprEnemy[e.def.nSpriteID]); + + // Draw Player + DrawSprite(vPlayerPos, sprPlayer); + + SetPixelMode(olc::Pixel::NORMAL); + + // Draw Enemy Bullets + for (auto& b : listEnemyBullets) FillCircle(b.pos, 3, olc::RED); + + // Draw Player Bullets + for (auto& b : listPlayerBullets) FillCircle(b.pos, 3, olc::CYAN); + + // Draw Fragments + for (auto& b : listFragments) Draw(b.pos, olc::YELLOW); + + // Draw Player Health Bar + DrawString(4, 4, "HEALTH:"); + FillRect(60, 4, (fPlayerHealth / 1000.0f * 576.0f), 8, olc::GREEN); + + DrawString(4, 14, "WEAPON:"); + FillRect(60, 14, (fPlayerGunTemp / 100.0f * 576.0f), 8, olc::YELLOW); + + return true; + } +}; + + +int main() +{ + Example demo; + if (demo.Construct(640, 480, 2, 2)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_SoundTest.cpp b/Videos/OneLoneCoder_PGE_SoundTest.cpp new file mode 100644 index 0000000..373d448 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_SoundTest.cpp @@ -0,0 +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 + +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; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_SpriteTransforms.cpp b/Videos/OneLoneCoder_PGE_SpriteTransforms.cpp new file mode 100644 index 0000000..ed0d9b5 --- /dev/null +++ b/Videos/OneLoneCoder_PGE_SpriteTransforms.cpp @@ -0,0 +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 +#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; +} \ No newline at end of file diff --git a/Videos/OneLoneCoder_PGE_olcEngine3D.cpp b/Videos/OneLoneCoder_PGE_olcEngine3D.cpp new file mode 100644 index 0000000..d140a16 Binary files /dev/null and b/Videos/OneLoneCoder_PGE_olcEngine3D.cpp differ diff --git a/Videos/RetroMenu.png b/Videos/RetroMenu.png new file mode 100644 index 0000000..f74213f Binary files /dev/null and b/Videos/RetroMenu.png differ diff --git a/Videos/SampleA.wav b/Videos/SampleA.wav new file mode 100644 index 0000000..9a53e04 Binary files /dev/null and b/Videos/SampleA.wav differ diff --git a/Videos/SampleB.wav b/Videos/SampleB.wav new file mode 100644 index 0000000..aac72c7 Binary files /dev/null and b/Videos/SampleB.wav differ diff --git a/Videos/SampleC.wav b/Videos/SampleC.wav new file mode 100644 index 0000000..111a57b Binary files /dev/null and b/Videos/SampleC.wav differ diff --git a/Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp b/Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp new file mode 100644 index 0000000..6bfe4d3 --- /dev/null +++ b/Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp @@ -0,0 +1,1509 @@ + +/* + Saving Sedit: An OLC Code Jam 2018 Submission + "At least this is checked off my list now..." - javidx9 + + Download the playable version here: https://onelonecoder.itch.io/saving-sedit + + Note: This was a JAM entry, it is incomplete, has bugs, has features that + dont go anywhere. I've not tidied up the code - it is a record of how + the JAM went. + + 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. + + Relevant Video: https://youtu.be/Qco5BstCxRM + + 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" +#define OLC_PGE_GRAPHICS2D +#include "olcPGEX_Graphics2D.h" + +#include "olcPGEX_TileMaps_new.h" +#define OLC_PGEX_SOUND +#include "olcPGEX_Sound.h" + +#include "Zix_PGE_Controller.h" + +#include +#include +#include +#include +#include +using namespace std; + + +class cAnimator +{ +public: + std::map> mapStates; + +public: + std::string sCurrentState; + int nCurrentFrame = 0; + float fTimeBetweenFrames = 0.1f; + float fTimeCounter = 0.0f; + + + void ChangeState(std::string s) + { + if (s != sCurrentState) + { + sCurrentState = s; + fTimeCounter = 0; + nCurrentFrame = 0; + } + } + + void Update(float fElapsedTime) + { + fTimeCounter += fElapsedTime; + if (fTimeCounter >= fTimeBetweenFrames) + { + fTimeCounter -= fTimeBetweenFrames; + nCurrentFrame++; + if (nCurrentFrame >= mapStates[sCurrentState].size()) + nCurrentFrame = 0; + } + } + + void DrawSelf(olc::PixelGameEngine *pge, olc::GFX2D::Transform2D &t) + { + olc::GFX2D::DrawSprite(mapStates[sCurrentState][nCurrentFrame], t); + } + +}; + +// Override base class with your custom functionality +class Discovery : public olc::PixelGameEngine +{ +public: + Discovery() + { + sAppName = "Discovery - OLC CodeJam 2018"; + } + +private: + + int nTileSizeX = 64; + int nTileSizeY = 64; + olc::TILE::Layer layerWorld; + olc::TILE::Layer layerCollectibles; + olc::TILE::Layer layerJavid; + olc::TILE::Atlas atlasWorldMagetzub; + olc::TILE::Atlas atlasWorldJavid; + olc::TILE::Atlas atlasWorldHopson; + olc::TILE::Atlas atlasCollectibles; + + olc::ResourcePack rpPlayer; + + olc::Sprite *sprBackdropMagetzub; + olc::Sprite *sprBackdropHopson; + + olc::Sprite *sprTitleScreen; + olc::Sprite *sprStoryScreen; + olc::Sprite *sprControlScreen; + olc::Sprite *sprCreditScreen; + olc::Sprite *sprCompleteScreen; + + float fBackdropScaleX = 1.0f; + float fBackdropScaleY = 1.0f; + + olc::Sprite *sprMiniMap = nullptr; + + olc::Sprite *backBuff; + + + + cAnimator animPlayer; + ControllerManager controller; + + float fPlayerPosX = 2.0f; + float fPlayerPosY = 2.0f; + float fPlayerVelX = 0.0f; + float fPlayerVelY = 0.0f; + float fCameraPosX = 0.0f; + float fCameraPosY = 0.0f; + bool bPlayerOnGround = false; + bool bPlayerTouchingWall = false; + bool bPlayerTouchingWallOld = false; + bool bCollisionLeft = false; + float fTimeToJump = 0.0f; + float fTimeToJumpMax = 0.25f; + float fFaceDir = 1.0f; + int nFloppyDisks = 0; + float fGameTime = 0.0f; + float fGameTimeMultiplier = 1.0f; + int nHopsonTokens = 0; + int nJavidTokens = 0; + + int nKeys = 0; + float fEffectTime = 0.0f; + float fModeTime = 0.0f; + + bool bFire = false; + + list> listKeys; + + struct sBullet + { + float bx; + float by; + float vx; + bool bRemove = false; + }; + + enum + { + MODE_MAGETZUB, + MODE_JAVID, + MODE_HOPSON, + MODE_HUHLIG + + } nRenderMode; + + enum + { + EFFECT_NONE, + EFFECT_UGLYSWEDISHFISH, + EFFECT_SEDIT, + EFFECT_BRANK, + EFFECT_GORBIT, + + } nRenderEffect; + + + enum + { + GS_LOADING, + GS_TITLE, + GS_STORY, + GS_CREDITS, + GS_GENERATE, + GS_MAIN, + GS_COMPLETE, + } nGameState = GS_LOADING; + + int nChallengeMapSizeX = 8; + int nChallengeMapSizeY = 8; + int nChallengePathSizeX = 4; + int nChallengePathSizeY = 4; + + void CreateMaze(bool* &pMap, int nCellWidth, int nCellHeight, int nCellsX, int nCellsY, int &nMapWidth, int &nMapHeight) + { + int *pLevel = new int[nCellsX * nCellsY]{ 0 }; + + enum + { + CELL_PATH_N = 0x01, + CELL_PATH_E = 0x02, + CELL_PATH_S = 0x04, + CELL_PATH_W = 0x08, + CELL_VISITED = 0x10, + }; + + std::stack> m_stack; + + + auto offset = [&](int x, int y) + { + return (m_stack.top().second + y) * nCellsX + (m_stack.top().first + x); + }; + + m_stack.push(std::make_pair(0, 0)); + pLevel[0] = CELL_VISITED; + int nVisitedCells = 1; + + // Do Maze Algorithm + while (nVisitedCells < nCellsX * nCellsY) + { + // Step 1: Create a set of the unvisted neighbours + + std::vector neighbours; + + // North Neighbour + if (m_stack.top().second > 0 && (pLevel[offset(0, -1)] & CELL_VISITED) == 0) + neighbours.push_back(0); + // East neighbour + if (m_stack.top().first < nCellsX - 1 && (pLevel[offset(1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(1); + // South neighbour + if (m_stack.top().second < nCellsY - 1 && (pLevel[offset(0, 1)] & CELL_VISITED) == 0) + neighbours.push_back(2); + // West neighbour + if (m_stack.top().first > 0 && (pLevel[offset(-1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(3); + + + // Are there any neighbours available? + if (!neighbours.empty()) + { + // Choose one available neighbour at random + int next_cell_dir = neighbours[rand() % neighbours.size()]; + + // Create a path between the neighbour and the current cell + switch (next_cell_dir) + { + case 0: // North + pLevel[offset(0, -1)] |= CELL_VISITED | CELL_PATH_S; + pLevel[offset(0, 0)] |= CELL_PATH_N; + m_stack.push(std::make_pair((m_stack.top().first + 0), (m_stack.top().second - 1))); + break; + + case 1: // East + pLevel[offset(+1, 0)] |= CELL_VISITED | CELL_PATH_W; + pLevel[offset(0, 0)] |= CELL_PATH_E; + m_stack.push(std::make_pair((m_stack.top().first + 1), (m_stack.top().second + 0))); + break; + + case 2: // South + pLevel[offset(0, +1)] |= CELL_VISITED | CELL_PATH_N; + pLevel[offset(0, 0)] |= CELL_PATH_S; + m_stack.push(std::make_pair((m_stack.top().first + 0), (m_stack.top().second + 1))); + break; + + case 3: // West + pLevel[offset(-1, 0)] |= CELL_VISITED | CELL_PATH_E; + pLevel[offset(0, 0)] |= CELL_PATH_W; + m_stack.push(std::make_pair((m_stack.top().first - 1), (m_stack.top().second + 0))); + break; + + } + + nVisitedCells++; + } + else + { + m_stack.pop(); // Backtrack + } + } + + printf("M1 %d\n", m_stack.size()); + + // Convert Maze into tile map + nMapWidth = (nCellWidth + 1) * nCellsX + 1; + nMapHeight = (nCellHeight + 1) * nCellsY + 1; + pMap = new bool[nMapWidth * nMapHeight]{ 0 }; + printf("M1\n"); + + // Draw Maze + for (int x = 0; x < nCellsX; x++) + { + for (int y = 0; y < nCellsY; y++) + { + for (int py = 0; py < nCellHeight; py++) + for (int px = 0; px < nCellWidth; px++) + { + if (pLevel[y * nCellsX + x] & CELL_VISITED) + pMap[(y* (nCellHeight + 1) + py + 1) * nMapWidth + (x * (nCellWidth + 1) + px) + 1] = true; + else + pMap[(y* (nCellHeight + 1) + py + 1) * nMapWidth + (x * (nCellWidth + 1) + px + 1)] = false; + } + + for (int p = 0; p < nCellWidth; p++) + if (pLevel[y * nCellsX + x] & CELL_PATH_S) + pMap[(y * (nCellHeight + 1) + nCellHeight + 1) * nMapWidth + (x * (nCellWidth + 1) + p + 1)] = true; + + for (int p = 0; p < nCellHeight; p++) + if (pLevel[y * nCellsX + x] & CELL_PATH_E) + pMap[(y * (nCellHeight + 1) + p + 1) * nMapWidth + (x * (nCellWidth + 1) + nCellWidth + 1)] = true; + } + } + + printf("M1\n"); + delete[] pLevel; + printf("M1\n"); + } + + void CalculateFlowMap() + { + // Update Flow map + + int nFlowWidth = layerJavid.nLayerWidth; + int nFlowHeight = layerJavid.nLayerHeight; + + // 1) Prepare it centered on player + for (int x = 0; x < nFlowWidth; x++) + for (int y = 0; y < nFlowHeight; y++) + { + layerJavid.GetTile(x, y)->id = 2; + layerJavid.GetTile(x, y)->edge_id[0] = 0; + + if (x == 0 || x == (nFlowWidth - 1) || y == 0 || y == (nFlowHeight - 1)) + layerJavid.GetTile(x, y)->edge_id[0] = -1; + else + //layerJavid.GetTile(x, y)->exist = layerWorld.GetTile(x, y)->exist; + layerJavid.GetTile(x, y)->edge_id[0] = layerWorld.GetTile(x, y)->exist ? -1 : 0; + + + } + + + + list> nodes; + + nodes.push_back({ listKeys.back().first,listKeys.back().second, 1 }); + + while (!nodes.empty()) + { + list> new_nodes; + + // For each node in processing queue, set its count value, and add unmarked + // neighbour nodes + for (auto &n : nodes) + { + int x = get<0>(n); + int y = get<1>(n); + int d = get<2>(n); + + // Set distance for this node + layerJavid.GetTile(x, y)->edge_id[0] = d; + + // Add neigbour nodes if unmarked + if ((x + 1) < nFlowWidth && layerJavid.GetTile(x+1, y)->edge_id[0] == 0) + new_nodes.push_back({ x + 1, y, d + 1 }); + if ((x - 1) >= 0 && layerJavid.GetTile(x - 1, y)->edge_id[0] == 0) + new_nodes.push_back({ x - 1, y, d + 1}); + if ((y + 1) < nFlowHeight && layerJavid.GetTile(x, y+1)->edge_id[0] == 0) + new_nodes.push_back({ x, y + 1, d + 1 }); + if ((y - 1) >= 0 && layerJavid.GetTile(x, y-1)->edge_id[0] == 0) + new_nodes.push_back({ x, y - 1, d + 1 }); + } + + new_nodes.sort([&](const tuple &n1, const tuple &n2) + { + return (get<1>(n1) * layerJavid.nLayerWidth + get<0>(n1)) < (get<1>(n2) * layerJavid.nLayerWidth + get<0>(n2)); + }); + + new_nodes.unique([&](const tuple &n1, const tuple &n2) + { + return (get<1>(n1) * layerJavid.nLayerWidth + get<0>(n1)) == (get<1>(n2) * layerJavid.nLayerWidth + get<0>(n2)); + }); + + + nodes.clear(); + nodes.insert(nodes.begin(), new_nodes.begin(), new_nodes.end()); + } + + for (int x = 0; x < nFlowWidth; x++) + { + for (int y = 0; y < nFlowHeight; y++) + { + if (layerJavid.GetTile(x, y)->edge_id[0] > 0) + { + int flow_xa, flow_xb, flow_ya, flow_yb; + flow_xa = flow_xb = flow_ya = flow_yb = layerJavid.GetTile(x, y)->edge_id[0]; + + if ((x + 1) < nFlowWidth && layerJavid.GetTile(x+1, y)->edge_id[0] > 0) + flow_xb = layerJavid.GetTile(x + 1, y)->edge_id[0]; + + if ((x - 1) >= 0 && layerJavid.GetTile(x-1, y)->edge_id[0] > 0) + flow_xa = layerJavid.GetTile(x - 1, y)->edge_id[0]; + + if ((y + 1) < nFlowHeight && layerJavid.GetTile(x, y+1)->edge_id[0] > 0) + flow_yb = layerJavid.GetTile(x, y + 1)->edge_id[0]; + + if ((y - 1) >= 0 && layerJavid.GetTile(x, y-1)->edge_id[0] > 0) + flow_ya = layerJavid.GetTile(x, y - 1)->edge_id[0]; + + float fdx = (float)(flow_xa - flow_xb); + float fdy = (float)(flow_ya - flow_yb); + float r = 1.0f / sqrtf(fdx * fdx + fdy * fdy); + + if (fabs(fdx) > fabs(fdy)) + { + if (fdx > 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 8; + } + + if (fdx < 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 10; + } + } + else + { + if (fdy > 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 9; + } + + if (fdy < 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 7; + } + } + } + } + } + } + + void CreateBorderMap() + { + // Use OLC Standard Bordered tile atlas + for (int x = 0; x < layerWorld.nLayerWidth; x++) + { + for (int y = 0; y < layerWorld.nLayerHeight; y++) + { + int s = 0; + auto a = [&](int i, int j) + { + return (layerWorld.GetTile(i, j) && layerWorld.GetTile(i, j)->exist); + }; + + if (a(x, y)) + { + s |= a(x - 1, y + 0) ? 1 : 0; s <<= 1; + s |= a(x - 1, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 0, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 1, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 1, y + 0) ? 1 : 0; s <<= 1; + s |= a(x + 1, y - 1) ? 1 : 0; s <<= 1; + s |= a(x - 0, y - 1) ? 1 : 0; s <<= 1; + s |= a(x - 1, y - 1) ? 1 : 0; + + int ix = s % 16; + int iy = s / 16; + layerWorld.GetTile(x, y)->id = (iy * 256 + 64) + (ix * 4) + 1; + } + else + layerWorld.GetTile(x, y)->id = 0; + } + } + } + +public: + + int sndHelperChange; + int sndJump; + int sndWall; + int sndGithubPatch; + int sndKeyCollect; + int sndDiskCollect; + int sndThump; + int sndTheme; + + + + bool OnUserCreate() override + { + controller.Initialize(); + backBuff = new olc::Sprite(ScreenWidth(), ScreenHeight()); + + olc::SOUND::InitialiseAudio(); + + animPlayer.ChangeState("idle"); + return true; + } + + bool OnUserDestroy() + { + olc::SOUND::DestroyAudio(); + return true; + } + + bool bFirstFrameLoading = true; + + bool GameState_Loading(float fElapsedTime) + { + + + + if (bFirstFrameLoading) + { + Clear(olc::BLACK); + DrawString(60, 240, "- Loading, Please Wait - ", olc::WHITE, 2); + bFirstFrameLoading = false; + + return true; + } + + rpPlayer.LoadPack("./discres/savingsedit.olcdat"); + + sndHelperChange = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Negative_Trigger_1_2.wav", &rpPlayer); + sndJump = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Jump_1_5.wav", &rpPlayer); + sndWall = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\LQ_Back_Button.wav", &rpPlayer); + sndThump = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Small_Impact_1_1.wav", &rpPlayer); + sndGithubPatch = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Skill_Unlock.wav", &rpPlayer); + sndKeyCollect = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\FA_Special_Item_2_1.wav", &rpPlayer); + sndDiskCollect = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\FA_Funny_Impact_1_3.wav", &rpPlayer); + sndTheme = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\Funky Chill 2 loop.wav", &rpPlayer); + + olc::SOUND::PlaySample(sndHelperChange); + + // Load Game Resources + // Define OLC Standard atlas + //atlasWorldMagetzub.Create(new olc::Sprite());// new olc::Sprite("thruster_landscape_all3_neo.png")); + //atlasWorldMagetzub.sprTileSheet->SaveToPGESprFile("discres\\discovery_magetzub_64x64.olcspr"); + //atlasWorldMagetzub.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_64x64.olcspr"); + atlasWorldMagetzub.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_64x64.olcspr", &rpPlayer)); + + //atlasWorldJavid.Create(new olc::Sprite());//new olc::Sprite("discovery_javid_64x64.png")); + //atlasWorldJavid.sprTileSheet->SaveToPGESprFile("discres\\discovery_javid_64x64.olcspr"); + //atlasWorldJavid.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_javid_64x64.olcspr"); + atlasWorldJavid.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_javid_64x64.olcspr", &rpPlayer)); + + //atlasWorldHopson.Create(new olc::Sprite());//new olc::Sprite("discovery_hopson_64x64.png")); + //atlasWorldHopson.sprTileSheet->SaveToPGESprFile("discres\\discovery_hopson_64x64.olcspr"); + atlasWorldHopson.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_64x64.olcspr", &rpPlayer)); + //atlasWorldHopson.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_64x64.olcspr"); + for (int i = 0; i < 64; i++) + for (int j = 0; j < 64; j++) + { + //atlasWorld.location.emplace_back(j * 16, i * 16, 16, 16); + atlasWorldMagetzub.location.emplace_back(j * 64, i * 64, 64, 64); + atlasWorldJavid.location.emplace_back(j * 64, i * 64, 64, 64); + atlasWorldHopson.location.emplace_back(j * 64, i * 64, 64, 64); + } + + //atlasCollectibles.Create(new olc::Sprite());// "discovery_collect1.png")); + //atlasCollectibles.sprTileSheet->SaveToPGESprFile("discres\\discovery_collectibles.olcspr"); + //atlasCollectibles.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_collectibles.olcspr"); + atlasCollectibles.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_collectibles.olcspr", &rpPlayer)); + atlasCollectibles.location.emplace_back(0, 0, 16, 16); // Blank + atlasCollectibles.location.emplace_back(16, 0, 16, 16); // Disk + atlasCollectibles.location.emplace_back(32, 0, 16, 16); // Exit + atlasCollectibles.location.emplace_back(48, 0, 16, 16); // Key + atlasCollectibles.location.emplace_back(64, 0, 16, 16); // Javid + atlasCollectibles.location.emplace_back(80, 0, 16, 16); // Github + atlasCollectibles.location.emplace_back(96, 0, 16, 16); // Hopson + atlasCollectibles.location.emplace_back(112, 0, 16, 16); // Up + atlasCollectibles.location.emplace_back(128, 0, 16, 16); // Right + atlasCollectibles.location.emplace_back(144, 0, 16, 16); // Down + atlasCollectibles.location.emplace_back(160, 0, 16, 16); // Left + + + + + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_000.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_001.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_002.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_003.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_004.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_005.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_006.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_007.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_008.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_009.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_010.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_005.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_006.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_007.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_008.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_009.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_010.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_000.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_001.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_002.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_003.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_004.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_005.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_006.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_007.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_008.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_009.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_010.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_005.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_006.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_007.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_008.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_009.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_010.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_000.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_001.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_002.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_003.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_004.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_005.olcspr", &rpPlayer)); + + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_000.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_001.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_002.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_003.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_004.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_005.olcspr", &rpPlayer)); + + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_005.olcspr", &rpPlayer)); + + //sprBackdropMagetzub = new olc::Sprite("discovery_magetzub_bg.png"); + //sprBackdropHopson = new olc::Sprite("discovery_hopson_bg.png"); + //sprTitleScreen = new olc::Sprite("discovery_titlescreen.png"); + //sprStoryScreen = new olc::Sprite("discovery_story.png"); + //sprCreditScreen = new olc::Sprite("discovery_credits.png"); + //sprControlScreen = new olc::Sprite("discovery_story2.png"); + //sprCompleteScreen = new olc::Sprite("discovery_title_bg.png"); + + //sprBackdropMagetzub->SaveToPGESprFile("discres\\discovery_magetzub_bg.olcspr"); + //sprBackdropHopson->SaveToPGESprFile("discres\\discovery_hopson_bg.olcspr"); + //sprTitleScreen->SaveToPGESprFile("discres\\discovery_titlescreen.olcspr"); + //sprStoryScreen->SaveToPGESprFile("discres\\discovery_story.olcspr"); + //sprCreditScreen->SaveToPGESprFile("discres\\discovery_credits.olcspr"); + //sprControlScreen->SaveToPGESprFile("discres\\discovery_story2.olcspr"); + //sprCompleteScreen->SaveToPGESprFile("discres\\discovery_title_bg.olcspr"); + + sprBackdropMagetzub = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_bg.olcspr", &rpPlayer); + sprBackdropHopson = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_bg.olcspr", &rpPlayer); + sprTitleScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_titlescreen.olcspr", &rpPlayer); + sprStoryScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_story.olcspr", &rpPlayer); + sprCreditScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_credits.olcspr", &rpPlayer); + sprControlScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_story2.olcspr", &rpPlayer); + sprCompleteScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_title_bg.olcspr", &rpPlayer); + + rpPlayer.ClearPack(); + + nGameState = GS_TITLE; + + return true; + } + + int nTitleSelection = 0; + int nLevelSeed = 1; + int nLevelSize = 2; + + bool GameState_Title(float fElapsedTime) + { + controller.Update(fElapsedTime); + + DrawSprite(0, 0, sprTitleScreen); + + DrawString(300, 280, "Story & Help", nTitleSelection == 0 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 310, "Credits", nTitleSelection == 1 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 340, "Level: " + to_string(nLevelSeed), nTitleSelection == 2 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 370, "Size: " + to_string((int)pow(2, (nLevelSize+2))), nTitleSelection == 3 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 400, "Start", nTitleSelection == 4 ? olc::WHITE : olc::YELLOW, 2); + + if (GetKey(olc::Key::UP).bPressed || controller.GetButton(UP).bPressed) + { + nTitleSelection--; + if (nTitleSelection < 0) nTitleSelection = 4; + olc::SOUND::PlaySample(sndThump); + } + + if (GetKey(olc::Key::DOWN).bPressed || controller.GetButton(DOWN).bPressed) + { + nTitleSelection++; + if (nTitleSelection > 4 ) nTitleSelection = 0; + olc::SOUND::PlaySample(sndThump); + } + + if (GetKey(olc::Key::LEFT).bPressed || controller.GetButton(LEFT).bPressed) + { + if (nTitleSelection == 2) + { + nLevelSeed--; + if (nLevelSeed < 0) nLevelSeed = 999; + } + + if (nTitleSelection == 3) + { + nLevelSize--; + if (nLevelSize < 1) nLevelSize = 3; + } + } + + if (GetKey(olc::Key::RIGHT).bPressed || controller.GetButton(RIGHT).bPressed) + { + if (nTitleSelection == 2) + { + nLevelSeed++; + if (nLevelSeed > 999) nLevelSeed = 0; + } + + if (nTitleSelection == 3) + { + nLevelSize++; + if (nLevelSize > 3) nLevelSize = 1; + } + } + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + if (nTitleSelection == 0) nGameState = GS_STORY; + if (nTitleSelection == 1) nGameState = GS_CREDITS; + + if (nTitleSelection == 4) nGameState = GS_GENERATE; + } + + return true; + } + + bool GameState_Generating(float fElapsedTime) + { + fPlayerPosX = 2.0f; + fPlayerPosY = 2.0f; + fPlayerVelX = 0.0f; + fPlayerVelY = 0.0f; + fCameraPosX = 0.0f; + fCameraPosY = 0.0f; + bPlayerOnGround = false; + bPlayerTouchingWall = false; + bPlayerTouchingWallOld = false; + bCollisionLeft = false; + fTimeToJump = 0.0f; + fTimeToJumpMax = 0.25f; + fFaceDir = 1.0f; + nFloppyDisks = 0; + fGameTime = 0.0f; + fGameTimeMultiplier = 1.0f; + nHopsonTokens = 0; + nJavidTokens = 0; + nKeys = 0; + fEffectTime = 0.0f; + fModeTime = 0.0f; + + nChallengeMapSizeX = (int)pow(2, nLevelSize + 2); + nChallengeMapSizeY = (int)pow(2, nLevelSize + 2); + srand(nLevelSeed); + + + // Generate a boolean maze + bool *bTileMaze = nullptr; + int nMapWidth, nMapHeight; + printf("D\n"); + CreateMaze(bTileMaze, nChallengePathSizeX - 1, nChallengePathSizeY - 1, nChallengeMapSizeX, nChallengeMapSizeY, nMapWidth, nMapHeight); + + if (sprMiniMap != nullptr) delete sprMiniMap; + sprMiniMap = new olc::Sprite(nChallengeMapSizeX, nChallengeMapSizeY); + printf("D\n"); + // Create tilemap to match dimensions of generated maze + layerWorld.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + layerCollectibles.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + layerJavid.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + + printf("D\n"); + // Transfer over boolean maze to tilemap + for (int x = 0; x < layerWorld.nLayerWidth; x++) + for (int y = 0; y < layerWorld.nLayerHeight; y++) + { + layerWorld.GetTile(x, y)->exist = !bTileMaze[y*layerWorld.nLayerWidth + x]; + } + + printf("D\n"); + CreateBorderMap(); + + printf("D\n"); + delete[] bTileMaze; + printf("D\n"); + listKeys.clear(); + printf("D\n"); + // Add 100 Drops + for (int i = 0; i < 136; i++) + { + bool bDone = false; + do + { + int x = rand() % layerWorld.nLayerWidth; + int y = rand() % layerWorld.nLayerHeight; + if (!layerWorld.GetTile(x, y)->exist && !layerCollectibles.GetTile(x, y)->exist) + { + + layerCollectibles.GetTile(x, y)->exist = true; + if (i < 100) layerCollectibles.GetTile(x, y)->id = 1; // Place disks + if (i == 100) // Place Exit + { + layerCollectibles.GetTile(x, y)->id = 2; listKeys.push_back(make_pair(x, y)); + } + if (i > 100 && i <= 104)// Place Keys + { + layerCollectibles.GetTile(x, y)->id = 3; listKeys.push_back(make_pair(x, y)); + } + if (i > 105 && i <= 125) layerCollectibles.GetTile(x, y)->id = 5; // Place Githubs + if (i > 125 && i <= 130) layerCollectibles.GetTile(x, y)->id = 4; // Place Javids + if (i > 130 && i <= 135) layerCollectibles.GetTile(x, y)->id = 6; // Place Hopsons + bDone = true; + } + + } while (!bDone); + } + + olc::SOUND::PlaySample(sndTheme, true); + nGameState = GS_MAIN; + printf("Entering Main\n"); + + return true; + } + + bool GameState_Main(float fElapsedTime) + { + fGameTime += fElapsedTime * fGameTimeMultiplier; + + controller.Update(fElapsedTime); + animPlayer.Update(fElapsedTime); + + bool bStartWallJump = bPlayerTouchingWall & !bPlayerTouchingWallOld; + bPlayerTouchingWallOld = bPlayerTouchingWall; + + if (bStartWallJump) + { + fTimeToJump = fTimeToJumpMax; + olc::SOUND::PlaySample(sndWall); + } + + if (fTimeToJump >= 0.0f) + fTimeToJump -= fElapsedTime; + + bPlayerTouchingWall = false; + + // Handle Input + if (IsFocused()) + { + /*if (GetKey(olc::Key::F1).bReleased) nRenderMode = MODE_MAGETZUB; + if (GetKey(olc::Key::F2).bReleased) { + nRenderMode = MODE_JAVID; fModeTime = 500.0f; CalculateFlowMap(); + } + if (GetKey(olc::Key::F3).bReleased) nRenderMode = MODE_HOPSON; + + + if (GetKey(olc::Key::F5).bReleased) nRenderEffect = EFFECT_NONE; + if (GetKey(olc::Key::F6).bReleased) nRenderEffect = EFFECT_UGLYSWEDISHFISH; + if (GetKey(olc::Key::F7).bReleased) nRenderEffect = EFFECT_GORBIT; + if (GetKey(olc::Key::F8).bReleased) nRenderEffect = EFFECT_SEDIT;*/ + + /*if (GetKey(olc::Key::UP).bHeld || controller.GetButton(UP).bHeld) + { + fPlayerVelY = -2.0f; + } + + if (GetKey(olc::Key::DOWN).bHeld || controller.GetButton(DOWN).bHeld) + { + fPlayerVelY = 2.0f; + }*/ + + if (GetKey(olc::Key::LEFT).bHeld || controller.GetButton(LEFT).bHeld) + { + fPlayerVelX += (bPlayerOnGround ? -25.0f : -15.0f) *fElapsedTime; + fFaceDir = -1.0f; + } + + if (GetKey(olc::Key::RIGHT).bHeld || controller.GetButton(RIGHT).bHeld) + { + fPlayerVelX += (bPlayerOnGround ? 25.0f : 15.0f) *fElapsedTime; + fFaceDir = +1.0f; + } + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + if (bPlayerOnGround) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + + if (fTimeToJump >= 0.0f) + { + if (fPlayerVelX < 0 && !bCollisionLeft) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + + if (fPlayerVelX > 0 && bCollisionLeft) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + } + } + + + bFire = false; + if (GetKey(olc::Key::A).bPressed || controller.GetButton(X).bPressed) + { + //bFire = true; + + if (nRenderMode == MODE_HOPSON) + { + if (fFaceDir < 0) + { + int bx = fPlayerPosX; + int by = fPlayerPosY + 0.5f; + if (bx > 0 && bx < (layerWorld.nLayerWidth - 1) && by > 0 && by < (layerWorld.nLayerHeight - 1) && layerWorld.GetTile(bx, by)->exist) + { + layerWorld.GetTile(bx, by)->exist = false; + CreateBorderMap(); + } + } + else + { + int bx = fPlayerPosX + 1.0f; + int by = fPlayerPosY + 0.5f; + if (bx > 0 && bx < (layerWorld.nLayerWidth - 1) && by > 0 && by < (layerWorld.nLayerHeight - 1) && layerWorld.GetTile(bx, by)->exist) + { + layerWorld.GetTile(bx, by)->exist = false; + CreateBorderMap(); + } + + } + } + } + + if (GetKey(olc::Key::J).bPressed || controller.GetButton(Y).bPressed) + { + if (nRenderMode != MODE_JAVID && nJavidTokens > 0) + { + nJavidTokens--; + nRenderMode = MODE_JAVID; + fModeTime = 60.0f; + CalculateFlowMap(); + olc::SOUND::PlaySample(sndHelperChange); + } + } + + if (GetKey(olc::Key::H).bPressed || controller.GetButton(B).bPressed) + { + if (nRenderMode != MODE_HOPSON && nHopsonTokens > 0) + { + nHopsonTokens--; + nRenderMode = MODE_HOPSON; + fModeTime = 30.0f; + olc::SOUND::PlaySample(sndHelperChange); + } + } + } + + + fPlayerVelY += 20.0f * fElapsedTime; + + if (bPlayerOnGround) + { + + fPlayerVelX += -3.0f * fPlayerVelX * fElapsedTime; + if (fabs(fPlayerVelX) < 0.01f) + { + fPlayerVelX = 0.0f; + animPlayer.ChangeState(bFire ? "idle_fire" : "idle"); + } + else + { + animPlayer.ChangeState(bFire ? "run_fire" : "run"); + } + + } + else + { + if (!bFire) + { + if (fPlayerVelY < 0) + animPlayer.ChangeState("jump"); + else + animPlayer.ChangeState("fall"); + } + else + animPlayer.ChangeState("air_fire"); + } + + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 1) // Disk + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nFloppyDisks++; + olc::SOUND::PlaySample(sndDiskCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 2) // Exit + { + if (nKeys == 4) + { + // Game Completed + nGameState = GS_COMPLETE; + } + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 3) // Keys + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nKeys++; + + + // Remove key from list + listKeys.remove_if([&](pair &p) {return p.first == ((int)(fPlayerPosX + 0.5f)) && p.second == ((int)(fPlayerPosY + 0.5f)); }); + CalculateFlowMap(); + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 4) // Javid + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nJavidTokens++; + + //fModeTime = 30.0f; + //nRenderMode = MODE_JAVID; + + //if (nRenderMode == MODE_JAVID) + //{ + // // Javid is helpful and likes to simplify so help out the user + // CalculateFlowMap(); + //} + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 6) // Hopson + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nHopsonTokens++; + + /*fModeTime = 30.0f; + nRenderMode = MODE_HOPSON;*/ + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 5) // Github + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + fEffectTime = 10.0f; + switch (rand() % 4) + { + case 0: nRenderEffect = EFFECT_UGLYSWEDISHFISH; break; + case 1: nRenderEffect = EFFECT_SEDIT; break; + case 2: nRenderEffect = EFFECT_GORBIT; break; + case 3: nRenderEffect = EFFECT_BRANK; break; + } + fGameTimeMultiplier = 0.75f; + olc::SOUND::PlaySample(sndGithubPatch); + } + + if (fEffectTime > 0.0f) + { + fEffectTime -= fElapsedTime; + } + else + { + nRenderEffect = EFFECT_NONE; + fGameTimeMultiplier = 1.0f; + } + + + if (fModeTime > 0.0f) + { + fModeTime -= fElapsedTime; + } + else + { + nRenderMode = MODE_MAGETZUB; + //olc::SOUND::PlaySample(sndHelperChange); + } + + + if (fPlayerVelX > 10.0f) + fPlayerVelX = 10.0f; + + if (fPlayerVelX < -10.0f) + fPlayerVelX = -10.0f; + + if (fPlayerVelY > 100.0f) + fPlayerVelY = 100.0f; + + if (fPlayerVelY < -100.0f) + fPlayerVelY = -100.0f; + + sprMiniMap->SetPixel(fPlayerPosX / nChallengePathSizeX, fPlayerPosY / nChallengePathSizeY, olc::GREEN); + + float fNewPlayerPosX = fPlayerPosX + fPlayerVelX * fElapsedTime; + float fNewPlayerPosY = fPlayerPosY + fPlayerVelY * fElapsedTime; + + sprMiniMap->SetPixel(fNewPlayerPosX / nChallengePathSizeX, fNewPlayerPosY / nChallengePathSizeY, olc::WHITE); + + // Collision + float fOffset = 0.2f; + if (fPlayerVelX <= 0) + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fPlayerPosY + 0.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fPlayerPosY + 0.9f)->exist) + { + fNewPlayerPosX = (int)fNewPlayerPosX + 1 - fOffset; + fPlayerVelX = 0; + bPlayerTouchingWall = true; + bCollisionLeft = true; + } + } + else + { + if (layerWorld.GetTile(fNewPlayerPosX + 1.0f - fOffset, fPlayerPosY + 0.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 1.0f - fOffset, fPlayerPosY + 0.9f)->exist) + { + fNewPlayerPosX = (int)fNewPlayerPosX + fOffset; + fPlayerVelX = 0; + bPlayerTouchingWall = true; + bCollisionLeft = false; + } + } + + bPlayerOnGround = false; + if (fPlayerVelY <= 0) + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fNewPlayerPosY)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.9f - fOffset, fNewPlayerPosY)->exist) + { + fNewPlayerPosY = (int)fNewPlayerPosY + 1; + fPlayerVelY = 0; + } + } + else + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fNewPlayerPosY + 1.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.9f - fOffset, fNewPlayerPosY + 1.0f)->exist) + { + fNewPlayerPosY = (int)fNewPlayerPosY; + fPlayerVelY = 0; + bPlayerOnGround = true; + + } + } + + fPlayerPosX = fNewPlayerPosX; + fPlayerPosY = fNewPlayerPosY; + + fCameraPosX = fPlayerPosX; + fCameraPosY = fPlayerPosY; + + int nVisibleTilesX = ScreenWidth() / nTileSizeX; + int nVisibleTilesY = ScreenHeight() / nTileSizeY; + + // Calculate Top-Leftmost visible tile + float fOffsetX = fCameraPosX - (float)nVisibleTilesX / 2.0f; + float fOffsetY = fCameraPosY - (float)nVisibleTilesY / 2.0f; + + + // Clamp camera to game boundaries + if (fOffsetX < 0) fOffsetX = 0; + if (fOffsetY < 0) fOffsetY = 0; + if (fOffsetX > layerWorld.nLayerWidth - nVisibleTilesX) fOffsetX = layerWorld.nLayerWidth - nVisibleTilesX; + if (fOffsetY > layerWorld.nLayerHeight - nVisibleTilesY) fOffsetY = layerWorld.nLayerHeight - nVisibleTilesY; + + + + + + SetDrawTarget(backBuff); + //Clear(olc::VERY_DARK_BLUE); + + + if (nRenderMode == MODE_MAGETZUB) + { + fBackdropScaleX = (float)(sprBackdropMagetzub->width - ScreenWidth()) / (float)((layerWorld.nLayerWidth) + (float)nVisibleTilesX); + fBackdropScaleY = (float)(sprBackdropMagetzub->height - ScreenHeight()) / (float)((layerWorld.nLayerHeight) + (float)nVisibleTilesY); + DrawPartialSprite(0, 0, sprBackdropMagetzub, fOffsetX * fBackdropScaleX, fOffsetY * fBackdropScaleY, ScreenWidth(), ScreenHeight()); + + + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldMagetzub, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderMode == MODE_HOPSON) + { + fBackdropScaleX = (float)(sprBackdropHopson->width - ScreenWidth()) / (float)((layerWorld.nLayerWidth) + (float)nVisibleTilesX); + fBackdropScaleY = (float)(sprBackdropHopson->height - ScreenHeight()) / (float)((layerWorld.nLayerHeight) + (float)nVisibleTilesY); + DrawPartialSprite(0, 0, sprBackdropHopson, fOffsetX * fBackdropScaleX, fOffsetY * fBackdropScaleY, ScreenWidth(), ScreenHeight()); + + + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldHopson, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderMode == MODE_JAVID) + { + Clear(olc::BLACK); + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldJavid, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + + olc::TILE::DrawLayer(layerJavid, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + // Draw Player + + olc::GFX2D::Transform2D t; + t.Translate(-220.0f, 174.0f); + t.Scale(fFaceDir * 0.25f, 0.25f); + + t.Translate((fPlayerPosX - fOffsetX) * nTileSizeX + 32, (fPlayerPosY - fOffsetY) * nTileSizeY - 48); + + SetPixelMode(olc::Pixel::ALPHA); + animPlayer.DrawSelf(this, t); + SetPixelMode(olc::Pixel::NORMAL); + + + //this_thread::sleep_for(20ms); + + string s = "Disks: " + to_string(nFloppyDisks) + "/100\nJx9: " + to_string(nJavidTokens) + " Hop: " + to_string(nHopsonTokens) + "\nKeys: " + to_string(nKeys) + " /4"; + + + + DrawString(11, 11, s, olc::BLACK, 2); + DrawString(9, 9, s, olc::YELLOW, 2); + + SetPixelBlend(0.5f); + SetPixelMode(olc::Pixel::ALPHA); + DrawSprite(10, ScreenHeight() - (sprMiniMap->height + 4) * 3, sprMiniMap, 3); + SetPixelMode(olc::Pixel::NORMAL); + SetPixelBlend(1.0f); + + //Draw(10 + (fNewPlayerPosX / nChallengePathSizeX), ScreenHeight() - sprMiniMap->height - 8 + (fNewPlayerPosY / nChallengePathSizeY), olc::YELLOW); + + //DrawString(10, ScreenHeight() - (sprMiniMap->height - 16) * 2, "Map", olc::BLACK, 2); + //DrawString(9, ScreenHeight() - (sprMiniMap->height - 17) * 2, "Map", olc::YELLOW, 2); + + // Draw Time Information + int minutes = (int)fGameTime / 60; + int seconds = (int)fGameTime - (minutes * 60); + string sMinutes = (minutes <= 9 ? "0" : "") + to_string(minutes); + string sSeconds = (seconds <= 9 ? "0" : "") + to_string(seconds); + DrawString(ScreenWidth() - 158, 12, sMinutes + ":" + sSeconds, olc::BLACK, 3); + DrawString(ScreenWidth() - 160, 10, sMinutes + ":" + sSeconds, olc::YELLOW, 3); + + if (fGameTimeMultiplier != 1.0f) + { + DrawString(ScreenWidth() - 158, 50, "x0.75", olc::BLACK, 4); + DrawString(ScreenWidth() - 160, 53, "x0.75", olc::YELLOW, 4); + } + + SetDrawTarget(nullptr); + + + // UglySwedishFisk Mode + if (nRenderEffect == EFFECT_UGLYSWEDISHFISH) + { + int sparkles = 100 / fElapsedTime; + for (int i = 0; i < sparkles; i++) + { + int x = rand() % ScreenWidth(); + int y = rand() % ScreenHeight(); + Draw(x, y, backBuff->GetPixel(x, y)); + } + } + + if (nRenderEffect == EFFECT_GORBIT) + { + int sparkles = 10 / fElapsedTime; + for (int i = 0; i < sparkles; i++) + { + int x = rand() % 32; + int y = rand() % 32; + DrawPartialSprite(x * 32, y * 32, backBuff, x * 32, y * 32, 32, 32); + } + } + + + if (nRenderEffect == EFFECT_SEDIT) + { + SetPixelMode([](int x, int y, const olc::Pixel &s, const olc::Pixel &d) + { + olc::Pixel p = s; + if (y % 2) + { + float c = ((float)s.r * 1.5f + (float)s.g * 1.5f + (float)s.b * 1.5f) / 3.0f; + p.r = c; + p.g = c; + p.b = c; + } + return p; + }); + + DrawSprite(0, 0, backBuff); + + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderEffect == EFFECT_BRANK) + { + for (int i = 0; i < 4; i++) + { + int sx = rand() % 2; + int sy = rand() % 2; + int tx = rand() % 2; + int ty = rand() % 2; + DrawPartialSprite(0, 0, backBuff, ScreenWidth()/2,ScreenHeight()/2, ScreenWidth()/2, ScreenHeight()/2); + DrawPartialSprite(ScreenWidth()/2, 0, backBuff, 0, ScreenHeight() / 2, ScreenWidth() / 2, ScreenHeight() / 2); + DrawPartialSprite( 0,ScreenHeight()/2, backBuff, ScreenWidth()/2, 0, ScreenWidth() / 2, ScreenHeight() / 2); + DrawPartialSprite(ScreenWidth() / 2, ScreenHeight()/2, backBuff, 0, 0, ScreenWidth() / 2, ScreenHeight() / 2); + } + } + + + + // No effect mode + if (nRenderEffect == EFFECT_NONE) + DrawSprite(0, 0, backBuff); + + string sHelper = ""; + if (nRenderEffect == EFFECT_UGLYSWEDISHFISH) + sHelper = "UGLYSWEDISHFISH Is Patching Your Code!\nHonestly, It's better like this..."; + if (nRenderEffect == EFFECT_GORBIT) + sHelper = "GORBIT Is Patching Your Code!\nLook how beautiful my gifs are..."; + if (nRenderEffect == EFFECT_SEDIT) + sHelper = "SEDIT Is Patching Your Code!\nI'm only going to change one little thing... oh."; + if (nRenderEffect == EFFECT_BRANK) + sHelper = "BRANKEC Is Patching Your Code!\nNow it'll be at least 7623 FPS..."; + + DrawString(ScreenWidth() - 338, ScreenHeight() - 22, sHelper, olc::BLACK); + DrawString(ScreenWidth() - 339, ScreenHeight() - 23, sHelper, olc::YELLOW); + return true; + } + + bool GameState_Complete(float fElapsedTime) + { + + controller.Update(fElapsedTime); + DrawSprite(0, 0, sprCompleteScreen); + + DrawString(4, 10, "YOU SAVED SEDIT!", olc::YELLOW, 4); + + int minutes = (int)fGameTime / 60; + int seconds = (int)fGameTime - (minutes * 60); + string sMinutes = (minutes <= 9 ? "0" : "") + to_string(minutes); + string sSeconds = (seconds <= 9 ? "0" : "") + to_string(seconds); + string sTime = "Your Time: " + sMinutes + ":" + sSeconds; + DrawString(10, 200, sTime, olc::YELLOW, 2); + DrawString(10, 240, "OS Disks Found: " + to_string(nFloppyDisks) + "/100", olc::YELLOW, 2); + DrawString(10, 260, "Level: " + to_string(nLevelSeed) + " Size: " + to_string((int)pow(2,2+nLevelSize)), olc::YELLOW, 2); + DrawString(10, 300, "Jump to Title Screen?", olc::YELLOW, 2); + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nGameState = GS_TITLE; + } + + return true; + } + + int nStoryStage = 0; + + bool GameState_Story(float fElapsedTime) + { + controller.Update(fElapsedTime); + + if(nStoryStage == 0) + DrawSprite(0, 0, sprStoryScreen); + else + DrawSprite(0, 0, sprControlScreen); + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nStoryStage++; + if (nStoryStage == 2) + { + nGameState = GS_TITLE; + nStoryStage = 0; + } + } + + return true; + } + + bool GameState_Credits(float fElapsedTime) + { + controller.Update(fElapsedTime); + DrawSprite(0, 0, sprCreditScreen); + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nGameState = GS_TITLE; + } + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + + switch (nGameState) + { + case GS_LOADING: GameState_Loading(fElapsedTime); break; + case GS_TITLE: GameState_Title(fElapsedTime); break; + case GS_GENERATE: GameState_Generating(fElapsedTime); break; + case GS_MAIN: GameState_Main(fElapsedTime); break; + case GS_STORY: GameState_Story(fElapsedTime); break; + case GS_CREDITS: GameState_Credits(fElapsedTime); break; + case GS_COMPLETE: GameState_Complete(fElapsedTime); break; + } + + return true; + } +}; + +int main() +{ + Discovery demo; + if (demo.Construct(512, 448, 2, 2)) + demo.Start(); + return 0; +} diff --git a/Videos/SavingSedit/Zix_PGE_Controller.h b/Videos/SavingSedit/Zix_PGE_Controller.h new file mode 100644 index 0000000..c68dfed --- /dev/null +++ b/Videos/SavingSedit/Zix_PGE_Controller.h @@ -0,0 +1,224 @@ +#pragma once + +#ifdef WIN32 +#include +#include + +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 +} \ No newline at end of file diff --git a/Videos/SavingSedit/licence.txt b/Videos/SavingSedit/licence.txt new file mode 100644 index 0000000..be2bc61 --- /dev/null +++ b/Videos/SavingSedit/licence.txt @@ -0,0 +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 diff --git a/Videos/SavingSedit/olcPGEX_Graphics2D.h b/Videos/SavingSedit/olcPGEX_Graphics2D.h new file mode 100644 index 0000000..c77be85 --- /dev/null +++ b/Videos/SavingSedit/olcPGEX_Graphics2D.h @@ -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 +#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 \ No newline at end of file diff --git a/Videos/SavingSedit/olcPGEX_Sound.h b/Videos/SavingSedit/olcPGEX_Sound.h new file mode 100644 index 0000000..55094aa --- /dev/null +++ b/Videos/SavingSedit/olcPGEX_Sound.h @@ -0,0 +1,906 @@ +/* + olcPGEX_Sound.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Sound - v0.4 | + +-------------------------------------------------------------+ + + 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 +#include +#include + +#include +#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 +#endif + +#ifdef USE_OPENAL +#include +#include +#include +#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 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 func); + static void SetUserFilterFunction(std::function 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 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 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 m_bAudioThreadActive; + static std::atomic m_fGlobalTime; + static std::function funcUserSynth; + static std::function 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 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 func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function 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 SOUND::m_bAudioThreadActive{ false }; + std::atomic SOUND::m_fGlobalTime{ 0.0f }; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function 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 lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + if(m_AudioThread.joinable()) + 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 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; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock 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); + }; + + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + + 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 * (float)n, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; + + // 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 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(¶ms); + snd_pcm_hw_params_any(m_pPCM, params); + + // Set other parameters + snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED); + 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; + if(m_AudioThread.joinable()) + 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)(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample; + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; + + // 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; + if(m_AudioThread.joinable()) + 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 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 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 \ No newline at end of file diff --git a/Videos/SavingSedit/olcPGEX_TileMaps_new.h b/Videos/SavingSedit/olcPGEX_TileMaps_new.h new file mode 100644 index 0000000..b89a7c2 --- /dev/null +++ b/Videos/SavingSedit/olcPGEX_TileMaps_new.h @@ -0,0 +1,381 @@ +#pragma once +#include "olcPixelGameEngine.h" + +#include +#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> location; + }; + + public: + + template + 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 + static void DrawLayer(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int tiles_x, int tiles_y, int nScale = 1); + + template + static olc::Pixel GetLayerPixel(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float x, float y); + + template + static std::vector ExtractEdgesFromLayer(olc::TILE::Layer &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 + TILE::Layer::Layer() + { + + } + + + + template + void TILE::Layer::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 + olc::rcode TILE::Layer::LoadFromFile(std::string filename) + { + return olc::FAIL; + } + + template + olc::rcode TILE::Layer::SaveToFile(std::string filename) + { + return olc::FAIL; + } + + template + T* TILE::Layer::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 + void TILE::DrawLayer(olc::TILE::Layer &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 + olc::Pixel TILE::GetLayerPixel(olc::TILE::Layer &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 + std::vector TILE::ExtractEdgesFromLayer(olc::TILE::Layer &layer, int sx, int sy, int width, int height) + { + enum + { + NORTH = 0, + EAST = 1, + SOUTH = 2, + WEST = 3 + }; + + std::vector 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; + } + +} diff --git a/Videos/SavingSedit/olcPixelGameEngine.h b/Videos/SavingSedit/olcPixelGameEngine.h new file mode 100644 index 0000000..a6f36ed --- /dev/null +++ b/Videos/SavingSedit/olcPixelGameEngine.h @@ -0,0 +1,2317 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.18 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprising and wonderful success for me, + and I'm delighted how people have reacted so positively towards it, so thanks + for that. + + However, there are limitations that I simply cannot avoid. Firstly, I need to + maintain several different versions of it to accommodate users on Windows7, + 8, 10, Linux, Mac, Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities and the effect + is becoming underwhelming. The engine itself is not slow at all, but the process + that Windows uses to draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours and glyphs. Sadly + I have no control over this, and recent videos that are extremely graphical + (for a command prompt :P ) have been dipping to unacceptable framerates. As + the channel has been popular with aspiring game developers, I'm concerned that + the visual appeal of the command prompt is perhaps limited to us oldies, and I + dont want to alienate younger learners. Finally, I'd like to demonstrate many + more algorithms and image processing that exist in the graphical domain, for + which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look and feel to the + programmer is almost identical, so all of my existing code from the videos is + easily portable, and the programmer uses this file in exactly the same way. But + I've decided that rather than just build a command prompt emulator, that I + would at least harness some modern(ish) portable technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is written in a + cross-platform style, uses modern(ish) C++ conventions and most importantly, + renders much much faster. I will use this version when my applications are + predominantly graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command prompt silliness to + come yet, but evolution is important!! + + 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 + 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 + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Rapberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + Thanks + ~~~~~~ + 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 40K YouTube followers, + 22 Patreons and 2.6K Discord server members who give me the motivation to keep + going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest + Marti Morta........Gris + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus + #if !defined _WIN32_WINNT + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //================================================================================== + + template + struct v2d_generic + { + T x = 0; + T y = 0; + + inline v2d_generic() : x(0), y(0) { } + inline v2d_generic(T _x, T _y) : x(_x), y(_y) { } + inline v2d_generic(const v2d_generic& v) : x(v.x), y(v.y){ } + inline T mag() { return sqrt(x * x + y * y); } + inline v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); } + inline v2d_generic perp() { return v2d_generic(-y, x); } + inline T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; } + inline T cross(const v2d_generic& rhs) { return this->x * rhs.y - this->y * rhs.x; } + inline v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y);} + inline v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y);} + inline v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + inline v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + inline v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + inline v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + 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 :( */ } + }; + + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + + typedef v2d_generic vi2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + //============================================================= + + + class ResourcePack + { + public: + 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)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + bool SetPixel(int32_t x, int32_t y, Pixel p); + + Pixel Sample(float x, float y); + Pixel SampleBL(float u, float v); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen = false); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + // Get Mouse Wheel Delta + int32_t GetMouseWheel(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + int32_t nMouseWheelDelta = 0; + int32_t nMousePosXcache = 0; + int32_t nMousePosYcache = 0; + int32_t nMouseWheelDeltaCache = 0; + int32_t nWindowWidth = 0; + int32_t nWindowHeight = 0; + int32_t nViewX = 0; + int32_t nViewY = 0; + int32_t nViewW = 0; + int32_t nViewH = 0; + bool bFullScreen = false; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + + +#ifdef _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 + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + If all else fails, create a file called "olcPixelGameEngine.cpp" with the following + two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying + about defining things. Dont forget to include that cpp file as part of your build! + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + + std::wstring ConvertS2W(std::string s) + { +#ifdef _WIN32 + 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); + std::wstring w(buffer); + delete[] buffer; + return w; +#else + return L"SVN FTW!"; +#endif + } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _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 + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y*width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + uint32_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + bFullScreen = full_screen; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + 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 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::GetMouseWheel() + { + return nMouseWheelDelta; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) + { + pattern = (pattern << 1) | (pattern >> 31); + return pattern & 1; + }; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + if (mask & 0x01) Draw(x + x0, y - y0, p); + if (mask & 0x02) Draw(x + y0, y - x0, p); + if (mask & 0x04) Draw(x + y0, y + x0, p); + if (mask & 0x08) Draw(x + x0, y + y0, p); + if (mask & 0x10) Draw(x - x0, y + y0, p); + if (mask & 0x20) Draw(x - y0, y + x0, p); + if (mask & 0x40) Draw(x - y0, y - x0, p); + if (mask & 0x80) Draw(x - x0, y - y0, p); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = nScreenWidth * nPixelWidth; + int32_t wh = nScreenHeight * nPixelHeight; + float wasp = (float)ww / (float)wh; + + nViewW = (int32_t)nWindowWidth; + nViewH = (int32_t)((float)nViewW / wasp); + + if (nViewH > nWindowHeight) + { + nViewH = nWindowHeight; + nViewW = (int32_t)((float)nViewH * wasp); + } + + nViewX = (nWindowWidth - nViewW) / 2; + nViewY = (nWindowHeight - nViewH) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + nWindowWidth = x; + nWindowHeight = y; + olc_UpdateViewport(); + + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { + nMouseWheelDeltaCache += delta; + } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // 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; + } + + nMousePosXcache = (int32_t)(((float)x / (float)(nWindowWidth - (nViewX * 2)) * (float)nScreenWidth)); + nMousePosYcache = (int32_t)(((float)y / (float)(nWindowHeight - (nViewY * 2)) * (float)nScreenHeight)); + + if (nMousePosXcache >= (int32_t)nScreenWidth) + nMousePosXcache = nScreenWidth - 1; + if (nMousePosYcache >= (int32_t)nScreenHeight) + nMousePosYcache = nScreenHeight - 1; + + if (nMousePosXcache < 0) + nMousePosXcache = 0; + if (nMousePosYcache < 0) + nMousePosYcache = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // 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 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + glClear(GL_COLOR_BUFFER_BIT); // Thanks Benedani! + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + nWindowWidth = xce.width; + nWindowHeight = xce.height; + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + XKeyEvent *e = (XKeyEvent *)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + XKeyEvent *e = (XKeyEvent *)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = true; break; + case 2: pMouseNewState[2] = true; break; + case 3: pMouseNewState[1] = true; break; + case 4: olc_UpdateMouseWheel(120); break; + case 5: olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = false; break; + case 2: pMouseNewState[2] = false; break; + case 3: pMouseNewState[1] = false; break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + + // Cache mouse coordinates so they remain + // consistent during frame + nMousePosX = nMousePosXcache; + nMousePosY = nMousePosYcache; + + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + glViewport(nViewX, nViewY, nViewW, nViewH); + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + +#ifdef _WIN32 + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); + }; + } gdistartup; +#endif + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + nWindowWidth = (LONG)nScreenWidth * (LONG)nPixelWidth; + nWindowHeight = (LONG)nScreenHeight * (LONG)nPixelHeight; + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE;// | WS_THICKFRAME; + + int nCosmeticOffset = 30; + nViewW = nWindowWidth; + nViewH = nWindowHeight; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + nCosmeticOffset = 0; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return NULL; + nWindowWidth = mi.rcMonitor.right; + nWindowHeight = mi.rcMonitor.bottom; + + + } + + olc_UpdateViewport(); + + // Keep client size as requested + RECT rWndRect = { 0, 0, nWindowWidth, nWindowHeight }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + glViewport(nViewX, nViewY, nViewW, nViewH); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval) wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: + { + sge->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); + return 0; + } + case WM_MOUSEWHEEL: + { + sge->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; return 0; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + if (glSwapIntervalEXT) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + 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"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif diff --git a/Videos/car_top1.png b/Videos/car_top1.png new file mode 100644 index 0000000..15ceb1d Binary files /dev/null and b/Videos/car_top1.png differ diff --git a/Videos/isometric_demo.png b/Videos/isometric_demo.png new file mode 100644 index 0000000..797ef4c Binary files /dev/null and b/Videos/isometric_demo.png differ diff --git a/Videos/light_cast.png b/Videos/light_cast.png new file mode 100644 index 0000000..4ac2ce8 Binary files /dev/null and b/Videos/light_cast.png differ diff --git a/Videos/logo_long.png b/Videos/logo_long.png new file mode 100644 index 0000000..e4ca5b3 Binary files /dev/null and b/Videos/logo_long.png differ diff --git a/Videos/mountains.obj b/Videos/mountains.obj new file mode 100644 index 0000000..6bb8954 --- /dev/null +++ b/Videos/mountains.obj @@ -0,0 +1,7302 @@ +# Blender v2.79 (sub 0) OBJ File: '' +# www.blender.org +v -43.592037 7.219297 -21.717901 +v -41.717117 7.750917 -20.780441 +v -42.342102 5.160596 -23.592819 +v -44.217018 9.241996 -21.092922 +v -72.028442 5.974188 -68.903564 +v -73.278381 7.996269 -68.278580 +v -71.090981 6.918389 -66.091179 +v -72.340919 4.623488 -72.965897 +v -74.528343 6.804868 -72.028442 +v -68.591080 4.523389 -68.591080 +v -68.903564 3.463808 -72.965897 +v -73.590881 7.987109 -67.028641 +v -4.218580 10.193540 -2.343662 +v -3.906100 9.939019 -3.281122 +v -6.406000 8.828799 -2.968642 +v -4.843560 6.084661 -0.156241 +v -2.343660 12.701460 -2.656142 +v -6.093520 9.363459 -5.156042 +v -7.343460 10.057420 -2.343662 +v 8.905900 0.877183 15.780640 +v 13.905701 1.795742 12.655760 +v 7.655960 1.314801 9.530880 +v 12.343260 1.345923 21.405420 +v 17.343081 2.610563 16.093121 +v -9.530880 1.044402 11.405801 +v -11.718301 1.406962 12.968240 +v -6.093520 0.700782 13.280740 +v -7.343460 1.823221 7.030980 +v -12.030781 3.174521 6.718480 +v -13.593220 1.449682 8.905900 +v -17.968040 0.566503 15.468140 +v -5.156040 1.385601 8.593420 +v 33.592442 8.749445 33.904919 +v 34.842381 8.469306 35.467361 +v 35.154881 7.396305 32.967461 +v 32.029999 11.430706 32.654980 +v 31.717520 11.993446 35.779858 +v -60.778881 2.869342 10.155860 +v -62.028816 4.524002 9.530879 +v -65.153702 2.476283 16.405600 +v -57.966480 1.941622 11.405801 +v -60.466381 3.129961 9.218379 +v -63.903763 6.527781 6.405999 +v -55.779079 14.997571 -49.841805 +v -54.841599 12.433493 -48.904343 +v -55.154099 11.735271 -51.716740 +v -57.341499 16.631474 -47.966885 +v 17.030581 18.012098 -16.093124 +v 16.405600 18.414919 -16.405603 +v 16.093121 15.788599 -14.218204 +v 18.593021 21.878038 -15.468143 +v 17.968040 18.273939 -16.718103 +v 19.530479 22.064796 -17.655563 +v 17.030581 17.594618 -17.030584 +v 18.593021 20.779415 -17.655563 +v 17.343081 18.992317 -17.968044 +v 18.905521 20.914297 -14.843184 +v 20.155460 25.721397 -15.780643 +v 19.530479 6.872031 70.153519 +v 16.405600 6.930011 68.278580 +v 16.405600 5.008032 73.278381 +v 20.780439 5.459071 66.091179 +v -66.403664 17.698992 64.841217 +v -65.466202 19.049089 64.216240 +v -67.341118 15.830710 62.966274 +v -67.653595 18.986233 65.778679 +v -67.028641 19.922493 67.028641 +v -64.216240 17.682510 65.153702 +v -64.528740 13.321570 62.341312 +v -68.278580 18.541889 63.278774 +v -65.778679 17.654449 62.341312 +v -62.028816 15.018351 65.466202 +v -61.091362 12.651411 62.966274 +v 35.467361 7.717345 30.467558 +v 38.904739 5.250325 30.467560 +v 34.529900 10.136765 28.592638 +v 33.279961 10.330864 30.155077 +v 12.030781 18.377075 -18.905525 +v 12.655760 22.685535 -18.905525 +v 12.655760 24.728355 -19.842983 +v 11.405801 16.412376 -18.280542 +v 12.968240 17.966316 -18.280544 +v 12.655760 15.385757 -16.405602 +v 14.530680 18.734735 -17.968044 +v 12.343260 24.668556 -20.155464 +v 59.216442 8.501028 -70.153519 +v 58.278980 10.758088 -68.903564 +v 60.466381 10.145909 -68.591080 +v 59.216442 7.038008 -72.653419 +v 56.716541 9.945108 -71.090981 +v 20.467960 6.322693 -36.717319 +v 21.092920 8.413754 -37.342300 +v 20.155460 6.730414 -39.217220 +v 21.405420 5.674515 -33.279961 +v 23.280338 7.782654 -35.779861 +v 18.905521 5.764234 -39.217220 +v 22.655361 9.045453 -38.279758 +v -17.030581 13.816570 60.778877 +v -18.593021 12.312671 62.966274 +v -16.093121 15.372951 62.653797 +v -18.593021 12.164350 59.841415 +v 66.716141 8.482108 -75.465797 +v 67.028641 8.554729 -72.028442 +v 68.903564 9.040567 -73.903358 +v 65.466202 6.336107 -76.090782 +v 63.591263 6.982468 -72.028442 +v 9.218380 7.417672 75.153320 +v 10.468340 4.138272 77.028244 +v 11.718301 5.040372 72.340919 +v 8.280920 6.615072 73.903358 +v 8.280920 9.280473 75.465797 +v 39.842201 9.461128 52.966682 +v 40.779663 12.272389 54.841595 +v 42.967079 7.984689 52.341698 +v 38.592239 11.705989 53.279182 +v -78.590683 15.604871 -50.466785 +v -78.590683 15.278332 -52.654202 +v -79.528137 16.494152 -49.216824 +v -77.028244 17.339493 -50.466785 +v -41.717117 10.386397 -15.155662 +v -42.029602 13.994178 -12.343262 +v -39.217220 9.993337 -14.843182 +v -41.717117 7.994437 -18.280542 +v -45.779461 9.309137 -17.968042 +v 42.654579 10.520681 9.218378 +v 43.279564 13.113441 7.968438 +v 41.404640 13.817182 7.655957 +v 42.654579 7.778382 11.718300 +v 45.154480 6.359322 9.843359 +v 43.904537 11.718181 7.343458 +v 41.092140 14.035683 8.905898 +v 40.779663 11.514942 11.093318 +v -78.903160 9.183393 -42.029602 +v -74.215858 11.482574 -43.279564 +v -77.653221 11.314133 -45.154480 +v -79.840637 8.479673 -39.842201 +v -73.903358 10.220373 -38.279758 +v 1.718680 4.506929 56.404041 +v 2.968640 4.825529 54.216644 +v -1.406200 4.593589 56.091560 +v 0.156240 6.091990 59.841419 +v 3.906100 4.851170 59.528919 +v -61.091362 11.665696 -18.593023 +v -62.653801 11.849397 -17.343082 +v -59.528919 13.122598 -17.030582 +v -63.278778 10.545097 -20.155462 +v -20.467960 2.636806 40.154682 +v -18.905521 3.925267 40.467182 +v -18.280540 1.597386 34.842381 +v -20.155460 4.284747 44.217018 +v -16.718100 3.454687 39.842201 +v -17.030581 5.504227 43.592037 +v 77.028244 9.475764 27.655159 +v 79.215660 8.433905 30.155077 +v 79.528137 12.050824 25.780237 +v 75.465797 7.164384 27.030199 +v 77.028244 8.752504 30.467558 +v -21.092920 12.119189 56.404037 +v -22.655361 15.489529 55.466576 +v -22.655361 14.562410 56.716537 +v -21.717899 9.918268 52.341698 +v -22.967861 12.523849 54.216640 +v -72.028442 12.550699 -1.406202 +v -71.715958 16.973280 -2.968643 +v -73.278381 15.647599 -3.281123 +v -72.028442 9.946340 0.468738 +v -69.216042 11.165200 -0.156242 +v -40.467182 0.292464 24.530300 +v -40.154682 0.489005 30.780039 +v -32.654980 0.408424 26.717701 +v -49.841801 0.393784 24.530300 +v -46.091961 0.575665 32.342480 +v 2.031180 3.637149 -66.403664 +v 4.218580 5.840509 -67.028641 +v 3.906100 5.487729 -68.903564 +v 1.406200 3.736029 -65.466202 +v 4.843560 3.941109 -66.091179 +v -0.156240 2.641069 -65.778679 +v -4.218580 2.169889 -66.403664 +v 0.468740 2.264489 -63.903763 +v 2.343660 1.220171 -55.779079 +v 4.843560 2.782670 -63.591263 +v 0.781220 3.949649 -69.216042 +v -3.906100 3.189168 -69.528542 +v -78.590683 11.596707 -74.215858 +v -78.590683 10.324747 -73.903358 +v -76.403259 9.301188 -73.590881 +v -78.903160 6.604668 -75.465797 +v -78.903160 11.970248 -74.215858 +v -25.780239 17.374910 58.591457 +v -26.405220 13.718309 60.466377 +v -22.967861 14.039350 61.403858 +v -26.717701 17.066669 58.278976 +v -23.592819 15.753809 57.966476 +v -21.717899 15.050089 58.278976 +v 17.343081 5.401688 45.779461 +v 15.468140 5.347367 46.404438 +v 17.968040 5.174648 48.591862 +v 21.717899 7.566607 44.217018 +v 18.280540 5.947347 42.029602 +v 15.468140 4.885347 42.654579 +v 15.155660 5.759367 40.154682 +v 11.718301 3.733007 47.029419 +v 12.030781 3.998509 55.466579 +v 11.093320 3.336887 42.029602 +v 5.781020 2.985328 47.966881 +v -7.655960 0.360194 -39.217220 +v -7.343460 0.712375 -33.904919 +v -0.156240 0.479214 -39.842201 +v -8.593420 0.477992 -50.779263 +v -20.467960 0.496913 -42.342102 +v -7.030980 1.775595 -27.655161 +v -7.343460 3.554776 -22.655361 +v -2.031180 2.564776 -26.405220 +v -15.780640 0.896095 -31.405020 +v 0.468740 1.364835 -31.092539 +v -15.155660 0.516454 -36.092342 +v 28.592640 20.960678 -5.781024 +v 27.655161 19.381720 -5.156044 +v 28.280140 19.276741 -3.906103 +v 28.280140 24.053938 -7.343463 +v 27.655161 27.377279 -7.030984 +v 57.966480 15.252089 -67.966095 +v 58.278980 12.750270 -66.716141 +v 59.841419 14.639909 -67.653595 +v 57.029018 17.440788 -67.966095 +v -75.153320 9.173630 -66.716141 +v -74.840820 9.166908 -67.966095 +v -76.715759 12.365149 -67.341118 +v -74.528343 10.196569 -64.841217 +v -75.153320 9.649710 -63.591263 +v -77.340744 10.533489 -65.466202 +v -76.715759 12.352930 -60.153904 +v -72.965897 10.417510 -58.591461 +v -70.778481 7.550710 -62.966278 +v 27.655161 17.446924 25.780237 +v 26.092739 12.785084 24.842777 +v 26.405220 13.947186 28.280138 +v 28.592640 23.253765 26.092735 +v 28.905121 26.212124 24.842775 +v 29.217621 24.425644 27.030197 +v 30.155079 31.552063 25.467754 +v -64.216240 18.429579 -13.905704 +v -65.153702 13.386257 -15.780642 +v -66.716141 16.926899 -14.843183 +v -63.591263 18.576658 -12.968243 +v -62.028816 14.967677 -14.843183 +v -62.966278 20.025019 -11.093323 +v -60.153900 17.860718 -11.718304 +v 32.654980 11.511279 -7.030982 +v 35.154881 8.746398 -8.905901 +v 32.029999 11.583899 -9.218382 +v 31.092539 16.179821 -5.468543 +v 33.592442 11.193899 -3.593602 +v 30.780039 15.104999 -4.531083 +v 29.842579 17.533579 -6.406003 +v 44.529499 14.671669 61.091358 +v 43.592037 14.399451 62.653797 +v 45.154480 17.404810 62.966274 +v 42.654579 14.303009 60.153896 +v 77.028244 9.135191 66.716141 +v 76.403259 10.243591 67.653595 +v 79.528137 8.205631 68.278580 +v 77.965698 10.110531 65.153702 +v 74.528343 8.087231 66.716141 +v 43.279564 5.751409 -68.903564 +v 43.592037 7.511029 -66.716141 +v 48.279362 5.408389 -68.903564 +v 42.342102 4.276808 -74.528343 +v 39.842201 6.200009 -67.966095 +v 45.779461 8.067670 -64.216240 +v 42.029602 9.137010 -62.028816 +v 50.779263 15.336312 -51.091766 +v 51.716736 14.375011 -51.716740 +v 49.529320 17.740473 -52.654202 +v 52.654198 10.756872 -48.904339 +v 53.591660 11.793250 -63.903767 +v 53.904144 14.259049 -64.528740 +v 52.341698 9.935949 -64.216240 +v 53.904144 13.385029 -62.653805 +v 55.154099 18.601070 -63.591267 +v 8.280920 1.303805 27.967661 +v 10.155860 2.186985 30.467560 +v 14.530680 3.302705 28.280140 +v 5.156040 0.692844 23.905319 +v 5.781020 1.267185 29.842579 +v -59.216442 16.986692 -49.216824 +v -58.591461 16.724873 -51.716740 +v -61.403862 14.082671 -51.716740 +v -59.216442 20.266113 -48.591866 +v -57.966480 18.179932 -48.904343 +v -61.091362 16.091932 -49.216824 +v -59.528919 20.107412 -47.341904 +v -57.341499 18.654793 -50.466785 +v -39.217220 9.685129 58.903961 +v -37.654778 9.489809 56.091560 +v -40.154682 7.838809 55.779079 +v -39.842201 11.240891 60.153900 +v -36.404819 11.304990 60.778881 +v -39.842201 12.194271 62.341312 +v -40.467182 15.583530 64.841217 +v -37.654778 15.006751 63.903759 +v -42.967079 11.537530 62.341316 +v -34.842381 9.706490 57.341499 +v -41.717117 13.167771 64.528740 +v -67.028641 7.408514 -31.092541 +v -64.528740 7.367615 -28.592642 +v -64.216240 8.447315 -32.029999 +v -71.090981 7.398735 -31.092541 +v -70.153519 7.038636 -25.780241 +v -46.404438 10.272257 -20.155462 +v -47.341900 8.581597 -20.467962 +v -48.279362 12.420697 -19.218002 +v 19.218000 6.339173 -44.217018 +v 19.530479 8.181813 -42.967079 +v 21.405420 6.087713 -44.841999 +v 17.343081 4.892653 -43.592037 +v 23.905319 9.056451 -56.716541 +v 25.155260 7.972471 -53.904144 +v 27.030201 11.294590 -55.779079 +v 20.467960 5.849671 -56.716541 +v 20.467960 5.612251 -53.904144 +v 17.655560 4.949410 -58.278980 +v 18.280540 4.708931 -55.154099 +v 76.403259 8.015810 59.216442 +v 75.465797 8.505310 57.653999 +v 71.403458 5.561609 59.528919 +v 79.215660 9.561211 62.966278 +v 79.215660 9.929270 57.653999 +v -23.592819 17.117950 55.466576 +v 13.905701 4.732747 40.154682 +v 20.467960 7.685006 39.529701 +v 17.343081 6.783526 37.654778 +v 18.905521 9.513605 35.779861 +v 15.468140 7.235186 35.154881 +v 20.780439 10.828906 36.717319 +v -42.654579 7.238830 -61.403862 +v -42.654579 9.610031 -60.153900 +v -41.404640 6.924490 -59.841419 +v -44.529499 5.880190 -64.216240 +v -44.217018 9.165091 -60.153900 +v -42.654579 10.426071 -59.528919 +v -41.717117 8.107350 -58.903961 +v -42.342102 4.417190 -65.153702 +v -38.592239 5.133730 -59.841419 +v -37.342300 12.326710 66.716141 +v -35.779861 14.638090 66.716141 +v -36.092342 13.681690 65.778679 +v -37.967258 9.736391 68.903564 +v -34.842381 11.491152 68.903564 +v -72.653419 10.199653 73.278381 +v -74.528343 12.541552 72.028442 +v -73.278381 9.138852 74.215858 +v -71.403458 13.668872 72.340919 +v 32.029999 8.912393 -47.654400 +v 32.967461 7.559873 -42.967079 +v 37.654778 10.871613 -47.341900 +v 29.530102 7.300473 -44.841999 +v -15.780640 12.874189 59.841415 +v -16.718100 11.807909 59.216438 +v -14.218201 10.387630 60.778881 +v -14.843180 9.004589 57.966480 +v 22.655361 8.949630 -66.091179 +v 22.655361 9.046069 -62.653801 +v 26.092739 8.988090 -64.216240 +v 22.342880 9.861488 -68.903564 +v 20.155460 8.854409 -66.716141 +v -60.466381 20.878897 -8.905904 +v -60.153900 23.482637 -10.155864 +v -60.778881 22.566519 -9.843364 +v -60.466381 23.118877 -8.280924 +v -58.278980 19.826059 -8.280923 +v 8.280920 3.565761 5.156040 +v 7.655960 2.550741 6.718480 +v 12.968240 3.972241 4.843559 +v 5.781020 4.899981 2.656139 +v 17.343081 5.818550 -60.466381 +v 17.030581 8.568170 -62.341316 +v 14.530680 5.705009 -63.278778 +v 18.593021 7.872970 -61.716343 +v 19.218000 11.044950 -62.653801 +v 11.405801 3.407050 -61.403862 +v -14.530680 12.717331 70.778481 +v -13.280740 13.316092 71.715958 +v -12.030781 10.005551 68.903564 +v -16.405600 14.721711 69.528542 +v -17.030581 13.315471 71.403458 +v 27.655161 11.035188 -78.590683 +v 27.342680 12.077047 -76.403259 +v 30.467560 9.257848 -75.778297 +v 22.967861 17.020267 -79.528137 +v 25.155260 14.803488 -76.715759 +v -31.092539 5.502989 -69.216042 +v -31.405020 5.808168 -71.715958 +v -33.279961 4.650329 -70.465996 +v -29.217621 4.039369 -67.028641 +v -27.342680 5.737969 -68.278580 +v 29.530102 8.375294 -41.404640 +v 30.780039 7.457933 -41.092140 +v 27.967661 7.137513 -40.467182 +v 29.842579 9.171793 -40.154682 +v 25.155260 6.734073 -40.467182 +v 27.967661 7.211354 -38.592239 +v 18.593021 6.376411 -52.029221 +v 11.093320 2.440271 -56.404041 +v 20.155460 5.877132 -50.154301 +v 16.405600 4.265811 -51.716736 +v 18.905521 6.899472 -49.529320 +v 22.967861 6.989812 -51.091763 +v 20.780439 5.742252 -47.966881 +v -31.405020 10.322928 50.466782 +v -30.467560 11.002249 51.716736 +v -30.780039 7.970048 49.216820 +v -33.279961 9.120548 52.654198 +v -53.904144 5.948573 77.965698 +v -56.716541 9.657652 76.715759 +v -57.966480 7.762533 77.653221 +v -51.716736 4.699792 77.340744 +v -56.091560 8.193433 75.465797 +v -58.591461 9.911572 75.153320 +v -57.341499 9.780951 72.653419 +v -60.153900 12.866252 75.465797 +v -60.153900 10.602472 77.028244 +v -60.153900 8.387513 79.215660 +v -51.091763 5.747152 74.215858 +v -54.216644 10.115412 71.090981 +v -60.153900 9.155952 77.965698 +v -77.965698 9.763836 -24.530302 +v -73.903358 8.284976 -23.592821 +v -77.653221 9.142515 -27.655163 +v -79.528137 11.398956 -23.280340 +v -77.653221 12.153357 -20.467962 +v 28.592640 13.139676 -16.405602 +v 29.217621 16.149918 -13.905704 +v 31.405020 10.865519 -14.218203 +v 27.967661 12.766137 -17.968042 +v 26.405220 16.767597 -16.093122 +v 27.030201 6.215275 -34.217419 +v 28.905121 7.020315 -35.467361 +v 25.467760 8.787894 -36.404819 +v 28.280140 5.820994 -31.405020 +v 44.529499 13.845850 -59.841423 +v 45.154480 12.094150 -62.028820 +v 45.779461 12.773471 -60.153904 +v -0.156240 6.471608 -72.653419 +v 18.905521 8.344186 34.217419 +v 19.842979 11.142026 34.529900 +v 20.155460 9.050965 33.592442 +v 16.093121 6.467366 33.904919 +v 20.780439 13.727446 35.467358 +v 18.593021 6.283646 31.717518 +v 77.340744 8.082350 57.341499 +v 76.403259 7.236409 55.154099 +v 4.218580 13.917877 -16.405602 +v 3.593600 15.769677 -17.968042 +v 2.343660 15.466336 -15.468143 +v 5.468540 13.578518 -14.218203 +v 5.781020 13.220237 -17.655561 +v 4.843560 4.939672 68.903564 +v 9.218380 4.902431 70.778481 +v 11.093320 4.691251 66.091179 +v 3.281120 5.732512 71.715958 +v 79.215660 8.988091 -60.153900 +v 79.215660 13.883070 -62.966282 +v 78.590683 9.967690 -60.778881 +v 79.528137 5.850291 -56.404041 +v 79.528137 15.219130 -66.091179 +v 77.340744 8.867850 -59.528919 +v 78.278198 15.047629 -62.966282 +v -2.343660 12.951698 -3.593602 +v -2.656140 13.770780 -6.093523 +v 28.280140 24.132059 -10.780824 +v 27.655161 21.652218 -10.468344 +v 28.280140 26.330538 -10.155864 +v 28.592640 24.611177 -12.343264 +v 27.655161 20.483997 -12.655763 +v -10.780820 12.827191 75.778297 +v -12.655760 15.020793 76.090782 +v -11.093320 12.819873 76.715759 +v -11.718301 12.221732 73.278381 +v 48.904339 3.875199 -3.593601 +v 41.404640 6.200019 -2.968641 +v 42.342102 6.423400 0.468739 +v 48.904339 4.830998 -7.655961 +v 41.717117 6.531439 -6.718481 +v 51.091763 5.044638 -7.968441 +v -46.716919 8.111629 -64.216240 +v -45.466980 5.912529 -64.841217 +v -48.591862 7.343809 -66.403664 +v -47.654400 6.919610 -63.278778 +v -46.091961 8.434489 -63.591263 +v -51.091763 6.134709 -64.216240 +v -49.216820 6.706590 -60.466381 +v -72.340919 11.110874 -37.967258 +v -69.841019 10.758713 -38.279758 +v -71.403458 14.175433 -40.467186 +v -71.403458 8.444274 -33.904919 +v 79.215660 7.639845 33.592442 +v 78.590683 7.091745 35.154881 +v 79.528137 9.898126 36.717319 +v 77.340744 6.298285 33.279961 +v 68.903564 2.612993 -44.217018 +v 67.966095 4.443432 -47.966881 +v 65.153702 3.122633 -44.529499 +v 70.465996 2.064293 -41.092140 +v 74.528343 1.655973 -42.342102 +v 65.153702 4.752872 -48.279362 +v 62.028816 6.785952 -49.529320 +v 67.028641 5.389472 -50.154301 +v 64.528740 10.513351 -56.404041 +v 70.778481 5.246032 -52.341698 +v 67.028641 1.785373 -40.467182 +v -69.528542 9.371400 2.343658 +v -72.965897 9.048521 2.968639 +v -77.028244 8.913640 -0.781221 +v -76.090782 11.988560 -2.031182 +v -76.715759 13.576078 -4.531083 +v -76.715759 7.440240 1.406199 +v -75.465797 9.231620 -0.468742 +v -79.840637 8.336860 0.156239 +v -74.840820 16.898821 -3.281123 +v -41.717117 14.829740 -8.593423 +v -41.092140 16.416037 -9.530883 +v -41.717117 15.814837 -10.780823 +v -42.654579 15.757460 -7.030982 +v -38.904739 14.153480 -7.968442 +v -37.967258 13.473538 -9.530882 +v 42.967079 7.319398 -10.468341 +v 43.279564 9.123578 -11.718303 +v 40.779663 7.877858 -11.718302 +v 47.341900 5.199658 -10.780821 +v 44.529499 7.147278 -12.343261 +v 42.029602 9.410470 66.403664 +v 43.279564 12.760050 65.466202 +v 41.717117 13.776290 62.653797 +v 45.466980 9.069892 69.841019 +v 45.779461 10.738592 67.341118 +v 45.466980 10.387032 70.778481 +v 46.404438 9.646671 71.090981 +v 40.154682 7.488471 67.341118 +v 44.841999 7.695391 71.403458 +v 43.279564 5.393152 73.278381 +v 47.341900 6.215912 74.215858 +v -75.778297 12.500029 -69.216042 +v -1.718680 18.760378 -7.343462 +v -1.718680 22.921120 -7.968444 +v -3.281120 16.817038 -8.593423 +v -1.406200 15.136739 -5.781023 +v -0.156240 15.706800 -6.718482 +v -52.966682 24.631317 -13.905704 +v -53.279182 28.569298 -13.280745 +v -52.341698 25.590178 -13.280744 +v -53.904144 18.328857 -14.843183 +v -71.090981 19.677120 -5.468544 +v -71.715958 22.911980 -4.531084 +v -71.403458 19.544060 -3.906103 +v -70.465996 19.414679 -6.718483 +v -71.715958 20.588980 -5.781024 +v -69.528542 20.386959 -6.718483 +v -68.903564 17.718519 -7.030983 +v -69.528542 22.268057 -7.343463 +v -72.340919 19.006958 -7.343462 +v 55.466579 10.149590 65.778679 +v 56.404041 12.299251 66.716141 +v 57.341499 10.129451 66.091179 +v 53.591660 11.823171 67.653595 +v 33.279961 12.420085 28.280138 +v 32.967461 16.145643 26.405218 +v -21.717899 6.258628 46.404438 +v -26.092739 5.686728 46.404438 +v -24.217800 7.513508 49.216820 +v -26.717701 3.425387 43.279564 +v 25.780239 16.951298 -16.718102 +v 26.405220 17.321177 -17.655561 +v 24.530300 20.330196 -18.905525 +v 24.842779 19.374998 -15.155663 +v -24.217800 4.877399 -2.968641 +v -22.967861 3.377780 0.781219 +v -19.530479 5.251539 -4.531081 +v -25.467760 7.340159 -6.093522 +v -29.217621 5.204539 -3.593601 +v -24.217800 7.005679 -7.030981 +v -21.092920 9.231618 -10.468342 +v -26.092739 8.569398 -8.905901 +v -28.905121 7.365779 -6.406002 +v -73.590881 18.239140 -4.531083 +v -75.465797 14.855979 -3.593602 +v -77.028244 11.596708 -72.965897 +v -78.590683 10.847809 -70.153519 +v -36.717319 4.570387 -78.278198 +v -33.904919 4.482487 -79.528137 +v -40.154682 5.369927 -79.840637 +v -34.217419 5.169127 -76.403259 +v -42.967079 3.327708 -72.028442 +v -41.404640 3.573688 -76.403259 +v -44.841999 3.338087 -77.653221 +v -38.592239 3.872149 -67.341118 +v 17.030581 9.968909 -65.778679 +v 16.718100 9.605748 -67.653595 +v 17.343081 12.895530 -63.591267 +v 20.467960 8.879450 -64.216240 +v 15.468140 10.464510 -65.778679 +v 27.342680 17.966324 22.967857 +v 28.280140 24.077744 21.717896 +v 27.030201 20.870964 22.030376 +v 27.655161 17.275404 24.530298 +v 28.905121 23.849464 24.217796 +v -9.530880 2.020349 -66.403664 +v -9.843360 1.110310 -61.091362 +v -9.530880 3.395468 -70.465996 +v -13.905701 3.737868 -72.028442 +v -73.590881 2.501287 -79.528137 +v -72.965897 3.172688 -77.965698 +v -64.841217 2.206507 -79.528137 +v -79.840637 3.750067 -79.840637 +v -77.965698 4.526427 -77.028244 +v 26.092739 18.790277 -14.843183 +v 27.030201 20.122679 -13.905704 +v 27.967661 20.135498 -14.218204 +v 25.780239 22.582378 -14.218204 +v -79.528137 3.911225 31.092539 +v -78.903160 4.181605 32.654980 +v -77.340744 2.929785 29.530102 +v -79.840637 7.465887 40.154682 +v -60.153900 15.155656 -22.342882 +v -58.278980 15.815455 -22.655363 +v -59.216442 13.685937 -23.592821 +v -58.903961 13.439977 -20.155462 +v 29.530102 27.829544 27.655157 +v 28.280140 22.138664 28.905117 +v 30.155079 26.934784 27.967657 +v 30.155079 28.404505 27.030197 +v -53.279182 8.933155 -27.030203 +v -54.529118 7.822315 -28.905123 +v -54.216644 10.469395 -26.405222 +v -52.029221 6.629096 -25.467762 +v -49.841801 4.980536 -27.030201 +v 43.592037 21.860952 -52.341702 +v 44.217018 28.248854 -52.029224 +v 44.529499 26.598471 -53.279186 +v 42.654579 17.225952 -50.779266 +v 19.842979 18.444836 -18.280544 +v 19.218000 21.818836 -18.280544 +v 21.717899 20.324097 -17.343084 +v 16.718100 4.840772 -47.966881 +v 17.655560 6.396552 -48.591862 +v 12.655760 2.254713 -45.154480 +v -72.965897 29.915113 -48.279366 +v -72.653419 26.174892 -48.904343 +v -74.215858 27.018993 -48.904343 +v -72.965897 24.003872 -47.654404 +v -72.340919 34.482334 -48.279366 +v -73.278381 17.615973 -46.716923 +v -71.403458 18.423452 -46.091965 +v 10.468340 7.036199 -7.343461 +v 7.655960 7.290099 -8.593421 +v 5.156040 6.444159 -4.843561 +v 12.343260 8.526059 -6.718482 +v 12.655760 8.073177 -8.905901 +v -13.905701 3.612140 1.093699 +v -14.530680 5.665959 -3.593601 +v -10.780820 4.774860 0.156239 +v -10.780820 6.710879 -2.031181 +v -78.903160 21.868877 -11.405805 +v -78.278198 18.641977 -9.218383 +v -77.653221 23.494238 -9.530884 +v -79.528137 14.177279 -7.655962 +v -52.654198 29.219318 -12.968245 +v -52.341698 25.795858 -12.030785 +v -52.341698 26.663177 -14.530684 +v -51.091763 19.564219 -13.905704 +v -52.966682 32.327194 -12.030787 +v -6.406000 14.492808 -77.653221 +v -7.968440 8.436328 -75.153320 +v -5.781020 14.740607 -76.090782 +v -9.218380 11.244548 -78.903160 +v 72.965897 1.156714 -37.967258 +v 79.528137 0.442595 -29.530102 +v 79.840637 1.107274 -39.842201 +v 78.278198 3.396072 -50.154301 +v 65.466202 1.191494 -35.154881 +v 53.904144 15.574350 -61.091366 +v 54.529118 18.103630 -62.028820 +v 54.841599 21.437370 -60.153904 +v 55.779079 20.798330 -61.403866 +v 56.404041 23.674891 -62.028820 +v 52.341698 10.894809 -62.341316 +v -30.155079 6.895207 47.341900 +v -31.405020 9.733948 48.591862 +v -29.217621 8.936229 51.716736 +v -27.655161 8.574288 51.716736 +v -2.031180 9.809608 -75.153320 +v -2.656140 13.136627 -76.090782 +v -4.843560 10.250888 -75.465797 +v 0.781220 10.991847 -75.153320 +v -1.406200 13.591947 -77.028244 +v -3.906100 4.718088 -72.028442 +v 22.030380 15.768458 -10.780823 +v 23.280338 16.959238 -9.530883 +v 24.217800 17.996218 -13.905704 +v 20.467960 16.806038 -12.030784 +v 21.092920 14.829118 -9.843363 +v 22.655361 16.997078 -8.280923 +v 25.780239 22.305899 -9.218384 +v 26.405220 19.551998 -11.405804 +v -47.966881 13.599878 -14.530682 +v -49.529320 17.471939 -14.218204 +v -47.966881 15.735498 -12.030784 +v -46.091961 11.410557 -15.155662 +v -48.904339 12.646518 -16.718102 +v -51.091763 20.346678 -10.780823 +v -50.466782 16.321417 -16.718102 +v 2.031180 7.150940 -0.781221 +v 1.093700 9.285939 -3.593601 +v -1.406200 9.085140 -2.343661 +v 2.968640 7.387760 1.406199 +v 5.468540 5.468820 -0.156241 +v -27.342680 7.353568 -75.778297 +v -27.655161 5.673887 -77.965698 +v -28.592640 7.704508 -75.465797 +v -25.155260 7.404227 -75.778297 +v 17.655560 15.318617 -20.780441 +v 17.343081 11.051676 -23.905321 +v 15.780640 14.951797 -20.780441 +v 18.593021 17.890015 -20.467964 +v 20.467960 13.275796 -22.967863 +v 13.905701 4.981766 37.342300 +v 14.843180 6.760945 33.279961 +v -1.406200 12.566573 75.465797 +v -1.093700 8.816592 74.215858 +v -2.968640 14.193152 75.465797 +v -0.156240 9.652772 77.653221 +v 0.468740 8.960032 75.153320 +v -0.156240 7.268153 79.840637 +v 2.343660 7.980412 76.715759 +v -4.531080 12.535439 -7.343462 +v -8.593420 11.833538 -11.093322 +v -3.593600 16.438618 -10.155863 +v 12.343260 4.304875 -27.967661 +v 13.593220 3.494955 -31.092539 +v 5.781020 2.031335 -30.467560 +v 15.155660 6.095036 -27.342682 +v -61.403862 19.744253 -46.091965 +v -60.153900 18.746952 -44.842003 +v -59.841419 23.378271 -46.716923 +v -62.028816 18.097532 -45.466984 +v -30.780039 14.660691 67.028641 +v -32.967461 14.133951 67.653595 +v -30.467560 12.963912 68.278580 +v -29.842579 17.213772 66.403664 +v -32.654980 15.805690 65.466202 +v -60.466381 14.343891 -53.279186 +v -58.278980 14.577652 -52.654202 +v -57.341499 14.082051 -54.529121 +v -60.778881 10.330231 -54.841599 +v -56.091560 15.469992 -52.341702 +v -72.340919 22.585440 -5.468544 +v -69.528542 15.661639 -3.593603 +v -73.278381 27.708700 -5.468544 +v -72.653419 29.211979 -5.156045 +v -28.592640 10.864918 -10.468342 +v -30.467560 13.671898 -11.093322 +v -28.592640 10.684859 -8.593422 +v -26.717701 10.037278 -10.780822 +v -29.530102 10.234419 -12.343262 +v -12.968240 9.126628 -78.903160 +v -16.093121 8.432668 -78.590683 +v -12.968240 7.089288 -77.028244 +v -9.218380 12.412127 -79.528137 +v -17.655560 7.820487 -79.215660 +v -14.218201 0.839931 -57.653999 +v 22.342880 18.746960 -4.531083 +v 19.842979 12.763099 -4.218582 +v 21.717899 16.286640 -2.343663 +v 22.030380 21.019897 -6.093524 +v 20.155460 13.774440 -5.781023 +v -79.215660 12.254691 65.778679 +v -78.278198 13.433890 65.466202 +v -79.528137 17.568390 63.591259 +v -79.528137 9.559391 68.278580 +v -78.590683 10.562192 67.341118 +v 28.905121 23.605938 -9.530884 +v 28.905121 20.695799 -10.780824 +v 29.842579 16.887218 -9.218383 +v -16.405600 6.459419 -6.406001 +v -10.780820 7.856499 -6.093522 +v -25.155260 4.785229 -71.090981 +v -27.342680 8.183028 -71.403458 +v -22.967861 3.842228 -71.090981 +v -25.467760 6.017508 -73.278381 +v -27.342680 10.195968 -74.528343 +v -24.217800 3.156209 -67.341118 +v 65.466202 9.013709 -70.465996 +v 62.966278 9.758948 -68.903564 +v 40.779663 16.953751 -54.529121 +v 42.967079 19.543449 -52.966686 +v 43.592037 22.028170 -54.529121 +v 40.154682 13.469871 -52.654202 +v 27.342680 4.919513 77.653221 +v 22.342880 2.256573 79.215660 +v 32.029999 4.852993 79.528137 +v 27.030201 4.907932 75.153320 +v -54.841599 5.625080 1.406199 +v -55.779079 4.046101 4.531080 +v -49.216820 4.799280 1.406199 +v -55.779079 7.904720 -0.468741 +v -57.966480 5.526801 2.656139 +v -50.154301 1.560762 9.843360 +v -58.591461 3.935021 5.781020 +v 50.154301 2.274257 -20.155460 +v 54.529118 1.561376 -23.905319 +v 46.716919 2.802216 -23.905319 +v 52.966682 2.030118 -14.218201 +v 27.342680 14.765046 38.592236 +v 26.717701 14.311566 37.967255 +v 25.155260 14.816926 40.154678 +v 28.905121 11.005906 39.842201 +v 28.592640 13.983807 37.967255 +v 29.842579 9.119926 42.342102 +v 30.467560 9.743107 38.904739 +v -54.216644 24.249857 -12.030785 +v -56.716541 22.561640 -10.468344 +v -56.404041 24.722258 -11.093324 +v -57.029018 19.567257 -12.030784 +v -56.091560 26.042458 -9.218384 +v -55.779079 23.543678 -10.468344 +v -58.591461 19.326178 -9.530883 +v -55.779079 24.401217 -11.718305 +v -54.529118 23.410017 -10.780824 +v -75.153320 18.981319 -4.218583 +v -75.778297 16.444719 -4.843563 +v 36.717319 9.626524 23.905317 +v 39.217220 6.853704 23.280336 +v 36.092342 10.708064 21.717897 +v 35.154881 12.852824 25.780237 +v 36.717319 8.393004 27.655159 +v 19.842979 10.758716 -25.467762 +v 21.405420 12.648355 -24.530302 +v 23.592819 9.550216 -25.155262 +v 18.593021 10.824017 -26.092741 +v 23.280338 7.607475 -29.842581 +v 36.717319 17.244282 7.030977 +v 37.967258 16.540543 7.343457 +v 37.029800 14.344522 6.093518 +v 36.404819 22.164902 7.655956 +v 37.654778 21.942122 8.280916 +v 57.653999 3.659769 55.779079 +v 58.903961 1.747528 49.216820 +v 52.654198 3.168428 52.966682 +v 58.278980 5.044649 58.591461 +v 62.653801 3.143409 54.529118 +v 3.593600 14.646008 -76.403259 +v 4.218580 13.104888 -76.090782 +v 5.468540 7.910207 -79.528137 +v 2.656140 11.990988 -76.715759 +v 3.593600 15.139788 -76.090782 +v 35.467361 5.938174 -35.154881 +v 34.842381 7.692314 -36.717319 +v 32.967461 6.622375 -36.404819 +v 38.279758 4.575875 -32.342480 +v 38.279758 5.719674 -37.342300 +v 58.278980 2.185760 -0.781220 +v 55.466579 4.533781 2.343659 +v 57.966480 3.058561 5.468540 +v 57.029018 2.166839 -4.531081 +v 53.279182 3.329560 0.156239 +v 54.529118 3.560259 -5.468541 +v 52.029221 3.258140 -3.281121 +v 54.841599 1.954438 -10.468340 +v 51.716736 3.127518 -9.843361 +v 62.028816 0.857638 -12.030781 +v -67.966095 10.704398 -18.593023 +v -71.403458 12.017257 -18.280542 +v -69.841019 18.335577 -15.468143 +v -67.341118 8.756776 -21.092922 +v -70.465996 8.228816 -22.030382 +v -67.966095 14.351837 -16.093122 +v -66.403664 12.095977 -17.030582 +v -67.966095 22.117899 -13.593224 +v -33.904919 3.385690 -58.278980 +v -36.404819 4.296951 -54.216644 +v -30.155079 2.838831 -52.966682 +v -39.842201 6.114571 -56.716541 +v 12.655760 8.854406 -79.215660 +v 15.468140 12.632467 -78.903160 +v 19.530479 13.378306 -79.215660 +v 11.718301 9.762608 -76.715759 +v 13.280740 11.583888 -76.090782 +v 31.717520 7.194274 -38.904739 +v 33.279961 5.310735 -32.967461 +v -68.278580 15.014690 69.216042 +v -68.903564 20.660412 65.466202 +v -64.216240 12.507992 71.715958 +v -63.278778 15.455972 70.153519 +v 45.154480 12.672170 65.466202 +v 74.840820 7.633741 11.405800 +v 75.153320 10.413862 10.468338 +v 74.215858 8.820242 9.843358 +v 77.028244 9.241382 14.530678 +v 77.653221 11.381262 11.405799 +v 76.715759 8.090281 7.655958 +v 72.965897 5.958322 12.655759 +v 74.528343 7.055122 15.155659 +v 4.531080 7.206469 -71.090981 +v 2.031180 6.272028 -71.715958 +v 6.093520 8.890429 -69.528542 +v 7.343460 12.019688 -73.903358 +v 9.530880 10.808148 -74.840820 +v 7.343460 10.425448 -75.465797 +v 7.968440 12.553728 -71.715958 +v 11.093320 13.093287 -74.528343 +v 10.468340 9.794968 -75.778297 +v -11.093320 8.648750 66.403664 +v -14.218201 10.545712 68.278580 +v -66.091179 22.960798 -12.655764 +v -67.028641 25.330778 -13.280744 +v -66.716141 26.137657 -12.030785 +v -64.528740 22.934559 -12.030785 +v 34.529900 7.871149 -67.966095 +v 32.342480 9.057049 -69.216042 +v 30.155079 12.478669 -67.028641 +v 36.717319 6.024848 -71.715958 +v -70.465996 26.733978 -12.968245 +v -70.778481 27.240559 -11.405806 +v -69.528542 30.029858 -11.405806 +v -71.403458 25.696999 -13.280744 +v 57.029018 7.923028 -73.903358 +v -78.278198 22.252178 -12.968244 +v -78.903160 20.910639 -12.343264 +v -77.965698 20.533438 -13.905704 +v -79.528137 22.750837 -13.593224 +v -31.092539 5.781928 -77.028244 +v -29.842579 8.230027 -76.090782 +v -29.842579 8.526048 -73.903358 +v 78.278198 10.802664 23.280336 +v 78.903160 10.193543 21.405418 +v 77.028244 12.513464 21.717897 +v 78.903160 12.149084 24.217798 +v 56.716541 16.401989 -64.841217 +v 56.091560 13.537630 -66.091179 +v 54.841599 12.528110 -65.153702 +v 57.653999 14.334729 -64.216240 +v -74.215858 18.849501 -4.843563 +v -73.903358 24.008760 -5.468544 +v -75.465797 22.853376 -6.093524 +v -63.903763 17.119171 67.341118 +v -60.778881 14.695471 67.341118 +v -65.466202 19.884651 66.716141 +v 61.403862 7.864429 -70.153519 +v -35.779861 5.289997 -20.155460 +v -45.154480 5.820376 -22.655361 +v 41.717117 12.036171 -58.278984 +v 39.842201 13.249531 -55.779083 +v 42.342102 17.308352 -56.404045 +v 41.404640 10.396770 -59.841419 +v 37.342300 11.067551 -56.404041 +v -72.653419 18.938011 65.466202 +v -71.090981 16.665071 66.716141 +v -70.778481 21.163952 65.153702 +v -73.590881 18.433851 65.778679 +v -72.965897 14.646031 67.341118 +v 0.156240 7.193072 73.278381 +v -2.656140 8.482732 72.965897 +v -2.343660 6.652291 70.153519 +v -13.905701 5.163648 46.716919 +v -15.468140 7.450627 46.091961 +v -14.530680 5.946728 47.966881 +v -14.218201 3.714687 41.092140 +v 27.342680 13.308142 13.905699 +v 26.717701 11.623584 15.155658 +v 29.842579 17.561041 14.843177 +v 27.655161 11.157282 11.718299 +v 24.530300 7.143622 11.718300 +v 29.842579 5.338832 70.153519 +v 25.780239 6.668772 71.403458 +v 26.717701 6.308671 65.778679 +v 24.530300 6.320871 68.591080 +v 22.342880 7.919991 71.090981 +v 34.217419 5.526812 68.903564 +v 33.279961 3.759253 77.340744 +v 21.405420 3.319792 75.465797 +v -46.716919 7.202810 -60.466381 +v -46.404438 7.467091 -57.966480 +v 29.842579 27.178303 24.217796 +v 29.217621 26.124844 22.342876 +v -51.404236 15.438259 -6.406003 +v -52.966682 18.609018 -7.655962 +v -53.591660 19.202278 -6.718483 +v -50.154301 17.975479 -6.718483 +v -51.091763 17.944338 -8.280923 +v -52.654198 20.800777 -9.530884 +v -54.529118 19.526358 -7.968443 +v 72.965897 9.696103 19.217999 +v 71.403458 10.289963 19.842978 +v 72.965897 9.370183 20.467958 +v 70.778481 10.178283 18.593019 +v -62.028816 1.228744 25.155260 +v -65.466202 3.040244 23.905319 +v -65.153702 1.979465 29.217621 +v -58.278980 0.731304 26.405220 +v -61.091362 1.129244 21.717899 +v -58.591461 1.063945 32.342480 +v 27.967661 9.830969 -62.966278 +v 29.217621 12.269310 -62.653805 +v 29.530102 11.294589 -63.591263 +v 26.717701 9.604530 -60.778881 +v 29.530102 10.998571 -60.778881 +v 47.029419 6.726762 7.343459 +v 47.029419 5.286941 8.905899 +v 49.841801 5.044641 7.343459 +v 46.404438 9.683901 6.405999 +v 46.091961 7.191841 7.655959 +v 33.279961 12.594049 58.591457 +v 31.092539 11.387989 58.903961 +v 31.717520 9.246290 61.403862 +v 32.029999 11.669969 55.154099 +v 44.529499 31.481873 -52.029224 +v 44.529499 23.231173 -51.091766 +v 3.281120 13.860497 -19.530481 +v 5.468540 12.394437 -20.467962 +v 4.843560 13.022496 -21.092922 +v 2.343660 13.981358 -19.218002 +v 1.406200 15.772117 -17.968042 +v 2.968640 14.711938 -20.467962 +v 1.093700 11.364177 -19.842981 +v 12.655760 9.452559 -11.093322 +v 13.905701 12.506759 -13.280742 +v 11.093320 11.981858 -13.280742 +v 15.468140 10.413858 -10.468342 +v -7.968440 11.164612 78.903160 +v -10.155860 12.786314 79.528137 +v -5.156040 11.546692 76.403259 +v -6.093520 13.122613 74.840820 +v 2.968640 10.956456 -21.092922 +v 2.656140 7.431696 -22.342882 +v -68.591080 25.806854 -48.904343 +v -68.903564 23.610813 -48.279366 +v -67.966095 25.807451 -47.654404 +v -69.528542 24.207130 -50.154305 +v -70.153519 24.416473 -48.279366 +v -67.028641 23.746912 -48.904343 +v 24.530300 10.132504 24.217798 +v 23.592819 9.491645 28.592638 +v 29.217621 20.985718 -8.905904 +v 28.905121 27.628736 -8.593425 +v 29.217621 20.769636 -7.655963 +v 28.592640 30.994200 -9.218385 +v 12.968240 7.931580 0.156239 +v 11.718301 6.561960 0.468739 +v 13.905701 7.542180 -0.468741 +v 12.343260 8.541920 -0.156241 +v 50.154301 6.570489 -67.653595 +v 47.029419 4.392768 -71.715958 +v -58.903961 9.674735 -28.592642 +v -58.591461 11.275676 -31.405022 +v -60.466381 8.746995 -28.905123 +v -58.903961 10.487715 -26.405222 +v -76.715759 2.695403 15.468140 +v -70.778481 2.342623 17.968040 +v -76.403259 3.250201 10.780819 +v -79.528137 2.909644 23.905319 +v -35.779861 4.751667 44.841999 +v -34.529900 7.289508 47.029419 +v -30.780039 5.875328 45.466980 +v -40.467182 3.428427 43.904537 +v -41.092140 7.146068 48.279362 +v -38.592239 7.461008 50.154301 +v -35.154881 10.831969 52.966682 +v 54.841599 4.396441 5.156039 +v 53.591660 6.625441 5.156039 +v 52.341698 4.216382 7.343459 +v 53.904144 5.339420 3.281119 +v 53.279182 3.055502 10.468339 +v 52.654198 4.630820 2.656139 +v -1.406200 14.021019 -4.218583 +v 0.156240 12.586099 -5.781022 +v -1.093700 11.314739 -3.593602 +v -0.468740 16.517958 -7.968443 +v -3.281120 8.258718 -18.280542 +v -1.718680 7.108217 -20.780441 +v -4.531080 5.114817 -21.092920 +v -2.656140 12.937037 -16.718102 +v -0.781220 10.443778 -18.905523 +v -2.343660 15.651257 -14.843183 +v -0.156240 17.927259 -16.093124 +v 72.028442 16.043110 -62.966282 +v 71.090981 14.219991 -61.403866 +v 73.278381 14.201069 -61.716347 +v 72.340919 14.879769 -64.841217 +v 70.153519 16.403809 -64.528740 +v 72.340919 13.051170 -59.841423 +v 69.841019 16.398331 -61.403866 +v 69.528542 11.942770 -59.841423 +v 3.906100 8.405808 -72.340919 +v 4.843560 11.578408 -73.590881 +v 2.968640 11.293967 -73.278381 +v 5.468540 10.019568 -72.653419 +v 48.591862 18.488771 -54.841602 +v 48.279362 16.077291 -56.404045 +v 47.029419 17.859489 -55.154102 +v 51.091763 15.477910 -56.091564 +v 45.466980 20.720209 -53.904148 +v 44.529499 18.242189 -55.466583 +v 47.029419 20.676250 -52.966686 +v 47.341900 19.164431 -56.404045 +v 45.779461 16.534431 -57.341503 +v 47.341900 12.880270 -59.216446 +v 50.154301 12.169210 -60.153904 +v 68.903564 2.107025 28.592640 +v 70.778481 3.861785 29.217621 +v 71.715958 4.734584 25.780239 +v 66.403664 1.600445 27.967661 +v 69.841019 2.136945 30.467560 +v -58.591461 13.864153 -41.404644 +v -60.153900 15.678113 -39.529705 +v -56.404041 11.900675 -37.654781 +v -58.591461 15.500513 -44.529503 +v -61.091362 18.485712 -42.654583 +v 79.528137 3.888632 -52.029221 +v 78.903160 5.672051 -56.091560 +v -73.278381 4.748628 45.466980 +v -76.715759 5.554288 45.779461 +v -74.840820 5.027548 49.216820 +v -72.028442 5.839307 42.654579 +v -75.465797 5.817947 42.342102 +v -70.778481 4.734587 43.279564 +v -64.841217 3.861167 45.779461 +v -68.903564 3.932587 40.779663 +v -71.403458 4.869467 40.467182 +v -71.090981 6.870186 37.967258 +v -74.215858 7.216866 38.279758 +v -52.341698 4.426948 -72.340919 +v -47.654400 3.591388 -71.715958 +v -52.341698 4.065008 -75.778297 +v -55.154099 3.494948 -72.653419 +v -53.591660 4.506289 -67.341118 +v 46.716919 8.812322 4.531079 +v 44.841999 9.842581 6.093519 +v 49.529320 8.958800 4.843558 +v 45.466980 6.807920 3.281119 +v 49.216820 5.410841 2.968639 +v 41.717117 8.002381 3.281119 +v 49.216820 4.318920 1.093699 +v -41.092140 9.819991 -52.029221 +v -42.654579 8.078053 -48.591862 +v -39.217220 7.807671 -51.716736 +v -41.717117 12.532371 -52.341702 +v -43.904537 9.645432 -50.466782 +v 27.967661 6.893376 -27.030203 +v 24.842779 6.514355 -30.155081 +v 35.467361 4.773015 -28.280140 +v -50.154301 1.171366 37.654778 +v -47.966881 1.843367 41.404640 +v -57.341499 2.469587 41.717117 +v -33.904919 0.442003 16.718100 +v -34.842381 1.035262 9.530880 +v -40.467182 0.476782 15.155660 +v 56.716541 22.019032 -60.466385 +v 57.966480 16.580811 -61.716347 +v 56.716541 24.642309 -59.841423 +v 32.029999 9.365906 37.029800 +v 33.592442 7.642286 37.342300 +v 30.780039 13.696325 36.717316 +v -48.279362 7.824751 -52.341698 +v -46.404438 8.351492 -47.966881 +v -51.404236 8.736012 -50.779263 +v 77.653221 9.722343 20.780437 +v 79.528137 10.137981 14.530678 +v 76.715759 9.917663 17.655558 +v 12.968240 8.103701 -2.031181 +v 15.468140 8.922779 -4.218582 +v 11.093320 6.321479 -4.843561 +v 8.593420 5.058060 0.156239 +v -28.592640 18.541891 64.528740 +v -30.780039 17.991350 63.903759 +v -27.030201 16.055332 67.341118 +v -29.530102 20.855110 63.591259 +v -27.967661 15.864291 63.591259 +v -29.217621 19.122931 63.278774 +v -31.092539 14.445830 62.653797 +v -27.967661 21.287851 65.466202 +v 11.093320 15.700096 -21.092922 +v 11.093320 21.047358 -20.155464 +v 13.593220 17.430437 -21.092922 +v 13.280740 11.782876 -22.342882 +v -54.529118 18.194578 -6.718483 +v -53.591660 21.511839 -7.343463 +v -55.779079 21.407459 -6.406003 +v -55.154099 16.170059 -5.156043 +v 58.278980 0.644026 37.342300 +v 52.029221 1.004746 34.529900 +v 50.466782 1.229347 40.779663 +v 59.216442 0.924785 30.780039 +v 73.278381 4.709565 28.280140 +v 75.153320 5.007405 31.717520 +v 67.341118 1.118266 34.842381 +v 71.715958 2.312726 34.529900 +v 68.903564 3.695164 23.592819 +v -42.967079 14.397618 -11.093322 +v 4.218580 6.286713 77.653221 +v 3.593600 9.328073 76.715759 +v 6.406000 3.903893 79.528137 +v 6.093520 5.673312 77.028244 +v 3.906100 11.206113 75.153320 +v 5.156040 8.854432 74.840820 +v -37.654778 5.936952 -49.841801 +v -42.029602 7.046573 -45.154480 +v -34.217419 4.362872 -46.716919 +v -33.904919 2.451253 -42.654579 +v -33.592442 1.962374 -38.904739 +v -32.342480 2.409753 -45.154480 +v -25.467760 1.305632 -46.716919 +v -40.154682 4.553293 -39.842201 +v -32.654980 3.140952 -48.279362 +v -67.966095 8.656054 -34.842381 +v -75.465797 9.959754 -33.904919 +v -59.841419 14.703410 65.466202 +v -58.903961 11.960490 62.966274 +v -61.716343 13.794590 62.028812 +v -60.153900 10.809389 61.403862 +v -56.404041 14.741250 65.466202 +v -56.716541 14.431810 63.591259 +v -44.841999 4.572848 46.716919 +v -49.216820 4.470908 47.654400 +v -46.716919 7.442709 51.716736 +v -43.279564 8.181828 49.216820 +v -43.904537 9.466009 51.716736 +v -42.029602 7.246769 52.966682 +v -74.840820 22.973614 -51.404240 +v -76.090782 17.541512 -52.966686 +v -78.278198 17.821053 -48.904343 +v -73.590881 22.968113 -51.404240 +v -73.278381 15.255752 -54.216648 +v -74.528343 21.914053 -49.841805 +v 48.904339 17.266232 -49.841805 +v 47.966881 22.575672 -50.154305 +v 47.654400 16.094372 -49.216824 +v 48.591862 25.484592 -50.779266 +v 28.280140 23.912336 -13.280744 +v -55.154099 10.551200 -2.031182 +v -58.278980 8.951480 -1.718681 +v -21.405420 7.791198 -12.655761 +v -17.030581 7.828438 -12.655761 +v -18.280540 4.724797 -17.343081 +v -65.778679 4.875562 8.593419 +v -62.341316 6.001040 4.218579 +v -49.216820 20.258179 -8.280923 +v -48.904339 17.855837 -9.530883 +v -47.966881 16.599737 -7.968443 +v -47.654400 20.242918 -8.905903 +v -70.778481 3.440026 32.967461 +v -68.903564 2.835185 28.905121 +v -74.215858 2.359104 26.405220 +v -67.653595 4.909745 33.279961 +v 39.529701 4.325047 42.967079 +v 34.529900 6.071246 41.404640 +v 36.717319 5.797208 46.716919 +v 41.092140 4.981767 43.279564 +v 39.842201 4.126667 40.154682 +v 43.592037 2.755227 40.779663 +v 47.966881 2.387788 46.404438 +v 46.716919 1.812226 37.342300 +v 40.154682 4.059526 37.029800 +v 40.154682 4.511808 46.091961 +v 21.405420 8.440004 31.717518 +v 19.218000 4.807204 25.780239 +v -32.029999 8.037792 76.715759 +v -26.092739 7.335893 78.278198 +v -26.717701 8.519973 75.778297 +v -40.154682 3.917333 79.840637 +v 11.405801 2.231532 -48.591862 +v 7.343460 1.626672 -51.091763 +v 8.593420 2.051472 -46.716919 +v 4.843560 1.276951 -52.029221 +v 6.718480 1.232993 -45.154480 +v 6.406000 2.668531 -54.841599 +v -41.092140 13.314870 66.403664 +v -43.592037 14.246250 63.903759 +v 35.154881 3.820292 74.528343 +v -60.153900 10.532275 -31.717522 +v -56.716541 9.468435 -31.405022 +v -58.591461 14.329255 -33.279964 +v -63.591263 6.757893 79.215660 +v -70.465996 6.396573 79.528137 +v -66.403664 6.943433 77.965698 +v 19.530479 7.495792 -45.779461 +v 25.155260 6.325752 -48.279362 +v 26.717701 7.805831 -51.404236 +v -52.341698 7.325490 -63.278778 +v -49.216820 6.994689 -65.778679 +v 24.217800 6.270814 -32.029999 +v -35.779861 2.957840 2.968640 +v -30.780039 2.518400 2.968640 +v -41.717117 1.862881 5.781020 +v -36.092342 5.212480 -0.468741 +v -32.967461 3.958220 -0.781221 +v -36.404819 8.100039 -5.156042 +v -40.154682 4.432460 0.156239 +v -75.465797 5.122141 6.093519 +v -74.840820 7.142401 4.218579 +v -73.278381 3.738481 8.280919 +v -79.528137 3.821501 10.155859 +v 73.278381 3.511448 48.279362 +v 72.965897 2.758887 42.029602 +v 69.841019 2.949308 49.841801 +v 76.403259 5.058688 47.029419 +v 17.030581 3.476652 75.153320 +v 19.218000 6.028532 72.340919 +v 13.905701 5.356531 68.591080 +v -48.279362 6.422800 -1.718681 +v -47.966881 6.005920 0.156239 +v -46.091961 6.210999 -2.343661 +v -52.029221 8.581600 -1.718681 +v -45.779461 3.958220 1.406199 +v -47.966881 8.986259 -4.218582 +v -56.091560 13.123197 -17.343082 +v -56.404041 13.963657 -18.905523 +v -55.466579 14.996978 -15.468143 +v -53.904144 15.838637 -15.780643 +v -54.216644 12.133218 -20.155462 +v -53.591660 13.387477 -19.842981 +v -53.904144 10.568276 -22.030382 +v -55.466579 12.563517 -18.593023 +v 63.903763 12.325468 -68.278580 +v 64.216240 12.171668 -67.653595 +v 66.091179 15.719608 -67.341118 +v 62.653801 12.847928 -67.966095 +v 66.716141 13.845228 -67.653595 +v 62.653801 10.880169 -67.028641 +v 65.466202 13.380150 -66.091179 +v 67.653595 12.238188 -69.528542 +v 66.403664 16.425789 -66.716141 +v 46.091961 11.400169 -61.716343 +v 48.591862 13.944731 -60.778885 +v 48.904339 8.806190 -63.278778 +v -11.093320 7.608099 -3.906101 +v -11.093320 9.548399 -2.968642 +v 39.842201 7.369440 0.156239 +v 26.405220 10.596351 -58.903961 +v 26.717701 9.672291 -57.029018 +v 27.655161 12.442651 -57.966484 +v 22.967861 9.304251 -60.153900 +v -35.154881 7.360297 -16.718102 +v -34.529900 9.881038 -14.218203 +v -1.093700 22.132557 -10.468344 +v -0.468740 21.882318 -12.655764 +v -2.656140 19.923698 -11.093323 +v 0.468740 14.503199 -9.530882 +v -74.840820 16.355610 62.653797 +v -75.778297 11.622370 61.403862 +v -76.403259 19.559950 63.591259 +v -72.340919 15.537750 62.653797 +v -72.653419 9.336609 60.153900 +v 13.593220 7.099050 -65.466202 +v 11.405801 5.423650 -65.153702 +v 13.905701 11.261028 -68.903564 +v 47.654400 4.097382 11.718300 +v 36.404819 7.925488 50.779263 +v 36.404819 10.459049 52.341698 +v 37.967258 7.877869 51.404236 +v 28.905121 10.249087 49.529320 +v -67.341118 13.867219 -4.218583 +v -64.216240 12.977340 -5.156042 +v -67.653595 15.267360 -6.093523 +v -63.903763 9.436700 -1.093702 +v -58.591461 16.821918 -6.718483 +v -57.341499 17.161879 -6.093523 +v -59.841419 12.941319 -4.843562 +v -57.341499 17.298599 -5.156043 +v -63.278778 19.312733 -47.654404 +v -66.403664 18.484491 -46.091965 +v -63.591263 18.161013 -48.279366 +v -66.091179 21.388533 -47.654404 +v -65.778679 9.571589 59.841419 +v -64.841217 6.722489 57.341499 +v -67.966095 9.146790 59.528919 +v -66.091179 13.513230 60.778877 +v -64.528740 11.662029 61.091362 +v -66.716141 12.518970 61.403858 +v -15.155660 15.025672 77.653221 +v -17.030581 12.051454 79.215660 +v -13.593220 17.228413 79.528137 +v -14.843180 17.355373 76.715759 +v -17.343081 11.997132 77.965698 +v -20.780439 8.951493 79.528137 +v -56.404041 14.169332 -50.779266 +v 22.342880 13.407626 36.717316 +v 23.905319 12.633105 33.904915 +v 23.592819 11.786546 38.592236 +v -5.156040 4.428188 53.904144 +v -6.406000 5.390709 57.029018 +v -2.656140 3.933187 47.341900 +v -7.655960 3.191007 46.091961 +v 37.342300 4.921351 71.403458 +v 27.655161 23.682838 -9.218384 +v 25.155260 26.201139 -7.968444 +v 26.717701 28.945879 -7.968445 +v -13.280740 10.222818 -10.780822 +v -14.218201 8.511418 -12.343261 +v -17.030581 9.362238 -10.780822 +v -10.780820 6.536937 -16.093122 +v -69.216042 8.241010 -64.841217 +v -65.466202 10.451094 -36.717319 +v -66.716141 10.701334 -38.279758 +v -63.903763 12.514673 -35.154884 +v -62.966278 12.499413 -38.592243 +v -67.966095 13.396633 -42.029606 +v -70.153519 16.237192 -43.904541 +v -64.841217 14.046653 -42.654583 +v -0.781220 22.022078 -13.593224 +v -4.218580 14.888938 -12.968243 +v 74.528343 6.535708 -76.403259 +v 77.028244 8.934368 -72.965897 +v 79.840637 5.919868 -79.840637 +v 70.778481 5.058667 -79.215660 +v 70.465996 5.759347 -77.653221 +v 39.842201 11.517982 12.968238 +v 40.779663 10.284482 13.593218 +v 39.842201 12.767982 12.030779 +v 38.279758 10.383963 15.780638 +v 41.717117 7.160723 15.468139 +v 44.841999 3.764123 20.467960 +v 41.404640 5.646443 19.842979 +v -78.278198 12.710588 -68.591080 +v -77.340744 10.157508 -70.465996 +v -79.528137 11.168848 -74.528343 +v -79.215660 12.919330 -65.153702 +v 51.091763 4.402528 -72.965897 +v 52.966682 5.578669 -70.778481 +v 55.154099 5.800228 -74.840820 +v 47.341900 3.601748 -76.715759 +v 23.592819 7.589783 19.217999 +v 21.717899 6.176824 23.592817 +v 25.467760 10.893003 20.155458 +v 20.780439 4.693083 17.655560 +v -0.468740 3.927081 4.531080 +v 3.281120 2.285241 5.781020 +v -1.718680 3.346041 3.906100 +v -4.531080 3.873381 5.156040 +v 1.718680 4.751660 3.281119 +v -0.156240 5.394360 0.156239 +v -3.906100 3.576141 2.968640 +v 6.406000 14.811408 -73.590881 +v -52.341698 23.941639 -10.468344 +v -52.341698 13.583400 -5.156043 +v -49.529320 13.632237 -18.280542 +v -49.841801 9.750417 -19.842981 +v -51.716736 10.021417 -20.155462 +v -52.654198 12.622717 -18.593023 +v -48.591862 6.873237 -21.717901 +v -51.404236 7.306576 -22.655363 +v -35.154881 9.657648 54.216644 +v -61.716343 20.981440 -8.905904 +v 20.155460 17.538456 -19.530481 +v 21.717899 25.654879 -16.405603 +v 22.342880 24.432959 -16.405603 +v 21.717899 5.037302 14.843179 +v 23.592819 7.401803 17.343079 +v 19.218000 3.480922 12.030780 +v -69.841019 18.566910 62.966274 +v -69.528542 11.771289 61.403858 +v -69.216042 18.157370 64.216240 +v 30.467560 7.887634 -38.592239 +v 29.530102 11.010174 -37.029800 +v 30.467560 7.208314 -36.092342 +v -68.278580 6.541190 -63.591263 +v -64.841217 5.478570 -63.903763 +v -67.028641 6.295229 -66.716141 +v -69.216042 8.074390 -59.841419 +v -52.966682 7.051450 -62.028816 +v -54.529118 9.917650 -58.278980 +v -57.341499 6.912890 -62.028816 +v -56.404041 11.403851 -57.966480 +v -58.591461 9.127851 -57.029018 +v -55.466579 13.364891 -56.404045 +v -53.904144 9.431190 -57.341499 +v 79.528137 6.992872 74.215858 +v 75.153320 8.613952 72.965897 +v 73.590881 10.240533 77.340744 +v 25.155260 12.149085 29.530100 +v 27.342680 19.415285 30.467556 +v 26.092739 14.932285 32.654976 +v 22.967861 18.931278 -14.843183 +v 21.717899 21.691877 -15.780643 +v 22.030380 17.280899 -13.593223 +v 23.592819 22.325418 -15.155663 +v 23.280338 19.263918 -16.093124 +v 42.654579 12.258950 -58.903965 +v 37.967258 5.620185 35.779861 +v 37.967258 6.768266 37.342300 +v 31.092539 12.148472 -55.466583 +v 30.780039 10.186811 -54.529118 +v 33.279961 12.489052 -55.154102 +v 30.155079 9.945110 -56.716541 +v 30.155079 13.423491 -55.154102 +v -73.278381 7.515936 -27.655163 +v 22.967861 13.959988 -74.528343 +v 25.780239 13.125628 -74.215858 +v 21.405420 16.911627 -76.403259 +v 19.218000 16.658329 -74.215858 +v -71.090981 20.040911 63.591259 +v -72.340919 23.468012 64.528732 +v 49.841801 3.162918 -12.655761 +v 45.154480 5.124578 -15.155661 +v 46.716919 3.573697 -17.655560 +v -24.530300 6.997728 -75.465797 +v 29.217621 20.963745 18.593018 +v 28.592640 18.485723 19.217997 +v 29.217621 21.970203 19.217997 +v 27.967661 16.334242 17.030579 +v 30.155079 19.656984 17.968037 +v 26.717701 10.171561 7.030979 +v 23.592819 7.204041 7.030979 +v 25.780239 8.183042 9.530879 +v 27.967661 13.569361 6.405998 +v 27.030201 11.374561 3.281118 +v 30.467560 14.246241 7.030978 +v 30.155079 16.406879 5.468537 +v 30.780039 14.484901 8.280918 +v 28.905121 12.887001 4.531078 +v -9.218380 6.072448 -72.965897 +v -11.093320 5.175848 -73.590881 +v -14.843180 1.718829 -64.841217 +v 33.904919 22.139282 11.093316 +v 31.405020 16.970242 10.468337 +v 32.029999 21.330561 12.343256 +v 35.154881 20.007940 11.093317 +v 34.217419 20.073862 8.280917 +v 31.092539 21.549063 13.280736 +v -55.154099 12.367597 -22.967863 +v -57.966480 14.499537 -24.530302 +v -1.718680 2.897427 45.154480 +v 0.468740 3.707368 51.091763 +v -0.156240 2.208346 40.154682 +v -10.468340 2.302346 40.154682 +v -11.093320 4.242028 47.966881 +v 5.781020 3.882529 54.841599 +v 7.968440 4.264610 61.403862 +v 28.905121 12.789340 2.656138 +v 30.467560 14.196200 2.343657 +v 11.093320 1.811613 -42.342102 +v 8.593420 13.309977 -14.843182 +v 9.530880 8.906298 -11.093322 +v 12.030781 14.898098 -14.218204 +v -0.468740 9.234056 -20.780441 +v -4.843560 8.250177 -17.030582 +v -0.468740 11.165188 -78.590683 +v 1.093700 13.927028 -75.778297 +v -41.092140 8.817191 -53.904144 +v -51.716736 6.469795 -32.967461 +v -53.279182 9.660074 -33.592442 +v -54.529118 7.683775 -31.717522 +v -51.716736 6.929374 -36.717319 +v -55.466579 10.516395 -34.529900 +v -65.466202 26.217619 -10.155864 +v -66.716141 26.953077 -10.468345 +v -66.091179 24.560518 -8.905904 +v -64.528740 22.084959 -10.780824 +v -65.778679 25.083597 -11.405805 +v -77.965698 7.368227 43.592037 +v -79.528137 5.475548 47.654400 +v -70.778481 14.950580 -2.656142 +v 44.217018 3.801357 -22.342880 +v 42.029602 3.897175 -29.842579 +v 43.904537 5.040956 -21.092920 +v -39.217220 3.211134 -35.779861 +v -37.967258 3.100675 -29.842579 +v -29.217621 1.392915 -32.967461 +v -40.467182 10.108091 68.591080 +v -39.529701 7.468952 71.715958 +v -72.653419 21.248158 -9.843364 +v -71.715958 27.741659 -9.218385 +v -72.340919 23.561378 -10.468344 +v -73.903358 20.126339 -10.468343 +v -73.903358 19.299938 -7.030983 +v -71.715958 21.041258 -8.280924 +v -70.465996 22.282698 -8.593424 +v -54.841599 13.114039 -3.281122 +v -54.216644 15.980239 -2.968643 +v -54.216644 16.491720 -3.906103 +v -57.029018 12.394439 -3.593602 +v -66.091179 8.603571 -57.029018 +v -64.841217 10.751371 -54.529118 +v -62.341316 7.894950 -57.341499 +v -70.778481 10.346710 -57.029018 +v -20.155460 11.793870 57.029015 +v -23.905319 15.679970 56.404037 +v -72.965897 24.899260 -11.718305 +v -72.965897 21.459339 -13.593224 +v -73.590881 24.222399 -11.718305 +v -71.715958 32.249081 -11.405807 +v 13.593220 18.298336 -19.842983 +v 45.154480 16.568630 61.091358 +v 31.092539 26.709562 25.155256 +v 31.405020 29.204065 24.217796 +v 31.092539 28.275105 26.092735 +v 32.029999 25.592625 24.842775 +v -72.653419 5.175246 37.029800 +v -76.403259 4.696126 35.467361 +v -75.778297 7.569047 40.467182 +v 38.279758 21.433104 9.843356 +v 37.029800 18.899542 10.780817 +v 38.592239 21.441643 10.780816 +v 38.592239 17.624521 9.218377 +v 74.215858 3.198320 2.031179 +v 75.153320 5.253981 4.843559 +v 77.340744 4.940260 1.718679 +v 70.465996 3.523041 5.781020 +v -73.590881 5.051961 5.781019 +v -68.278580 3.444302 10.468339 +v -69.528542 4.862741 6.718479 +v 71.715958 8.817804 21.405418 +v 70.465996 6.046824 22.030378 +v 72.028442 10.878344 22.342878 +v 30.780039 21.947023 27.967657 +v 31.092539 18.685925 28.592636 +v 31.405020 24.397564 27.655157 +v -78.278198 9.988455 -31.405022 +v -13.593220 1.974576 -25.780239 +v -51.716736 10.371133 -46.716919 +v -51.716736 11.853672 -45.779465 +v -50.154301 9.416553 -46.716919 +v -54.529118 13.543112 -46.716923 +v 54.216644 9.065589 -67.653595 +v 27.030201 15.401646 34.842377 +v 7.655960 7.147896 -23.280340 +v -27.030201 2.922452 -48.279362 +v -23.905319 1.400232 -48.591862 +v -25.780239 4.011312 -52.029221 +v 71.403458 11.519832 75.465797 +v 67.653595 12.040451 72.653419 +v 69.528542 14.317053 77.653221 +v 72.653419 11.751773 75.153320 +v 69.841019 8.689032 69.216042 +v -13.905701 0.733744 24.217800 +v -18.280540 0.565284 27.030201 +v -12.968240 1.496064 26.092739 +v -23.905319 9.690008 50.466782 +v -22.030380 7.064888 48.279362 +v -47.966881 7.965151 69.528542 +v -45.779461 5.029992 74.840820 +v -47.029419 11.239071 66.091179 +v 36.404819 10.926551 -60.153900 +v 35.467361 10.720251 -57.966480 +v 33.592442 10.903349 -62.966278 +v 32.967461 13.092071 -59.528923 +v 38.279758 8.965509 -62.341316 +v 65.153702 1.562608 45.779461 +v -79.215660 10.762989 61.403862 +v -79.528137 7.537310 58.903961 +v -78.278198 11.115170 61.403862 +v -31.717520 4.988477 -19.530479 +v -34.217419 3.491896 -24.217800 +v -29.842579 5.916817 -19.218002 +v -32.029999 6.507017 -17.030582 +v -32.967461 14.744279 -11.405804 +v -25.780239 3.424757 -21.092920 +v -43.592037 8.142769 57.653999 +v -30.780039 9.085149 55.154099 +v 14.530680 17.064838 -14.843183 +v 73.903358 0.437117 -16.405600 +v 65.153702 0.576276 -22.030380 +v 79.528137 0.788678 -12.655760 +v 20.467960 12.335239 -1.718682 +v 17.655560 8.843440 -0.156241 +v 18.593021 9.641780 0.781218 +v 30.155079 27.802086 22.030376 +v 31.092539 23.931866 21.405416 +v 29.530102 21.668705 20.467957 +v 31.405020 27.868004 22.655357 +v -75.153320 17.343752 -47.341904 +v -75.778297 13.964872 -46.404442 +v -74.215858 13.432033 -45.466984 +v -73.278381 20.057381 -4.843563 +v -77.028244 19.067379 -15.155663 +v -75.153320 21.245117 -15.780643 +v -76.403259 14.616117 -17.968042 +v -75.778297 20.620117 -13.593224 +v 3.593600 7.175372 73.590881 +v 42.654579 8.579788 55.154099 +v 38.279758 12.191209 55.154095 +v 40.779663 16.704128 57.653996 +v -69.841019 24.671598 -9.530884 +v -69.528542 23.700539 -8.593424 +v -68.278580 21.923199 -9.218384 +v -48.904339 4.667428 -68.903564 +v -44.217018 3.722609 -68.903564 +v -73.590881 12.525671 -56.404045 +v -32.654980 12.824739 -10.780822 +v -33.904919 16.051039 -11.405804 +v -32.654980 11.158498 -9.530882 +v -33.904919 13.352078 -10.468342 +v 74.528343 10.241753 79.528137 +v 79.840637 6.951972 79.840637 +v -68.278580 2.526944 26.092739 +v -69.216042 2.616664 23.592819 +v -65.153702 5.264368 53.904144 +v -61.403862 5.858229 55.154099 +v -71.090981 5.869229 56.091560 +v -59.528919 5.333328 52.654198 +v -61.716343 7.468329 57.966480 +v -57.653999 7.015449 57.029018 +v -46.404438 12.082570 63.903759 +v -47.654400 12.674610 64.216240 +v -46.716919 8.901430 60.153900 +v 7.655960 5.194149 -66.716141 +v 18.280540 12.539087 -78.278198 +v 10.780820 7.740527 -79.528137 +v 20.155460 15.093389 -76.403259 +v 67.028641 10.366271 71.403458 +v 65.153702 13.147012 72.965897 +v 57.653999 12.374931 67.966095 +v 60.153900 9.592952 67.653595 +v 54.529118 8.860531 63.591263 +v 62.028816 6.572351 63.903763 +v 32.029999 10.722089 -61.716343 +v 72.028442 6.831123 16.405598 +v -46.716919 1.917201 6.718480 +v -45.154480 1.035262 10.155860 +v -28.905121 9.221848 -72.965897 +v -35.154881 4.511790 -64.528740 +v -65.778679 2.556244 21.717899 +v -67.028641 3.598103 17.655560 +v -69.841019 3.454064 25.467760 +v -19.842979 6.489327 46.091961 +v -20.780439 8.030468 46.716919 +v -19.842979 7.463448 47.341900 +v -53.591660 9.193770 -55.779079 +v -56.404041 12.437772 -53.591663 +v 27.030201 12.896157 -19.842981 +v 31.405020 8.930717 -17.655561 +v 28.905121 9.359797 -21.092922 +v -73.903358 19.931637 -15.468143 +v -72.340919 23.020618 -14.843184 +v -71.715958 19.647818 -15.468143 +v -74.528343 20.809317 -13.905704 +v 31.717520 21.433105 26.717697 +v 15.155660 3.696373 -41.092140 +v 17.655560 6.450253 -40.467182 +v 14.218201 2.760694 -37.654778 +v 19.218000 8.914233 -40.154682 +v 22.342880 13.684716 -22.967863 +v 22.030380 18.563856 -22.030384 +v 23.592819 14.221836 -22.655363 +v -20.780439 12.742970 58.903957 +v -20.780439 14.364670 60.778877 +v -67.341118 20.828238 -8.905904 +v -67.653595 28.786579 -9.843365 +v -65.778679 20.430292 -49.529324 +v -21.092920 13.229410 63.278774 +v -24.530300 14.885291 65.466202 +v 33.904919 15.263083 23.280336 +v -62.028816 12.874811 72.340919 +v -66.716141 9.927432 73.590881 +v -64.841217 7.847972 76.403259 +v 2.656140 11.454518 -10.468342 +v -75.465797 13.488196 -18.905523 +v -74.840820 16.054096 -18.593023 +v 17.655560 16.165798 -12.343263 +v 18.280540 19.297480 -11.405804 +v 18.593021 18.004778 -13.280743 +v -72.028442 7.292550 58.591461 +v -35.467361 15.524919 -10.468343 +v -36.717319 11.453299 -7.655962 +v -34.529900 11.859779 -7.655962 +v -36.717319 13.977078 -11.405803 +v 44.841999 3.512666 34.217419 +v -65.778679 8.269096 -24.842781 +v -63.278778 11.702316 -24.217802 +v -62.341316 9.432416 -25.467762 +v -64.216240 9.435476 -22.342882 +v -62.341316 12.366977 -23.905321 +v -60.466381 10.242355 -25.155262 +v -17.343081 12.181452 68.591080 +v -19.842979 12.900431 65.778679 +v -20.780439 12.280931 68.278580 +v -15.780640 11.231130 67.028641 +v -16.093121 11.727950 64.216240 +v 13.593220 4.718110 62.653801 +v 15.155660 4.498369 58.903961 +v 10.468340 4.969569 59.841419 +v 15.780640 6.290370 65.153702 +v 16.718100 5.909510 60.153900 +v 2.656140 4.910970 62.653801 +v -63.903763 16.643078 -7.343462 +v -66.091179 16.907978 -7.343462 +v -61.403862 9.381760 -2.031181 +v -65.466202 21.080318 -8.280924 +v -63.591263 24.255959 -9.530884 +v -66.403664 18.009659 -7.968443 +v -51.091763 0.701403 16.093121 +v 18.280540 6.532660 2.968639 +v 18.280540 4.062582 7.655959 +v 21.717899 8.216002 3.593599 +v 21.092920 11.369680 0.156238 +v -0.468740 6.124936 -22.030382 +v -0.156240 3.778756 -24.530300 +v 25.780239 13.340485 23.280336 +v 26.405220 17.171644 22.655359 +v 41.092140 14.122331 -49.841805 +v 37.029800 10.770912 -52.654198 +v 27.655161 10.494431 -57.029018 +v 29.530102 10.808152 -55.466579 +v 29.217621 9.732712 -54.216644 +v 29.842579 7.975512 -49.216820 +v 32.654980 8.501031 -49.841801 +v 32.029999 9.042411 -52.966682 +v 27.967661 9.422651 -51.716736 +v 32.967461 17.578144 20.780436 +v 31.717520 24.177843 21.717896 +v 34.842381 14.995143 18.905519 +v 31.717520 20.141005 18.905518 +v 30.780039 24.857763 19.842976 +v 33.904919 3.621913 79.528137 +v 39.842201 3.015233 79.840637 +v 2.343660 15.885638 -13.593223 +v 0.156240 20.225819 -13.905704 +v 55.779079 11.820108 -69.528542 +v 54.841599 7.536068 -70.465996 +v 56.716541 11.860989 -69.216042 +v 56.091560 11.638209 -70.778481 +v 65.778679 6.308047 -78.590683 +v 60.466381 5.340027 -79.528137 +v -71.090981 17.968132 -52.341702 +v -71.403458 22.466413 -51.404240 +v -69.216042 18.804932 -51.404240 +v 67.653595 7.401168 -75.465797 +v 66.403664 8.308768 -76.715759 +v 74.528343 4.119966 33.904919 +v 76.090782 7.185126 33.904919 +v 75.153320 4.665006 35.779861 +v 32.654980 7.243107 44.841999 +v -60.153900 3.075028 -71.090981 +v -55.779079 4.444028 -75.153320 +v -57.341499 3.163528 -75.153320 +v 27.967661 19.197405 31.717516 +v 64.841217 12.985250 -62.653805 +v 63.591263 13.589490 -63.903767 +v 61.716343 13.439970 -61.091366 +v 65.778679 14.954230 -62.028820 +v 64.841217 12.785069 -63.591267 +v 77.653221 7.503127 48.279362 +v 77.028244 7.527528 50.154301 +v 79.528137 8.678648 47.341900 +v 79.215660 7.835768 46.404438 +v -71.090981 29.918158 -9.530885 +v -46.091961 3.183073 79.528137 +v -50.466782 3.601173 79.528137 +v -50.466782 3.922833 78.903160 +v 64.528740 14.653950 -64.528740 +v 62.966278 13.674331 -58.278984 +v -75.465797 20.017078 -9.843363 +v -76.403259 19.526978 -11.405804 +v -18.280540 9.138247 46.091961 +v -18.593021 10.040349 49.841801 +v -17.030581 9.674748 50.466782 +v 31.717520 11.248211 -59.841419 +v 29.217621 12.240010 -59.528923 +v 28.905121 14.559360 -0.156242 +v 28.280140 17.261360 -3.281123 +v 26.405220 14.520901 -0.156242 +v 29.217621 15.343660 1.093698 +v 30.780039 15.493179 -0.781222 +v 14.843180 4.862145 31.717520 +v -6.718480 3.222741 4.843559 +v -74.528343 3.358845 32.967461 +v -78.590683 7.640446 37.654778 +v -13.280740 7.108849 56.091560 +v -9.530880 7.702709 59.841419 +v -14.843180 9.306088 54.841599 +v -17.030581 9.090029 55.154099 +v -16.718100 11.835990 57.341496 +v 19.842979 25.288057 -14.530684 +v 27.030201 17.332785 21.405418 +v -74.528343 21.072998 -12.968244 +v 18.593021 5.413272 -47.029419 +v -70.153519 13.246491 -54.216648 +v -67.653595 14.652731 -52.654202 +v -36.717319 6.395953 74.840820 +v -33.279961 7.205893 76.090782 +v -35.467361 8.418032 71.715958 +v 33.592442 7.332818 -16.718102 +v 33.592442 8.670718 -13.905702 +v 37.029800 6.772518 -15.468141 +v 62.653801 5.868587 -75.153320 +v 71.403458 7.996868 -73.590881 +v 61.091362 5.210027 -78.278198 +v -15.155660 13.875173 75.153320 +v -9.843360 13.641393 75.778297 +v -51.716736 18.944698 -17.030584 +v -53.279182 14.859636 -17.030582 +v -54.841599 13.258697 -22.655363 +v 70.153519 10.587809 -69.528542 +v -25.467760 2.806481 4.531080 +v -22.655361 1.346521 8.593420 +v 76.403259 10.686084 20.467958 +v 74.528343 9.330503 21.405418 +v 75.465797 9.771784 22.342878 +v -27.655161 10.813653 73.278381 +v -26.405220 13.953892 71.090981 +v -27.342680 12.918752 70.153519 +v -24.530300 11.668151 71.403458 +v -30.155079 10.274712 72.653419 +v -26.092739 16.223171 69.528542 +v 56.716541 15.291772 -54.216648 +v 54.529118 17.191771 -56.091564 +v 55.154099 13.551651 -52.966686 +v 56.716541 16.364752 -57.029022 +v -72.028442 31.873093 -48.279366 +v -56.091560 21.281118 -7.968443 +v -59.528919 12.014199 -2.656142 +v -60.153900 20.184937 -7.655963 +v -62.028816 13.427758 -5.781023 +v 36.717319 8.988700 -0.781221 +v 34.529900 12.440220 2.031178 +v 38.904739 9.200501 2.968639 +v 18.905521 14.248668 -73.278381 +v 17.343081 13.190948 -75.778297 +v -27.342680 3.543180 0.156239 +v 1.406200 10.321689 -74.528343 +v 39.842201 12.587931 62.028812 +v 37.967258 14.484290 60.153896 +v 36.404819 9.186470 63.903763 +v 31.405020 14.392124 30.155077 +v 29.530102 17.888206 32.029995 +v 29.530102 20.787964 29.217617 +v 29.842579 14.972567 33.279957 +v 18.905521 5.154510 58.278980 +v 18.593021 5.264369 61.091362 +v 25.467760 6.229930 62.341316 +v 77.965698 6.936106 36.717319 +v 76.403259 7.540966 36.404819 +v 77.340744 5.681226 38.592239 +v 51.404236 2.743002 14.530680 +v 55.779079 2.353003 17.343081 +v 54.529118 2.352384 21.717899 +v 57.029018 3.399144 23.592819 +v -10.468340 7.642879 -3.593601 +v -70.153519 7.433541 3.906099 +v 23.905319 7.612354 -37.967258 +v -46.091961 15.701319 -8.905903 +v -47.341900 16.099258 -9.530883 +v -47.341900 21.162718 -8.593424 +v -44.217018 14.478778 -9.218382 +v -30.155079 12.299850 60.153896 +v -28.592640 12.151529 58.591457 +v -31.092539 10.585389 58.903961 +v -29.842579 16.481970 61.403858 +v -28.280140 13.895910 61.091358 +v 39.842201 19.472042 7.343456 +v -23.905319 11.179248 52.966682 +v -33.592442 8.097599 -6.093522 +v 27.655161 33.002258 -7.655965 +v 29.217621 18.385620 -3.593603 +v 30.155079 19.900520 -5.468544 +v 58.903961 4.987253 -44.529499 +v 57.029018 6.071833 -44.217018 +v 59.528919 3.436973 -41.404640 +v 57.029018 9.835253 -48.591862 +v -25.780239 15.687892 67.966095 +v -23.280338 13.560230 67.028641 +v 37.029800 7.778989 -65.466202 +v 25.780239 16.384895 -21.092922 +v 26.092739 12.594637 -21.717901 +v 56.404041 12.519571 69.528542 +v 55.779079 9.834051 72.965897 +v 60.778881 12.471372 70.778481 +v 51.404236 10.568292 71.715958 +v 50.154301 6.893393 75.465797 +v 72.340919 5.253982 8.280919 +v 72.028442 7.060001 10.468339 +v 68.591080 4.182202 11.405800 +v 68.903564 4.922562 16.093121 +v 65.153702 3.644502 12.030780 +v 66.091179 4.359822 16.093121 +v 63.591263 3.076861 8.593419 +v -68.591080 12.534231 72.653419 +v -70.778481 9.082092 74.528343 +v -58.278980 12.484171 68.591080 +v -69.216042 13.204391 70.465996 +v 40.467182 6.338574 -35.154881 +v 39.842201 6.055353 -39.842201 +v -52.341698 3.146447 45.154480 +v -60.778881 4.135209 -67.653595 +v -61.403862 6.444770 -65.153702 +v -60.778881 5.828310 -62.653801 +v -64.528740 4.912169 -66.091179 +v -5.781020 14.601447 -78.278198 +v -3.281120 13.486348 -77.965698 +v -5.468540 15.071426 -79.528137 +v -54.216644 11.839051 64.528740 +v -51.716736 9.292049 61.403862 +v -55.466579 9.524590 61.403862 +v -53.904144 11.214650 66.403664 +v -54.216644 7.743609 58.591461 +v -54.529118 6.482008 53.591660 +v -48.904339 7.204669 55.466579 +v 16.405600 14.703387 -74.215858 +v 15.468140 12.584247 -73.278381 +v 52.341698 1.806748 47.654400 +v 56.716541 0.966287 43.592037 +v -8.280920 7.467110 61.403862 +v -11.093320 11.225029 61.091362 +v 57.029018 6.388608 -77.653221 +v 56.716541 7.390807 -79.528137 +v 55.154099 5.327207 -78.903160 +v 22.342880 15.339380 0.156237 +v 22.655361 15.612201 -2.031183 +v 23.905319 14.522739 -1.718682 +v 22.655361 19.482420 -3.593603 +v -18.280540 2.889488 -69.841019 +v -18.905521 5.007388 -74.528343 +v -71.403458 29.501293 -47.966885 +v -62.028816 14.939593 -41.404644 +v 26.405220 15.869159 -3.281123 +v -46.404438 4.362875 -25.780239 +v -48.904339 4.658895 -31.717520 +v 46.091961 13.103070 62.028812 +v 46.404438 13.449149 60.778877 +v 47.654400 15.348551 64.216240 +v 48.904339 11.539351 62.653801 +v 51.091763 10.872251 62.028816 +v 49.841801 7.387149 58.903961 +v 46.091961 11.226250 60.153900 +v -54.841599 13.331352 69.841019 +v 53.279182 7.723453 -46.716919 +v 53.279182 5.994933 -44.217018 +v -48.904339 13.502239 -6.093523 +v -46.716919 11.951940 -5.781022 +v -45.779461 17.243658 -7.030983 +v -44.529499 8.670719 -4.531082 +v -57.029018 14.674697 -21.717901 +v 11.093320 9.634449 -68.903564 +v 67.341118 12.701472 77.965698 +v 64.216240 8.822692 68.591080 +v -12.030781 3.391196 -21.717899 +v 68.903564 9.595990 -57.966480 +v 71.715958 7.925471 -56.716541 +v 19.842979 8.278251 -60.778881 +v 23.280338 10.860646 40.154682 +v 26.092739 11.132247 42.967079 +v 65.153702 5.761183 17.030581 +v 67.341118 4.979323 17.968040 +v 9.218380 6.665716 -23.905321 +v 13.593220 8.374696 -24.217802 +v 8.905900 4.881056 -25.467760 +v 9.530880 10.785556 -22.030382 +v -67.653595 18.408218 -8.280923 +v -68.903564 27.492018 -10.468345 +v -69.841019 29.889479 -10.155865 +v 75.778297 12.414583 18.593019 +v 78.590683 9.997002 14.218199 +v 74.528343 9.967703 17.968039 +v 73.903358 12.002003 18.905519 +v 66.403664 3.408283 21.405420 +v -58.278980 10.066589 59.841419 +v 52.029221 10.673270 65.153702 +v 52.341698 10.363830 61.716343 +v 52.029221 7.634350 60.153900 +v 52.029221 5.160609 57.029018 +v 46.091961 4.891448 52.341698 +v 27.342680 9.217571 -53.904144 +v 27.342680 10.734912 -52.341698 +v -44.529499 3.590167 -79.528137 +v -55.779079 4.714427 -78.590683 +v -52.029221 4.069887 -77.965698 +v 17.655560 10.443769 -69.528542 +v 8.280920 2.126551 -57.341499 +v -3.906100 0.829551 -57.029018 +v 65.153702 5.623870 62.341316 +v 12.655760 5.431606 37.654778 +v -46.091961 8.101851 -53.591660 +v -14.843180 11.727351 62.653797 +v -16.093121 17.105730 61.091358 +v 70.153519 7.511663 19.217999 +v 41.717117 4.661944 27.655161 +v -55.154099 13.818392 -47.341904 +v -55.154099 17.951674 -46.404442 +v -57.653999 20.355831 -47.029423 +v -54.841599 14.118672 -45.154484 +v -53.591660 14.772352 -44.217022 +v -7.968440 9.404371 71.715958 +v -44.217018 10.007371 -58.903961 +v -43.592037 14.340230 -58.903965 +v 6.718480 9.852339 -11.718303 +v 8.280920 13.407618 -17.343082 +v 4.843560 8.309378 -8.905901 +v -27.655161 11.400189 56.404041 +v -24.530300 17.939470 57.341496 +v 20.155460 17.152716 -20.780441 +v -1.718680 21.172480 -8.905904 +v 42.029602 3.817852 76.715759 +v 48.591862 4.699193 79.528137 +v 38.279758 5.048306 38.279758 +v -61.403862 1.514385 32.654980 +v -65.778679 3.090307 39.217220 +v -45.779461 3.451007 -78.903160 +v -55.466579 6.231128 -77.340744 +v -0.156240 11.499667 -79.840637 +v -39.529701 12.246751 66.091179 +v 38.592239 7.720393 -43.592037 +v -68.278580 28.400839 -12.655765 +v 13.905701 11.242088 -71.715958 +v 14.218201 14.346328 -73.590881 +v 12.343260 11.751129 -72.965897 +v 9.843360 8.410687 -77.028244 +v 12.030781 14.313987 -75.465797 +v 15.155660 16.129169 -74.215858 +v -73.903358 12.933376 -18.593023 +v -73.903358 16.156019 -16.718102 +v -73.903358 10.398597 -20.467962 +v 35.154881 5.728207 -78.278198 +v 32.342480 8.507728 -75.465797 +v 32.967461 7.742368 -72.965897 +v 30.780039 7.859547 -79.528137 +v -78.903160 17.933979 -16.093124 +v 10.155860 1.745074 -36.092342 +v 78.278198 9.986021 6.093519 +v 79.215660 9.928022 9.218378 +v 79.528137 9.773621 4.843558 +v -13.905701 1.352025 30.467560 +v -14.843180 2.519006 35.779861 +v -10.155860 0.653784 21.405420 +v -4.218580 0.570784 23.905319 +v 25.155260 7.037415 -34.842381 +v 25.155260 12.548857 -22.342882 +v 21.092920 6.315975 -30.155081 +v 27.030201 8.396656 -24.530302 +v -66.716141 32.933281 -11.093326 +v 20.467960 21.089478 -13.280744 +v 49.216820 13.750650 67.028641 +v 48.591862 9.820612 69.528542 +v 49.841801 12.520810 65.466202 +v 51.716736 13.799491 66.716141 +v 42.029602 11.618710 57.341499 +v 44.217018 8.743349 57.029018 +v 40.467182 6.542428 50.779263 +v -33.904919 14.009430 63.591259 +v -33.279961 17.955351 66.403664 +v 5.468540 2.141206 38.592239 +v 10.468340 3.180626 37.967258 +v 10.468340 3.209926 37.029800 +v 51.404236 7.235781 5.156039 +v 47.029419 2.880335 -29.530102 +v 44.217018 4.246294 -35.467361 +v 45.154480 8.522392 -45.466980 +v 51.091763 8.497373 -47.029419 +v 45.779461 11.629072 -47.966881 +v 43.592037 6.169493 -41.404640 +v 54.529118 3.162314 -36.717319 +v 42.967079 9.115652 -45.779461 +v 27.967661 8.855650 58.903961 +v 29.842579 8.550489 60.778881 +v 27.342680 8.402168 54.216644 +v 22.967861 6.331869 55.154099 +v 25.467760 8.379588 49.841801 +v -55.466579 12.553751 67.653595 +v 75.153320 4.151086 38.279758 +v 76.090782 4.126667 42.654579 +v -2.656140 11.917747 -79.215660 +v 75.778297 6.925128 52.029221 +v 73.590881 4.238988 50.466782 +v 74.840820 8.862369 53.591660 +v 77.028244 7.372508 53.904144 +v 15.155660 13.328268 -75.465797 +v 15.155660 13.188507 -77.340744 +v 53.591660 15.496230 -58.903965 +v 8.905900 6.354440 3.906099 +v 34.529900 14.533110 58.591457 +v 37.654778 17.302271 57.966476 +v 39.529701 14.813270 58.278976 +v 46.091961 16.433731 -49.841805 +v 49.841801 12.672152 -48.904343 +v 54.216644 3.173924 26.092739 +v 49.529320 2.595304 21.717899 +v 54.529118 1.539405 28.905121 +v -16.718100 2.246181 4.531080 +v 76.403259 15.864869 -62.028820 +v -17.343081 8.136647 -78.278198 +v 78.903160 13.885528 49.841797 +v -62.341316 9.713813 75.465797 +v 23.280338 10.853921 3.593598 +v 32.967461 16.339739 5.468537 +v -63.903763 14.078991 -52.029224 +v -65.466202 15.928971 -52.341702 +v -64.216240 16.074232 -50.154305 +v -39.529701 6.967219 -3.593601 +v 73.590881 6.262888 53.904144 +v 16.093121 17.036757 -18.593023 +v -12.343260 8.583449 59.841419 +v -10.780820 3.191001 3.906100 +v 62.341316 11.287869 -64.841217 +v 59.528919 11.555809 -64.841217 +v 67.028641 17.459108 -65.153702 +v 59.841419 11.730989 -66.716141 +v 26.717701 25.424780 -7.343464 +v 24.217800 20.767818 -6.093524 +v 67.341118 15.302129 -63.903767 +v -33.592442 1.140846 35.154881 +v -32.029999 2.652066 39.529701 +v -27.030201 1.292826 34.842381 +v -40.154682 1.876326 40.154682 +v -24.842779 1.970926 39.217220 +v 45.779461 2.929165 27.967661 +v -60.778881 17.427996 -22.967863 +v -61.403862 13.439356 -22.342882 +v 40.154682 11.308641 5.468538 +v -52.029221 22.739239 -16.093124 +v 73.590881 10.576223 22.967859 +v -65.778679 8.853821 5.468539 +v 9.843360 20.986937 -20.467964 +v 23.592819 19.635616 -18.905525 +v 23.592819 22.228374 -20.467964 +v 22.030380 18.485117 -18.280544 +v -20.467960 9.810252 76.090782 +v 59.528919 15.481590 -58.278984 +v 58.591461 15.944831 -54.529121 +v -60.466381 15.955215 -35.154884 +v 79.528137 13.798862 11.093318 +v 79.840637 4.584420 0.156239 +v 78.903160 9.367122 3.593599 +v -70.465996 19.378653 -45.779465 +v -71.715958 14.104033 -43.904541 +v 18.593021 23.836657 -18.280544 +v 15.780640 23.427717 -17.343084 +v 39.842201 4.015567 -79.840637 +v 43.904537 17.885750 -56.404045 +v 44.217018 19.222410 -57.654003 +v 45.466980 34.381031 -51.404240 +v 33.904919 23.066401 13.280736 +v 34.529900 23.834822 12.343256 +v 33.592442 21.639402 12.655756 +v 34.217419 23.786001 15.780636 +v 36.404819 16.810923 13.280737 +v 79.528137 9.814488 -72.653419 +v -62.653801 16.325693 -51.404240 +v -74.215858 5.519468 -74.840820 +v -79.528137 6.539368 -75.778297 +v 16.718100 10.805719 -6.406002 +v 8.905900 15.144676 -20.780441 +v 2.031180 3.406456 -25.780239 +v 17.655560 3.007283 21.092920 +v 32.342480 15.084860 2.031178 +v 32.342480 17.196060 3.906097 +v 5.468540 14.127828 -74.215858 +v 15.780640 18.150627 -74.528343 +v -71.403458 30.317938 -14.530684 +v 16.093121 8.864190 -64.216240 +v -77.965698 18.354498 -8.593423 +v -52.029221 9.411072 68.591080 +v 45.466980 22.233871 -50.779266 +v 44.529499 14.013092 -49.216824 +v 35.154881 17.272362 6.718477 +v -67.341118 19.383533 -50.154305 +v -67.653595 20.620111 -51.091766 +v -70.778481 23.158552 -49.841805 +v -71.403458 28.858593 -48.904343 +v -69.528542 23.385593 -47.029423 +v 28.905121 21.156618 -12.343264 +v -25.155260 5.573197 -16.405600 +v -27.967661 7.832697 -14.218202 +v 19.530479 15.206318 -10.780823 +v 18.905521 14.708279 -10.155863 +v 17.343081 13.801918 -10.468342 +v 17.968040 16.174337 -9.530883 +v -59.216442 20.100693 -46.716923 +v -58.278980 23.922092 -47.654404 +v 30.467560 14.376238 -11.718303 +v 28.592640 26.693077 -12.655765 +v 44.529499 4.917688 50.466782 +v 61.403862 2.301724 23.905319 +v 30.155079 12.866848 -69.841019 +v 27.342680 11.859168 -71.403458 +v 29.530102 11.003448 -72.653419 +v 21.405420 20.666496 -20.780443 +v -43.592037 7.192449 53.591660 +v -7.030980 5.473700 1.093699 +v -7.343460 5.482861 3.593599 +v -1.406200 25.210539 -9.843364 +v 2.031180 9.736999 -7.030982 +v -27.655161 12.455489 53.904140 +v 28.280140 26.141317 -8.905904 +v -74.840820 7.379233 79.215660 +v -76.090782 9.788272 74.215858 +v -79.840637 9.467233 79.840637 +v -24.217800 2.058196 -25.467760 +v -17.343081 2.400596 -23.280338 +v -48.279362 8.579152 -44.529499 +v -52.341698 10.905792 -44.217018 +v 23.592819 16.655300 -3.593603 +v -78.278198 21.859129 63.278774 +v -75.153320 14.543490 66.091179 +v 13.905701 2.283433 79.528137 +v 37.967258 9.055243 19.217999 +v 37.029800 8.020079 -4.843561 +v -2.031180 15.693998 -3.281123 +v -13.280740 9.991510 62.028816 +v 55.466579 19.942011 -57.654003 +v 57.653999 16.403811 -53.279186 +v 53.591660 15.129410 -57.029022 +v 55.466579 17.973631 -59.216446 +v 33.279961 10.897870 -56.716541 +v -32.967461 11.978811 70.153519 +v 57.653999 11.531411 -51.404236 +v 49.841801 19.152832 -51.091766 +v -17.968040 14.835252 73.278381 +v -17.030581 13.178152 74.840820 +v 41.717117 8.653018 -13.593221 +v 43.592037 5.497517 -16.405600 +v -53.904144 6.697468 50.154301 +v -51.091763 8.945369 51.404236 +v -57.653999 6.491768 48.279362 +v -72.340919 23.640112 -49.529324 +v -26.092739 2.459791 -54.529118 +v -21.092920 1.567470 -61.091362 +v -29.530102 2.595290 -59.841419 +v -20.467960 1.140831 -53.279182 +v 79.215660 9.067449 53.591660 +v 79.528137 13.378948 50.154297 +v 78.590683 8.288649 55.154099 +v -59.841419 4.502648 45.779461 +v -61.403862 4.825527 43.904537 +v -57.341499 4.120568 45.779461 +v 70.153519 0.654398 -9.843360 +v 39.217220 14.022249 59.841415 +v 79.840637 7.363967 40.154682 +v 19.530479 7.169853 -40.779663 +v 24.217800 11.339154 -35.779861 +v -73.903358 23.217152 65.153702 +v 31.717520 22.434683 16.093117 +v 4.531080 0.727642 13.593220 +v 30.467560 11.840847 -73.903358 +v -74.215858 24.709448 63.903759 +v -42.654579 7.034973 -43.279564 +v -43.592037 10.971712 -44.529499 +v 66.716141 1.903780 2.031180 +v -73.590881 20.679918 -6.093524 +v -75.465797 25.523039 -7.030984 +v 55.779079 2.057575 -32.029999 +v -79.528137 17.100231 -59.528923 +v 36.092342 12.877850 56.404037 +v -70.465996 26.642418 -14.530684 +v -69.841019 23.166479 -13.593224 +v 39.217220 17.297382 11.718298 +v 38.592239 15.607942 12.030778 +v -50.466782 7.467091 -56.716541 +v -16.093121 5.919887 41.404640 +v 23.280338 23.585176 -7.030984 +v -32.342480 7.498848 47.966881 +v 40.467182 18.027363 9.530877 +v -44.529499 3.650595 -32.654980 +v -47.029419 5.272893 -38.904739 +v 51.091763 3.810487 -78.278198 +v 6.406000 10.517632 74.840820 +v 58.591461 18.918449 -58.591465 +v 40.779663 17.856470 58.591457 +v 24.842779 16.843279 0.156237 +v -26.405220 16.904331 65.778679 +v -25.155260 18.762232 66.091179 +v -26.717701 14.028971 62.653797 +v -21.405420 15.022611 65.153702 +v -44.529499 9.946332 -45.779461 +v -45.154480 7.705753 -43.904537 +v -1.093700 5.742270 64.841217 +v 67.653595 14.776629 -66.091179 +v -44.529499 16.122458 -7.343462 +v -41.404640 7.174139 -3.593601 +v -79.528137 10.358313 -44.841999 +v 61.091362 1.435640 -3.281120 +v -61.403862 13.949614 -36.717323 +v -73.590881 12.022751 69.841019 +v -75.778297 10.872252 68.591080 +v -9.530880 11.923272 77.965698 +v -50.779263 7.995053 -42.029602 +v 73.278381 1.543059 -3.281120 +v 20.780439 13.011489 -61.716347 +v 67.966095 16.373314 79.528137 +v 32.654980 22.449322 14.843176 +v 36.092342 13.722564 16.718098 +v 34.217419 20.729364 17.655556 +v 32.967461 28.552822 13.905696 +v 31.717520 22.039782 14.218197 +v -4.531080 9.792552 73.590881 +v -59.216442 20.259394 -35.467365 +v -57.029018 15.797754 -35.154884 +v -59.528919 18.383795 -36.404823 +v 32.029999 17.558603 27.655159 +v 68.591080 4.325649 56.091560 +v 55.779079 18.980091 -60.153904 +v -78.903160 17.536013 -49.529324 +v 8.280920 14.771756 -19.218002 +v 62.653801 3.776323 18.905521 +v 60.466381 3.061602 15.155659 +v -67.653595 8.510801 3.906099 +v -68.903564 10.949141 2.968638 +v -77.340744 24.241320 -8.905904 +v -54.529118 11.553374 -42.967079 +v 58.903961 3.446742 11.405800 +v -74.840820 23.324560 -13.593224 +v 61.716343 10.495673 74.840820 +v 32.342480 28.280602 13.280735 +v 79.528137 13.284350 64.841217 +v 16.093121 11.403239 -5.156042 +v -76.090782 11.670568 -71.715958 +v 79.528137 2.451259 -3.281121 +v 23.592819 12.656280 1.093698 +v 17.655560 4.381794 -35.154881 +v -42.342102 10.631151 -53.904144 +v -44.217018 9.450110 -57.341499 +v -69.841019 6.665733 78.903160 +v -76.403259 18.236698 -7.030983 +v -77.653221 16.540539 -7.655962 +v -75.778297 21.612537 -7.655963 +v 32.967461 6.423397 -21.405422 +v -79.215660 5.951009 55.779079 +v -43.279564 11.576571 -57.653999 +v -6.093520 16.708387 -78.903160 +v 9.843360 18.503416 -18.593025 +v -20.780439 11.347712 72.340919 +v -55.466579 10.761754 -40.779663 +v 27.342680 11.351988 46.404438 +v 4.218580 9.576459 -6.718482 +v 63.278778 0.839947 39.842201 +v -61.716343 23.092632 -46.716923 +v -70.465996 4.517906 36.092342 +v -70.465996 25.890472 -46.716923 +v -28.592640 11.295232 74.528343 +v 50.779263 4.047307 -79.528137 +v 43.592037 10.226478 -12.968242 +v 40.154682 4.604557 -20.467960 +v 47.341900 23.754253 -51.091766 +v 61.716343 6.143262 11.718300 +v 65.153702 11.439274 79.528137 +v 78.590683 15.463282 11.718298 +v -29.217621 8.496167 46.091961 +v -78.590683 7.388989 58.591461 +v -75.778297 6.478950 57.341499 +v -78.903160 18.748190 63.278774 +v -76.090782 21.032093 -48.591866 +s 1 +f 1 2 3 +f 1 4 2 +f 5 6 7 +f 5 8 9 +f 5 10 11 +f 6 12 7 +f 13 14 15 +f 13 16 17 +f 15 18 19 +f 15 14 18 +f 20 21 22 +f 20 23 24 +f 25 26 27 +f 25 28 29 +f 26 30 31 +f 25 27 32 +f 33 34 35 +f 33 36 37 +f 38 39 40 +f 38 41 42 +f 39 42 43 +f 38 40 41 +f 44 45 46 +f 44 47 45 +f 48 49 50 +f 48 51 52 +f 52 51 53 +f 48 54 49 +f 52 55 56 +f 51 57 58 +f 59 60 61 +f 59 62 60 +f 63 64 65 +f 63 66 67 +f 64 68 69 +f 63 65 70 +f 64 69 71 +f 68 72 73 +f 74 35 75 +f 74 76 77 +f 78 79 80 +f 78 81 82 +f 82 83 84 +f 82 79 78 +f 82 81 83 +f 78 80 85 +f 86 87 88 +f 86 89 90 +f 91 92 93 +f 91 94 95 +f 91 93 96 +f 92 95 97 +f 98 99 100 +f 98 101 99 +f 102 103 104 +f 102 105 106 +f 107 108 109 +f 107 110 111 +f 112 113 114 +f 112 115 113 +f 116 117 118 +f 116 119 117 +f 120 121 122 +f 120 123 124 +f 125 126 127 +f 125 128 129 +f 126 129 130 +f 125 131 132 +f 133 134 135 +f 133 136 137 +f 138 139 140 +f 138 141 142 +f 143 144 145 +f 143 146 144 +f 147 148 149 +f 147 150 148 +f 148 151 149 +f 148 150 152 +f 153 154 155 +f 153 156 157 +f 158 159 160 +f 158 161 162 +f 163 164 165 +f 163 166 167 +f 168 169 170 +f 168 171 172 +f 173 174 175 +f 173 176 177 +f 178 176 173 +f 178 179 180 +f 180 176 178 +f 180 181 182 +f 178 183 184 +f 176 182 177 +f 185 186 187 +f 185 188 189 +f 190 191 192 +f 190 193 191 +f 194 195 160 +f 194 192 195 +f 196 197 198 +f 196 199 200 +f 201 200 202 +f 201 197 196 +f 203 197 201 +f 203 204 198 +f 203 205 206 +f 196 198 199 +f 207 208 209 +f 207 210 211 +f 212 213 214 +f 212 208 215 +f 208 216 209 +f 208 217 215 +f 218 219 220 +f 218 221 222 +f 223 224 225 +f 223 226 224 +f 227 228 229 +f 227 230 12 +f 231 230 232 +f 231 233 234 +f 230 235 7 +f 231 232 233 +f 236 237 238 +f 236 239 240 +f 239 241 242 +f 239 238 241 +f 243 244 245 +f 243 246 247 +f 243 247 244 +f 246 248 249 +f 250 251 252 +f 250 253 254 +f 253 255 254 +f 253 250 256 +f 257 258 259 +f 257 260 258 +f 261 262 263 +f 261 264 265 +f 266 267 268 +f 266 269 270 +f 267 271 268 +f 267 270 272 +f 273 274 275 +f 273 276 274 +f 277 278 279 +f 277 280 281 +f 282 283 284 +f 282 285 286 +f 287 288 289 +f 287 290 291 +f 290 292 293 +f 287 291 294 +f 295 296 297 +f 295 298 299 +f 300 301 302 +f 300 298 303 +f 295 299 304 +f 300 303 305 +f 306 307 308 +f 306 309 310 +f 311 124 4 +f 311 312 313 +f 314 315 316 +f 314 317 315 +f 318 319 320 +f 318 321 322 +f 318 322 319 +f 321 323 324 +f 325 326 327 +f 325 328 329 +f 158 160 195 +f 159 162 330 +f 205 201 331 +f 200 332 333 +f 333 202 200 +f 333 334 335 +f 203 201 205 +f 333 332 336 +f 337 338 339 +f 337 340 341 +f 338 341 342 +f 338 337 341 +f 339 342 343 +f 337 344 340 +f 337 339 345 +f 339 343 345 +f 346 347 348 +f 346 349 347 +f 347 349 350 +f 346 348 302 +f 351 352 353 +f 351 354 352 +f 355 356 357 +f 355 358 356 +f 359 360 98 +f 359 361 362 +f 363 364 365 +f 363 366 367 +f 368 369 370 +f 368 371 372 +f 373 374 375 +f 373 376 374 +f 377 378 379 +f 377 380 378 +f 378 380 381 +f 377 379 382 +f 383 384 385 +f 383 386 387 +f 388 389 390 +f 388 391 392 +f 393 394 395 +f 393 396 397 +f 398 399 358 +f 398 400 401 +f 400 402 403 +f 400 358 402 +f 324 404 322 +f 324 323 405 +f 404 406 322 +f 404 407 408 +f 406 409 322 +f 406 408 410 +f 411 412 413 +f 411 414 412 +f 415 416 417 +f 415 418 419 +f 420 419 421 +f 420 422 423 +f 417 416 420 +f 417 424 415 +f 419 425 426 +f 419 416 415 +f 417 423 427 +f 416 419 420 +f 428 429 430 +f 428 431 432 +f 433 434 435 +f 433 436 437 +f 438 439 440 +f 438 441 439 +f 442 443 272 +f 442 444 443 +f 184 183 445 +f 184 179 178 +f 446 447 448 +f 446 449 334 +f 447 334 450 +f 446 448 451 +f 452 326 325 +f 452 453 326 +f 454 455 456 +f 454 457 458 +f 459 460 461 +f 459 462 460 +f 463 464 465 +f 463 466 467 +f 463 465 468 +f 464 467 469 +f 470 14 17 +f 470 471 14 +f 472 473 474 +f 472 475 476 +f 477 478 479 +f 477 480 478 +f 481 482 483 +f 481 484 485 +f 484 481 486 +f 481 485 482 +f 487 488 489 +f 487 490 491 +f 490 492 493 +f 487 491 488 +f 494 495 496 +f 494 497 495 +f 498 499 500 +f 498 154 501 +f 502 503 504 +f 502 505 506 +f 507 508 504 +f 507 503 509 +f 509 510 508 +f 509 503 511 +f 503 502 506 +f 502 504 512 +f 513 166 514 +f 513 167 166 +f 515 516 517 +f 515 518 519 +f 515 517 520 +f 516 519 521 +f 522 523 524 +f 522 525 526 +f 523 526 527 +f 523 522 526 +f 528 529 530 +f 528 531 532 +f 533 534 535 +f 533 536 537 +f 536 538 539 +f 536 540 541 +f 541 538 536 +f 541 542 543 +f 538 541 539 +f 533 537 534 +f 229 228 544 +f 227 232 230 +f 228 12 6 +f 227 229 232 +f 545 546 547 +f 545 548 549 +f 550 551 552 +f 550 553 551 +f 554 555 556 +f 554 557 558 +f 559 557 554 +f 559 560 561 +f 554 558 555 +f 557 562 558 +f 563 564 565 +f 563 566 564 +f 567 76 568 +f 567 77 76 +f 569 570 571 +f 569 150 572 +f 573 574 575 +f 573 576 437 +f 577 578 579 +f 577 580 581 +f 582 580 577 +f 582 583 584 +f 580 584 585 +f 582 579 583 +f 165 521 519 +f 165 586 521 +f 521 587 516 +f 165 164 556 +f 186 588 187 +f 186 589 588 +f 590 591 592 +f 590 593 591 +f 594 595 596 +f 594 597 395 +f 598 367 599 +f 598 600 601 +f 367 601 363 +f 598 599 602 +f 603 604 605 +f 603 606 607 +f 608 609 179 +f 608 610 611 +f 612 613 614 +f 612 615 616 +f 617 618 619 +f 617 620 618 +f 621 622 623 +f 621 624 622 +f 625 626 627 +f 625 628 626 +f 629 241 630 +f 629 631 632 +f 633 634 635 +f 633 636 637 +f 638 639 640 +f 638 641 639 +f 53 642 643 +f 53 58 644 +f 645 646 407 +f 645 647 317 +f 648 649 650 +f 648 651 652 +f 648 652 649 +f 651 653 654 +f 655 656 657 +f 655 658 659 +f 660 661 579 +f 660 662 663 +f 664 665 666 +f 664 667 665 +f 552 668 669 +f 552 670 550 +f 552 669 671 +f 668 551 672 +f 673 674 675 +f 673 676 674 +f 677 678 679 +f 677 505 512 +f 506 505 677 +f 506 680 511 +f 677 512 681 +f 503 506 511 +f 682 683 280 +f 682 684 685 +f 683 685 686 +f 683 682 685 +f 682 280 687 +f 683 686 281 +f 413 688 689 +f 413 690 691 +f 692 693 694 +f 692 445 695 +f 696 692 695 +f 692 694 697 +f 698 699 700 +f 698 701 702 +f 699 702 703 +f 699 704 705 +f 706 707 708 +f 706 709 710 +f 707 671 711 +f 707 710 712 +f 713 714 715 +f 713 716 717 +f 718 719 720 +f 718 721 719 +f 722 723 724 +f 722 725 726 +f 335 727 333 +f 335 449 728 +f 729 730 731 +f 729 732 733 +f 729 733 730 +f 732 734 735 +f 736 18 471 +f 736 737 18 +f 736 738 737 +f 547 471 545 +f 739 740 741 +f 739 742 740 +f 743 744 745 +f 743 746 744 +f 747 748 749 +f 747 750 751 +f 752 753 754 +f 752 289 288 +f 752 754 755 +f 753 288 756 +f 555 558 757 +f 554 556 758 +f 757 759 760 +f 757 558 562 +f 761 762 763 +f 761 764 765 +f 766 767 768 +f 766 769 770 +f 767 766 770 +f 766 768 676 +f 609 771 210 +f 608 179 184 +f 772 773 774 +f 772 775 776 +f 777 778 779 +f 777 780 781 +f 782 783 474 +f 782 784 783 +f 785 661 786 +f 785 579 661 +f 787 788 397 +f 787 789 790 +f 788 790 791 +f 787 397 792 +f 793 106 794 +f 793 103 106 +f 795 796 797 +f 795 798 796 +f 799 800 801 +f 799 802 800 +f 803 804 805 +f 803 806 807 +f 804 808 805 +f 804 807 809 +f 810 811 812 +f 810 813 811 +f 814 815 816 +f 814 817 818 +f 814 818 815 +f 817 819 820 +f 551 553 821 +f 550 670 553 +f 822 823 824 +f 822 825 826 +f 822 827 825 +f 822 824 827 +f 823 828 824 +f 826 829 828 +f 587 830 831 +f 163 519 166 +f 832 833 834 +f 832 835 836 +f 837 838 839 +f 837 840 723 +f 838 837 726 +f 837 839 841 +f 842 843 844 +f 842 845 846 +f 847 848 849 +f 847 850 851 +f 852 853 854 +f 852 855 856 +f 857 858 859 +f 857 860 861 +f 862 863 864 +f 862 865 866 +f 862 866 863 +f 865 867 868 +f 865 868 866 +f 867 869 486 +f 869 870 486 +f 869 871 813 +f 872 873 874 +f 872 875 876 +f 877 245 878 +f 877 874 879 +f 880 881 882 +f 880 345 883 +f 884 885 886 +f 884 887 888 +f 859 858 889 +f 859 890 857 +f 891 67 892 +f 891 893 894 +f 895 534 537 +f 895 259 534 +f 896 897 898 +f 896 899 900 +f 897 900 901 +f 896 902 903 +f 175 904 905 +f 175 174 906 +f 907 908 909 +f 907 910 908 +f 908 911 912 +f 908 910 911 +f 385 384 480 +f 385 913 914 +f 915 916 917 +f 243 918 246 +f 919 920 921 +f 919 922 920 +f 923 924 925 +f 923 926 924 +f 90 89 927 +f 90 87 86 +f 928 929 664 +f 928 930 931 +f 932 933 719 +f 932 593 934 +f 933 720 719 +f 933 932 934 +f 935 936 937 +f 935 938 936 +f 939 940 941 +f 939 942 224 +f 943 830 521 +f 943 944 945 +f 946 894 947 +f 946 948 894 +f 106 103 102 +f 106 949 794 +f 2 123 950 +f 1 3 951 +f 952 953 954 +f 952 955 956 +f 957 958 959 +f 957 960 961 +f 730 962 963 +f 730 733 962 +f 730 963 731 +f 962 462 964 +f 965 966 967 +f 965 968 966 +f 969 970 971 +f 969 972 973 +f 974 975 802 +f 974 976 977 +f 975 977 978 +f 974 979 976 +f 974 802 980 +f 975 978 981 +f 982 341 491 +f 982 493 983 +f 607 240 984 +f 607 606 240 +f 607 984 985 +f 240 242 984 +f 986 987 988 +f 986 989 990 +f 987 991 992 +f 987 990 991 +f 993 994 995 +f 993 996 994 +f 997 998 999 +f 997 1000 1001 +f 1000 171 1001 +f 1000 1002 171 +f 1003 1004 1005 +f 1003 1006 1007 +f 1008 1009 1010 +f 1008 1011 1012 +f 1013 1014 1015 +f 1013 1016 1014 +f 1017 639 1018 +f 1017 640 639 +f 1019 1020 1021 +f 1019 455 1020 +f 1022 455 1019 +f 1022 1023 455 +f 455 1023 456 +f 1022 1024 1025 +f 1026 1027 1028 +f 1026 659 1029 +f 1030 1031 734 +f 1030 1032 1033 +f 1034 1021 1035 +f 1034 1024 1021 +f 1036 1037 1038 +f 1036 1039 1040 +f 1037 1036 1040 +f 1036 1038 1041 +f 236 238 239 +f 237 1042 1043 +f 1044 1045 1046 +f 1044 1047 1045 +f 1048 1049 375 +f 1048 1050 1051 +f 268 271 1052 +f 266 1053 269 +f 1054 1055 1056 +f 1054 1057 634 +f 1058 1059 1060 +f 1058 1061 1059 +f 1062 1063 1064 +f 1062 1065 1066 +f 1063 1067 1068 +f 1067 1062 1066 +f 1069 1070 1071 +f 1069 863 1072 +f 1069 1071 1073 +f 1070 1072 1074 +f 548 1075 1076 +f 548 471 1075 +f 1076 1075 1077 +f 545 549 1078 +f 1079 1080 1081 +f 1079 1082 1083 +f 1079 1083 1080 +f 1082 1084 1085 +f 339 338 342 +f 340 491 341 +f 1086 1087 1088 +f 1086 1089 1090 +f 1087 1091 1088 +f 1087 1092 1093 +f 1094 1095 1096 +f 1094 904 1097 +f 1098 1099 1100 +f 1098 275 1101 +f 1102 1100 1103 +f 1102 1104 1100 +f 1102 1103 797 +f 1100 1105 1106 +f 1099 1105 1100 +f 1099 1107 1105 +f 1100 1106 1103 +f 1099 1101 1108 +f 1109 1110 1111 +f 1109 1112 1113 +f 1114 1115 1116 +f 1114 1117 1118 +f 1119 680 679 +f 1119 466 1120 +f 1121 1122 1123 +f 1121 1124 1125 +f 1126 1124 1121 +f 1126 1127 1128 +f 1129 1124 1126 +f 1129 1130 1131 +f 1121 1125 1122 +f 1129 1128 1130 +f 1132 1133 1134 +f 1132 1135 1136 +f 1011 1137 1138 +f 1011 1008 1139 +f 1140 1137 1141 +f 1140 483 1142 +f 1138 1140 1142 +f 1140 1141 1143 +f 1144 1145 1146 +f 1144 1147 1148 +f 1149 441 1150 +f 1149 1151 441 +f 1152 1153 172 +f 1152 1002 1154 +f 1155 170 31 +f 1155 1156 1157 +f 1158 1159 686 +f 1158 1160 1159 +f 1161 820 1162 +f 1161 1163 820 +f 1164 1165 1148 +f 1164 1166 1165 +f 936 1167 937 +f 936 1168 1169 +f 1170 1050 1171 +f 1170 1172 1173 +f 750 1174 1175 +f 750 749 1176 +f 1174 1177 1175 +f 1174 1178 1179 +f 1177 1179 1180 +f 1174 1181 1178 +f 85 1182 1183 +f 85 1184 1185 +f 1186 988 1187 +f 1186 1188 1189 +f 1190 1191 1192 +f 1190 1193 1191 +f 1194 156 1111 +f 1194 1195 157 +f 1113 1110 1109 +f 1113 1196 1197 +f 1113 1112 1196 +f 1109 1111 1198 +f 121 709 1199 +f 120 122 123 +f 121 1199 524 +f 709 708 1199 +f 1200 1201 735 +f 1200 1202 1203 +f 1201 1204 735 +f 1201 1203 1205 +f 1206 1207 1208 +f 1206 1146 1145 +f 1209 1208 1207 +f 1209 1210 1211 +f 1211 1210 1212 +f 1211 1208 1209 +f 1209 1207 1213 +f 1206 1214 881 +f 497 309 1215 +f 497 1216 309 +f 73 1217 1218 +f 73 1219 69 +f 73 1218 1220 +f 1217 1221 1222 +f 1223 1224 1225 +f 1223 1153 1224 +f 1223 1066 1065 +f 1226 1227 1228 +f 119 1229 1230 +f 119 116 1231 +f 1229 1232 1233 +f 1229 1234 1232 +f 1235 1236 1237 +f 1235 1238 1236 +f 619 618 1239 +f 619 437 617 +f 803 807 804 +f 806 1240 1241 +f 853 856 1095 +f 852 854 855 +f 583 1242 764 +f 583 579 785 +f 1242 1243 1244 +f 583 764 584 +f 39 43 1245 +f 42 809 1246 +f 804 41 808 +f 42 1246 43 +f 1247 1248 990 +f 1247 1249 1250 +f 1251 1252 1253 +f 1251 1254 1252 +f 1255 1256 1257 +f 1255 1258 1259 +f 1260 1258 1261 +f 1260 1262 1263 +f 1260 1261 1192 +f 1258 1264 1261 +f 1265 451 448 +f 1265 1043 1266 +f 398 358 400 +f 399 356 358 +f 1267 1268 1269 +f 1267 1270 1268 +f 1271 1272 1273 +f 1271 407 405 +f 1272 1274 1275 +f 1272 1276 1274 +f 305 1277 301 +f 305 1278 1277 +f 980 802 799 +f 980 1279 974 +f 1055 1280 1056 +f 1055 1281 1282 +f 1283 1284 424 +f 1283 1285 1284 +f 410 409 406 +f 410 1286 316 +f 409 319 322 +f 409 1287 1288 +f 492 1289 493 +f 492 1290 1136 +f 1150 841 839 +f 1150 441 1291 +f 1292 1156 1293 +f 1292 1294 1156 +f 1295 1296 1297 +f 1292 1298 1294 +f 1299 1300 518 +f 1299 1060 1301 +f 1299 1302 1060 +f 1299 518 520 +f 1303 1304 1305 +f 1303 1306 1304 +f 61 1307 1308 +f 61 1309 109 +f 61 109 1307 +f 1309 461 460 +f 1310 1311 1312 +f 1310 1313 805 +f 1314 1311 805 +f 1314 1298 1312 +f 1310 1312 1315 +f 1314 1294 1298 +f 1316 1317 145 +f 1316 1318 1319 +f 1320 1321 1322 +f 1320 1317 1323 +f 1320 1323 1321 +f 1316 145 1318 +f 1324 1325 1326 +f 1324 794 1327 +f 793 1326 1328 +f 1325 1329 1330 +f 793 1328 1331 +f 1326 1330 1332 +f 1325 1324 1327 +f 793 1331 103 +f 1333 1334 1335 +f 1333 443 444 +f 1336 661 1337 +f 1336 786 661 +f 1137 1140 1138 +f 483 482 1338 +f 1339 1340 1341 +f 1339 1342 318 +f 1343 122 1344 +f 1343 950 122 +f 1345 1346 1347 +f 1345 1348 1346 +f 1349 1350 1351 +f 1349 1352 1353 +f 1354 1355 379 +f 1354 602 1356 +f 1008 1010 1139 +f 1009 1357 1010 +f 1358 1359 1360 +f 1358 1361 1016 +f 1359 1358 1016 +f 1358 1360 1257 +f 1362 1363 1364 +f 1362 1365 1363 +f 1366 1367 372 +f 1366 1368 1369 +f 1370 1371 746 +f 1370 1372 1373 +f 1374 1375 1376 +f 1374 1377 1378 +f 1377 1376 1379 +f 1374 1378 1375 +f 1380 1381 1382 +f 1380 1383 1384 +f 1381 1384 1385 +f 1380 1382 478 +f 1386 46 756 +f 1386 44 46 +f 1387 336 332 +f 1387 1388 450 +f 1387 1389 815 +f 1387 332 1389 +f 1390 1391 140 +f 1390 1392 1393 +f 1394 540 979 +f 1394 542 540 +f 704 1395 473 +f 704 1396 1397 +f 1398 786 737 +f 1398 1399 1400 +f 1399 1401 1244 +f 1399 1398 1401 +f 7 1402 10 +f 7 12 230 +f 1403 1404 1215 +f 1403 1405 1406 +f 1407 1408 496 +f 1407 1409 1371 +f 1404 495 1215 +f 1404 1406 1409 +f 1084 1410 1085 +f 1084 1411 1410 +f 1412 1413 1414 +f 1412 1415 1416 +f 1417 1418 1419 +f 1417 1420 1418 +f 1421 1418 1420 +f 1421 1422 1357 +f 1421 1420 1423 +f 1418 128 132 +f 589 1424 1425 +f 589 1426 1427 +f 1424 589 1427 +f 589 1425 588 +f 1428 1429 1430 +f 1428 1431 1053 +f 1432 1433 1434 +f 1432 1435 1433 +f 1436 32 1437 +f 1436 1438 1439 +f 1438 1440 1441 +f 1436 1439 32 +f 1438 1442 1439 +f 1438 1441 1442 +f 1097 1095 1094 +f 1097 1443 1095 +f 987 1187 988 +f 991 1444 829 +f 991 990 711 +f 986 988 1445 +f 1446 712 710 +f 1446 1447 1448 +f 1446 1448 1449 +f 1447 1450 1451 +f 67 66 892 +f 67 948 63 +f 891 892 958 +f 66 63 70 +f 1452 296 304 +f 1452 1068 296 +f 368 370 1453 +f 369 249 370 +f 53 643 55 +f 642 1454 643 +f 53 55 52 +f 643 1454 725 +f 644 1455 1456 +f 644 642 53 +f 1457 1458 970 +f 1457 1459 24 +f 1460 1461 1352 +f 1460 1462 70 +f 1463 889 401 +f 1463 1464 1465 +f 1466 1402 235 +f 1466 1467 1468 +f 235 1402 7 +f 235 234 1469 +f 1402 1468 10 +f 1466 1469 1467 +f 1470 1471 493 +f 1470 1472 1471 +f 1473 1471 1472 +f 1473 1474 1475 +f 1470 1289 1472 +f 1471 1476 493 +f 1477 1478 1479 +f 1477 263 1478 +f 1480 1481 238 +f 1480 1482 1481 +f 1483 1484 1485 +f 1483 1486 1487 +f 576 1486 700 +f 573 437 574 +f 1483 1487 1456 +f 576 700 620 +f 954 953 795 +f 952 1488 955 +f 34 1489 35 +f 34 1162 1490 +f 33 35 36 +f 1489 75 35 +f 1491 1492 1493 +f 1491 1494 1495 +f 1496 430 429 +f 1496 309 1216 +f 392 1497 1498 +f 392 391 1499 +f 1497 1499 1500 +f 392 1498 389 +f 959 1462 1501 +f 959 958 892 +f 1460 70 1461 +f 1462 892 66 +f 959 1501 1502 +f 70 65 1461 +f 1503 1504 531 +f 1503 813 1505 +f 719 721 770 +f 719 591 932 +f 721 1506 770 +f 791 718 720 +f 1507 1508 1509 +f 1507 1510 1508 +f 1508 1510 1434 +f 1507 1509 1511 +f 1512 1513 1514 +f 1512 1515 1516 +f 1515 1517 1518 +f 1515 1512 1519 +f 1517 1515 1519 +f 1515 1518 1520 +f 610 1521 1522 +f 610 608 184 +f 1521 610 697 +f 610 1522 611 +f 1522 674 768 +f 608 611 1523 +f 1524 1525 1526 +f 1524 1527 1528 +f 1525 972 1529 +f 1525 1519 972 +f 1530 1531 626 +f 1530 635 1531 +f 1532 1392 1533 +f 1532 1534 1535 +f 1533 1392 1390 +f 1533 206 1532 +f 1533 139 206 +f 1390 1393 1536 +f 1537 139 138 +f 1537 204 206 +f 1532 206 1534 +f 1537 142 1538 +f 1539 1520 1540 +f 1539 1516 1520 +f 1275 1273 1272 +f 1275 1541 1273 +f 1028 1542 1543 +f 1028 1544 1542 +f 1080 1083 1545 +f 1082 1079 1546 +f 1141 1137 1139 +f 1140 1143 483 +f 1547 1548 855 +f 1547 696 1548 +f 883 1549 881 +f 883 345 343 +f 1550 1551 1552 +f 1550 1553 1551 +f 1550 1552 634 +f 1551 1553 1554 +f 1555 1556 1557 +f 1555 1558 1559 +f 1122 1560 1561 +f 1122 1125 1560 +f 164 1562 556 +f 163 165 519 +f 1563 812 1564 +f 1563 1565 812 +f 1566 1210 1213 +f 1566 1567 1568 +f 269 1053 1431 +f 269 922 270 +f 349 1569 1570 +f 347 350 748 +f 1571 1572 1573 +f 1571 1574 1575 +f 1572 1576 1577 +f 1571 1573 1574 +f 1578 1579 1580 +f 1578 1581 1240 +f 1579 1313 1445 +f 1579 1240 1313 +f 1582 1583 1584 +f 1582 1469 1585 +f 158 195 1586 +f 160 330 1587 +f 1588 1589 1590 +f 1588 1591 926 +f 1184 1592 724 +f 85 1185 1182 +f 257 259 1593 +f 258 535 534 +f 1594 1595 984 +f 1594 1596 1597 +f 1131 1598 1599 +f 1124 1600 1125 +f 1601 1602 1603 +f 1601 1604 846 +f 1605 1606 1607 +f 1605 1608 1606 +f 1301 1609 1299 +f 1301 1610 1611 +f 1612 994 1613 +f 1612 1614 995 +f 1615 1616 1617 +f 1615 631 1616 +f 472 474 783 +f 473 1395 474 +f 1618 430 1496 +f 1618 136 431 +f 212 215 1619 +f 217 211 1568 +f 1620 1621 1622 +f 1620 45 1623 +f 279 278 1624 +f 279 1335 687 +f 278 941 1624 +f 277 279 687 +f 815 818 1625 +f 815 1389 816 +f 1021 1020 1626 +f 1034 1035 1025 +f 186 189 1426 +f 185 187 188 +f 1211 1627 1214 +f 1212 211 1628 +f 1627 1628 1629 +f 1212 1210 211 +f 1630 1631 1632 +f 1630 1633 1634 +f 1635 1636 1637 +f 1635 31 1636 +f 571 1638 1639 +f 571 570 691 +f 1569 1640 1641 +f 1569 1277 1642 +f 1643 1644 956 +f 1643 1645 1646 +f 1643 1647 1645 +f 1643 956 955 +f 1648 1305 1304 +f 1648 848 851 +f 1649 1650 779 +f 1649 1651 1650 +f 1652 1653 950 +f 1652 1654 1653 +f 765 762 761 +f 765 1655 1344 +f 1655 1654 1652 +f 765 1344 1656 +f 1655 1652 1343 +f 1654 1657 1653 +f 297 296 1067 +f 298 1658 303 +f 1452 304 1659 +f 295 297 1658 +f 83 1544 1660 +f 83 81 1542 +f 1661 678 1662 +f 1661 1663 678 +f 1664 774 773 +f 1664 1665 1666 +f 1667 1668 1669 +f 1667 1670 1668 +f 1671 653 651 +f 1671 1672 1673 +f 1674 759 944 +f 1674 760 759 +f 1675 1676 1677 +f 1675 930 1678 +f 1679 733 1204 +f 1679 462 962 +f 113 1680 114 +f 113 1681 1682 +f 1683 1684 1685 +f 1683 1577 1684 +f 489 1686 1290 +f 489 488 1687 +f 234 233 1688 +f 235 1469 1466 +f 1656 1689 762 +f 1656 1344 1690 +f 1689 1691 762 +f 1689 1656 1692 +f 1693 1479 1632 +f 1693 1694 1477 +f 1695 998 1696 +f 1695 1252 999 +f 1697 1375 1698 +f 1697 1699 1375 +f 1697 1698 1700 +f 1374 1376 1377 +f 1697 1700 1127 +f 1698 1701 1702 +f 1703 1704 1642 +f 1703 1705 1704 +f 177 174 173 +f 177 1706 174 +f 886 885 1707 +f 886 391 1708 +f 886 1707 1709 +f 884 1708 887 +f 831 830 943 +f 831 517 587 +f 1369 1367 1366 +f 1369 1581 1189 +f 1630 1632 1479 +f 1631 1710 1711 +f 1712 565 564 +f 1712 1713 565 +f 565 1714 563 +f 565 1715 850 +f 1007 1004 1003 +f 1007 1716 1004 +f 903 902 1717 +f 903 899 896 +f 1718 1719 1294 +f 1718 805 808 +f 934 720 933 +f 934 394 1720 +f 393 395 1721 +f 394 593 395 +f 1722 998 1001 +f 1722 1723 1059 +f 1695 1696 1724 +f 1722 1001 40 +f 1725 1726 1727 +f 1725 150 569 +f 1386 756 294 +f 46 1166 1728 +f 756 1729 753 +f 46 45 1166 +f 436 1730 574 +f 436 1731 1732 +f 1733 1734 1735 +f 1733 1676 1736 +f 270 922 919 +f 270 267 266 +f 1596 1737 1597 +f 1596 242 632 +f 1738 1739 317 +f 1738 1740 1739 +f 1738 317 647 +f 1739 96 1741 +f 1742 1743 1744 +f 1742 726 1743 +f 446 451 449 +f 1265 1388 1480 +f 1745 1746 101 +f 1745 195 1746 +f 1747 1748 1685 +f 1747 1557 1556 +f 1041 1373 1749 +f 1041 1038 1373 +f 1750 1746 192 +f 1750 99 1746 +f 1746 99 101 +f 1750 192 1751 +f 1595 1670 984 +f 1595 1597 1752 +f 1529 972 971 +f 1529 1526 1525 +f 946 947 72 +f 894 893 1753 +f 893 1754 1755 +f 891 894 948 +f 1347 1411 738 +f 1347 1346 1410 +f 1346 1348 1756 +f 1347 1410 1411 +f 1757 1677 1758 +f 1757 432 1677 +f 1759 1760 1761 +f 1759 50 1029 +f 1762 1376 1699 +f 1762 1353 1461 +f 1763 1764 1765 +f 1763 1766 527 +f 1692 1765 1691 +f 1763 1690 1766 +f 1767 1262 1191 +f 1767 75 1263 +f 1768 1769 1770 +f 1768 310 876 +f 1768 1770 307 +f 1769 1771 1772 +f 1770 1772 1773 +f 1768 307 310 +f 955 1647 1643 +f 955 1488 272 +f 1774 1775 1776 +f 1774 386 1777 +f 1775 1777 1778 +f 1774 1776 387 +f 1779 1780 1781 +f 1779 1782 1783 +f 1538 1781 1537 +f 1538 461 1779 +f 1537 1781 204 +f 142 141 1784 +f 1779 461 1782 +f 1538 1784 459 +f 1363 1785 1786 +f 1363 1365 1787 +f 1785 1788 1786 +f 1785 1789 1788 +f 1786 1788 1790 +f 1786 1364 1363 +f 1786 1790 1364 +f 1788 1789 1555 +f 1719 808 1791 +f 1718 1294 1314 +f 1792 375 1793 +f 1792 1666 1665 +f 1792 1665 1050 +f 1666 1794 1795 +f 1796 1081 1080 +f 1796 1797 214 +f 1798 1799 1434 +f 1798 606 603 +f 1800 798 1801 +f 1800 641 798 +f 1802 1803 1494 +f 1802 320 1803 +f 1803 320 1804 +f 1802 1494 1341 +f 355 1805 358 +f 1806 1807 1805 +f 358 1805 1287 +f 399 401 889 +f 1805 1808 1288 +f 1805 1807 1808 +f 1808 1807 1804 +f 1806 355 357 +f 1809 1810 1752 +f 1809 1811 1812 +f 1810 1668 1670 +f 1813 1809 1812 +f 1668 1813 1669 +f 1810 1670 1752 +f 1667 1669 985 +f 1813 1812 1511 +f 1814 1279 980 +f 1814 1815 1279 +f 1816 456 1817 +f 1816 1756 457 +f 1818 1819 1624 +f 1818 1820 1821 +f 1415 1822 1416 +f 1415 1823 1822 +f 1005 1645 921 +f 1005 1004 1716 +f 1824 1825 1826 +f 1824 1233 1232 +f 1827 104 1416 +f 102 1828 105 +f 1560 1125 1600 +f 1122 1561 1123 +f 1829 1830 1195 +f 1829 1197 1831 +f 1832 1257 1256 +f 1832 1361 1358 +f 1132 1136 1686 +f 1135 1833 1136 +f 1834 1135 1134 +f 1135 1835 1833 +f 1481 1482 1836 +f 1480 238 1043 +f 1837 1838 1839 +f 1837 1840 1841 +f 1842 1306 1843 +f 1842 1844 1845 +f 1572 1577 1846 +f 1576 562 557 +f 1847 1848 1270 +f 1847 1641 1849 +f 1838 1841 1850 +f 1837 1839 1851 +f 212 214 216 +f 213 1081 214 +f 1852 1853 666 +f 1852 1574 1853 +f 1727 1726 1639 +f 1727 1854 1725 +f 1855 1639 161 +f 1855 1856 1854 +f 1170 1171 1172 +f 1050 1665 1171 +f 1482 1388 1625 +f 1482 1480 1388 +f 1857 1858 1494 +f 1857 1716 1007 +f 1859 1860 1861 +f 1859 1862 1863 +f 449 451 1864 +f 446 334 447 +f 1865 1439 1442 +f 1865 28 1439 +f 1599 1598 1866 +f 1131 1867 1600 +f 1451 1448 1447 +f 1451 636 1322 +f 1868 1869 1391 +f 1868 1870 362 +f 1870 1871 1872 +f 1870 1536 967 +f 1379 65 71 +f 1379 1376 1461 +f 58 1455 644 +f 58 57 1873 +f 57 51 50 +f 58 1873 1484 +f 604 1874 605 +f 604 1669 1508 +f 1678 1853 1875 +f 1678 930 928 +f 645 407 1271 +f 646 1876 408 +f 647 1273 1541 +f 645 317 1876 +f 1272 1271 405 +f 647 1541 1738 +f 1582 1877 1878 +f 1585 1688 1233 +f 1879 1880 1881 +f 1879 1270 1880 +f 1882 1883 1884 +f 1882 1731 435 +f 1827 102 104 +f 105 1885 106 +f 1412 1416 1886 +f 1822 1823 1887 +f 478 480 1888 +f 477 479 1889 +f 1449 1890 1446 +f 1449 1321 1323 +f 1449 1323 1891 +f 1320 1322 1892 +f 1886 104 1893 +f 1886 1413 1412 +f 104 103 1893 +f 1827 1416 1828 +f 578 1894 1895 +f 577 579 582 +f 1896 937 1167 +f 1896 1897 1898 +f 1899 1900 1901 +f 1899 1269 1902 +f 1899 1901 1903 +f 1900 1902 1904 +f 1905 1906 1907 +f 1905 1908 1906 +f 649 652 1909 +f 648 650 651 +f 1367 1188 1910 +f 1369 1368 1581 +f 1787 1911 1368 +f 1787 1365 1246 +f 1368 1912 1913 +f 1368 1911 1581 +f 1911 1241 1581 +f 1911 1787 1241 +f 1914 1915 1916 +f 1914 254 1915 +f 1499 1497 392 +f 1499 1709 1500 +f 1497 1500 1917 +f 1709 1707 1918 +f 1919 1894 578 +f 1919 581 1296 +f 695 1548 696 +f 695 445 1920 +f 1921 1922 1923 +f 1921 535 260 +f 1924 1925 36 +f 1924 1616 1926 +f 1927 1625 1163 +f 1927 1925 1836 +f 36 1925 1927 +f 36 35 77 +f 33 37 34 +f 1927 1836 1625 +f 1780 1783 1928 +f 1779 1781 1538 +f 1928 1783 1929 +f 1928 204 1780 +f 1928 1929 1930 +f 1783 1782 1929 +f 1931 1932 1933 +f 1931 500 499 +f 1934 1357 1422 +f 1934 1935 1073 +f 1935 1936 1937 +f 1934 1073 1357 +f 663 1337 661 +f 663 19 1938 +f 163 1562 164 +f 513 514 1939 +f 166 519 514 +f 165 556 555 +f 556 1562 758 +f 521 586 943 +f 1940 440 403 +f 1940 97 95 +f 1941 1942 1943 +f 1941 1944 708 +f 1945 1946 1947 +f 1945 1948 1949 +f 127 131 125 +f 127 1950 131 +f 162 1951 330 +f 162 161 1951 +f 585 1952 581 +f 585 763 1691 +f 222 221 1953 +f 222 219 218 +f 1954 1860 1859 +f 1954 255 1955 +f 632 241 629 +f 632 1737 1596 +f 629 630 1926 +f 236 606 237 +f 1956 1957 1958 +f 1956 508 1959 +f 1960 1904 1961 +f 1960 1176 1904 +f 1647 272 270 +f 952 956 953 +f 1005 921 365 +f 1645 1962 919 +f 436 574 437 +f 1730 1963 575 +f 1730 1732 1964 +f 433 437 619 +f 623 1253 1061 +f 623 622 1866 +f 1533 140 139 +f 1390 1536 1868 +f 1965 1966 1967 +f 1965 564 566 +f 1966 1968 1969 +f 1712 1967 1713 +f 1970 898 901 +f 1970 1971 898 +f 1972 1971 1970 +f 1972 1973 902 +f 1972 1974 1975 +f 1972 1608 1976 +f 1754 1977 1978 +f 894 1753 1979 +f 1754 1978 1285 +f 1977 1980 354 +f 861 1981 1982 +f 861 858 857 +f 1463 401 403 +f 889 356 399 +f 859 1465 890 +f 858 861 356 +f 1983 1153 1152 +f 1983 1224 1153 +f 1984 1136 1833 +f 1984 1985 1472 +f 1986 1985 1467 +f 1986 1472 1985 +f 1987 1985 1984 +f 1987 1468 1467 +f 1986 1467 1584 +f 1987 1833 10 +f 1453 371 368 +f 1453 1789 1785 +f 371 1912 372 +f 371 1453 1913 +f 1920 1096 856 +f 1920 445 905 +f 905 1096 1920 +f 905 183 175 +f 1920 1548 695 +f 1096 1095 856 +f 1988 1989 1990 +f 1988 675 1989 +f 1991 1992 1993 +f 1991 1994 1704 +f 1992 1995 1993 +f 1992 1704 1705 +f 1995 1996 1702 +f 1995 1997 1996 +f 1998 1917 1500 +f 1998 1999 1917 +f 2000 2001 1192 +f 2000 849 848 +f 1869 2002 1391 +f 1869 2003 2002 +f 2004 927 1887 +f 2004 2005 2006 +f 1820 87 90 +f 1820 226 87 +f 774 1795 2007 +f 1664 773 1665 +f 2008 2007 2009 +f 774 2010 772 +f 2011 611 2012 +f 2011 1523 611 +f 1696 1722 1059 +f 1695 1724 1252 +f 1909 651 2013 +f 651 650 1671 +f 2014 1118 1409 +f 2014 1406 1115 +f 1118 744 746 +f 1118 1115 1114 +f 1859 1861 1862 +f 1860 220 2015 +f 637 2016 2017 +f 637 636 1451 +f 2018 2019 1593 +f 2018 2020 2021 +f 2021 2019 2018 +f 2021 2022 2023 +f 2019 2024 1593 +f 2019 2021 2023 +f 420 421 1753 +f 426 2025 421 +f 2026 1959 276 +f 2026 2027 1957 +f 2028 2029 1249 +f 2028 1315 2029 +f 2029 2030 1249 +f 2029 2031 2030 +f 626 2032 1530 +f 626 628 2032 +f 906 1706 2033 +f 906 904 175 +f 1631 1711 2034 +f 1710 2035 1711 +f 1764 526 1297 +f 1763 1765 1692 +f 1619 213 212 +f 213 2036 1401 +f 1093 1091 1087 +f 1093 2037 2038 +f 381 380 2039 +f 381 600 378 +f 2029 1315 2031 +f 2028 1249 1247 +f 2040 199 2041 +f 2040 332 199 +f 1975 2042 2043 +f 1975 1974 2042 +f 2044 2045 2046 +f 2044 2047 1185 +f 1685 1684 2048 +f 1685 1748 2049 +f 1683 2050 1846 +f 1685 2048 1747 +f 1169 2051 1896 +f 1169 1168 2052 +f 2051 2053 2054 +f 2051 1169 2053 +f 1198 1613 2055 +f 1198 1112 1109 +f 1220 1218 2056 +f 1220 1219 73 +f 1995 2056 1993 +f 1702 1700 1698 +f 1702 2056 1995 +f 1220 1701 1219 +f 563 1714 2057 +f 565 1713 1715 +f 1714 2058 2057 +f 1712 564 1965 +f 2058 2059 2022 +f 2058 850 2059 +f 2058 2022 2057 +f 2059 2060 2023 +f 2059 2023 2022 +f 2060 849 2061 +f 2062 320 319 +f 2062 1804 320 +f 320 1340 318 +f 1803 1804 1495 +f 1339 1341 1858 +f 2062 319 2063 +f 2064 2065 2066 +f 2064 614 2065 +f 2067 1917 1999 +f 2067 366 1917 +f 1132 1134 1135 +f 1133 596 1134 +f 2068 382 182 +f 2068 1276 405 +f 181 1276 2068 +f 181 2069 210 +f 1274 210 209 +f 1274 1276 181 +f 2068 405 382 +f 1275 1274 209 +f 1715 2070 850 +f 1715 1713 2035 +f 727 2071 331 +f 335 334 449 +f 1148 1145 1144 +f 1148 2072 1164 +f 1778 100 99 +f 1778 1777 913 +f 100 2073 2074 +f 1778 99 1775 +f 994 2075 1613 +f 993 995 2054 +f 472 783 475 +f 474 1395 1047 +f 833 2076 1422 +f 832 834 1752 +f 677 681 678 +f 512 504 1958 +f 2077 2078 1623 +f 2077 47 2079 +f 1623 1621 1620 +f 1623 2078 2080 +f 1623 2080 2081 +f 2078 2077 2079 +f 480 384 1888 +f 480 2082 385 +f 2083 341 982 +f 2083 2084 341 +f 1542 2085 1543 +f 1542 2086 458 +f 1026 1543 659 +f 2085 2087 656 +f 1587 330 2088 +f 1587 2089 194 +f 158 1586 1871 +f 195 192 1746 +f 2090 725 1454 +f 2090 726 725 +f 2091 546 1078 +f 2091 738 547 +f 207 211 217 +f 210 2069 609 +f 2092 542 1394 +f 2092 2093 543 +f 1282 1281 1554 +f 1282 1280 1055 +f 533 535 540 +f 895 537 2020 +f 970 1458 1510 +f 970 973 1457 +f 970 1510 971 +f 1458 1435 1432 +f 1508 1434 1874 +f 1457 973 1459 +f 284 1864 451 +f 284 23 282 +f 2094 1256 1259 +f 2094 1490 1162 +f 412 414 1659 +f 411 413 689 +f 2095 1002 1000 +f 2095 2096 1154 +f 2064 2066 2097 +f 2065 614 1835 +f 2066 2098 1134 +f 2066 2065 2098 +f 872 876 873 +f 875 1771 1768 +f 616 613 612 +f 616 188 187 +f 769 591 770 +f 769 2099 592 +f 1277 1569 2100 +f 1569 1642 1640 +f 2101 356 1982 +f 2101 357 356 +f 925 2049 2102 +f 925 924 2050 +f 2103 2104 2105 +f 2067 1356 599 +f 908 912 2106 +f 911 2105 2107 +f 2105 2104 2107 +f 2103 2033 1356 +f 2104 1999 2108 +f 2103 2105 2033 +f 2109 1758 2110 +f 2109 2111 1758 +f 1758 1677 2110 +f 1757 2111 432 +f 2112 2113 2114 +f 2112 2115 2113 +f 2112 2114 922 +f 2113 2115 390 +f 1677 432 2116 +f 1677 1676 2110 +f 1677 2116 1675 +f 432 431 2116 +f 1550 634 637 +f 1552 1554 1281 +f 2117 741 740 +f 2117 1541 1275 +f 901 2118 1606 +f 901 2119 2118 +f 2118 2119 2120 +f 901 1606 1970 +f 214 1081 1796 +f 213 1619 2036 +f 2121 1637 1636 +f 2121 2122 1535 +f 2123 1637 2124 +f 2123 27 26 +f 2121 1636 149 +f 2123 2124 27 +f 2043 1973 1975 +f 2043 2075 1973 +f 1291 841 1150 +f 1291 2125 95 +f 839 1744 2126 +f 837 841 2127 +f 1742 839 838 +f 839 2126 2128 +f 837 2127 840 +f 1291 441 438 +f 1320 1892 2032 +f 1322 636 1892 +f 1530 636 635 +f 1322 1448 1451 +f 50 1660 1027 +f 50 1761 57 +f 1555 1557 1788 +f 1556 1748 1747 +f 1683 2049 2050 +f 1748 2129 2049 +f 528 485 484 +f 528 530 485 +f 701 2130 1761 +f 698 702 699 +f 1263 75 1489 +f 1263 1262 1767 +f 1489 1490 1263 +f 1924 77 567 +f 2094 1162 1256 +f 74 75 836 +f 2131 537 2132 +f 2131 2133 2020 +f 2133 2134 2057 +f 2131 2020 537 +f 1680 2135 2136 +f 112 114 2137 +f 751 2138 2139 +f 751 1175 2138 +f 206 205 2140 +f 138 140 141 +f 205 331 2141 +f 203 206 204 +f 331 2071 2141 +f 727 202 333 +f 206 2140 1534 +f 2141 2071 2142 +f 200 201 196 +f 2141 2142 2140 +f 1071 2143 1010 +f 1069 864 863 +f 2144 812 811 +f 2144 2145 1564 +f 2146 2147 2148 +f 2146 2149 2027 +f 860 1981 861 +f 860 1151 1564 +f 2149 2145 2150 +f 2149 2151 2101 +f 2145 1981 1564 +f 2149 2150 2027 +f 2144 1564 812 +f 860 890 1151 +f 2152 1930 2153 +f 2152 2154 2155 +f 2154 2156 2155 +f 2154 1016 1361 +f 2157 1221 1979 +f 2157 1994 1221 +f 501 1830 499 +f 501 157 1195 +f 1830 1831 1932 +f 1829 1195 1197 +f 1831 1830 1829 +f 1830 1932 499 +f 2158 1932 1831 +f 2158 1304 2159 +f 1931 1933 500 +f 2158 1197 1304 +f 1989 694 693 +f 1989 2160 1990 +f 262 1478 263 +f 262 265 1478 +f 2161 1843 2162 +f 2161 2163 2164 +f 888 2165 2166 +f 888 2107 2104 +f 1108 1334 1107 +f 1108 2167 682 +f 1107 1106 1105 +f 1107 1334 444 +f 1333 1335 271 +f 1108 1101 2167 +f 11 8 5 +f 11 614 613 +f 1049 2168 375 +f 1049 1173 2168 +f 1923 979 540 +f 1923 1922 2169 +f 1921 1923 540 +f 1923 1015 979 +f 1681 2170 2171 +f 1681 115 1359 +f 2148 1237 2172 +f 2146 2151 2149 +f 1237 2173 1235 +f 1237 2148 2147 +f 2174 1936 2175 +f 2174 2176 1937 +f 2174 1937 1936 +f 2176 1191 1193 +f 1945 1949 1946 +f 1948 1180 1179 +f 1895 1894 1156 +f 1895 2177 578 +f 1894 1293 1156 +f 578 2177 660 +f 1819 1821 1430 +f 1820 1818 226 +f 1288 2063 319 +f 358 1287 402 +f 1076 1077 714 +f 1075 471 470 +f 465 469 2178 +f 464 463 467 +f 463 468 466 +f 465 2178 468 +f 2012 2179 770 +f 2012 611 768 +f 767 2012 768 +f 2012 789 2011 +f 1306 2159 1304 +f 1842 1843 2180 +f 2181 1755 1283 +f 2181 1753 893 +f 1520 1518 1540 +f 1512 1516 2182 +f 1539 1540 1862 +f 1518 1517 2183 +f 1046 1045 221 +f 1046 256 252 +f 1611 1939 1609 +f 1611 1610 1245 +f 2184 2185 2186 +f 2184 1583 2185 +f 1875 1590 1589 +f 1875 1853 1590 +f 1875 1589 1736 +f 1588 1574 1573 +f 1952 1765 1297 +f 585 581 580 +f 1952 1297 1296 +f 1764 527 526 +f 1297 526 2187 +f 1692 1691 1689 +f 1296 1293 1919 +f 1295 2187 1298 +f 1951 1638 691 +f 159 330 160 +f 571 1639 569 +f 1638 161 1639 +f 1930 1929 62 +f 2152 2153 1014 +f 656 1543 2085 +f 655 1172 658 +f 851 850 2070 +f 851 848 847 +f 325 265 264 +f 327 2070 1634 +f 326 453 2188 +f 328 325 264 +f 928 664 1853 +f 664 931 667 +f 722 724 2189 +f 723 2045 1185 +f 2190 2003 1869 +f 2190 362 361 +f 1390 140 1533 +f 1391 2002 141 +f 1007 1006 1858 +f 1003 365 1006 +f 30 29 2177 +f 26 31 2123 +f 30 2177 1895 +f 29 28 2191 +f 489 1687 1686 +f 488 344 1687 +f 735 1204 733 +f 1200 734 1202 +f 2192 1850 1329 +f 2192 2193 1839 +f 1330 1850 2194 +f 1330 1329 1850 +f 2192 1329 2195 +f 1326 1332 1328 +f 219 2015 220 +f 219 2196 2197 +f 2198 2194 1841 +f 2198 1092 1090 +f 2198 1840 1092 +f 2198 1841 1840 +f 173 183 178 +f 174 1706 906 +f 1421 1357 128 +f 1422 2175 1934 +f 2199 170 169 +f 2199 2200 2201 +f 2199 169 2202 +f 170 1155 168 +f 170 1636 31 +f 2201 2200 2203 +f 2201 2203 149 +f 1062 2202 1065 +f 1767 1191 2204 +f 1262 1192 1191 +f 627 626 1531 +f 625 2205 2206 +f 1916 2207 1142 +f 1916 844 2207 +f 312 1450 313 +f 312 311 951 +f 1890 1891 2208 +f 1449 1448 1321 +f 1898 2209 156 +f 1898 937 1896 +f 43 1246 2210 +f 42 41 809 +f 532 531 1504 +f 532 529 528 +f 1183 1182 2211 +f 85 1183 78 +f 1182 1185 2047 +f 85 80 1184 +f 134 1673 135 +f 134 137 496 +f 1938 1337 663 +f 1938 786 1336 +f 1336 1337 1938 +f 661 660 663 +f 785 786 1398 +f 663 662 19 +f 966 152 1854 +f 966 968 152 +f 968 1535 2122 +f 968 965 1535 +f 575 2212 1487 +f 575 1963 2213 +f 573 1487 576 +f 2212 2213 2214 +f 1267 1880 1270 +f 1268 1385 2215 +f 1851 2216 2217 +f 1851 1839 2216 +f 2218 1280 1282 +f 2218 1405 308 +f 2120 2119 2219 +f 2120 2220 2221 +f 2222 1408 1371 +f 2222 654 2223 +f 722 56 2224 +f 2189 2225 56 +f 1428 1053 268 +f 1431 2226 269 +f 2227 954 797 +f 954 2228 1488 +f 638 640 796 +f 1017 1018 2229 +f 2230 2231 2232 +f 2230 2233 2234 +f 2195 2193 2192 +f 2195 225 224 +f 2235 1413 467 +f 2235 1414 1413 +f 289 2236 292 +f 289 752 2184 +f 613 2237 8 +f 616 615 2238 +f 612 614 615 +f 613 8 11 +f 659 658 2239 +f 655 1543 656 +f 658 1172 1171 +f 655 659 1543 +f 1408 1407 1371 +f 1408 2223 496 +f 713 657 714 +f 717 1173 657 +f 1626 1020 2240 +f 1626 2046 2241 +f 1433 2242 1266 +f 1433 1042 1434 +f 1433 1266 1043 +f 2242 23 284 +f 984 1670 1667 +f 1594 242 1596 +f 2243 1915 1863 +f 2243 1540 2244 +f 2245 853 1095 +f 2245 909 853 +f 2104 2165 888 +f 2108 1999 2246 +f 1734 1589 2247 +f 1733 2110 1676 +f 2248 602 379 +f 2248 600 598 +f 664 666 1853 +f 665 667 2249 +f 2206 146 143 +f 2206 1771 146 +f 1217 1222 1218 +f 1221 1994 1991 +f 1979 947 894 +f 2157 2025 2250 +f 1979 421 2025 +f 946 72 68 +f 2001 848 1648 +f 2000 1192 1261 +f 1018 2251 2229 +f 1018 641 2252 +f 2183 1528 2253 +f 2183 1519 1528 +f 1041 1749 2254 +f 1373 1372 1749 +f 2184 2186 2236 +f 2185 1878 2255 +f 2184 2236 289 +f 2186 1372 292 +f 289 292 287 +f 2186 1749 1372 +f 2256 1039 1825 +f 2256 2257 1040 +f 1036 2254 1039 +f 2255 2254 2185 +f 1037 1040 2258 +f 1039 1826 1825 +f 1373 1038 1371 +f 1039 2255 1826 +f 475 783 2259 +f 472 476 473 +f 282 286 283 +f 285 2124 286 +f 2260 1244 1657 +f 2260 2261 764 +f 701 2262 702 +f 701 1761 1760 +f 2262 1760 2263 +f 2130 1485 1873 +f 941 940 1624 +f 941 281 939 +f 2264 2265 2263 +f 2264 1029 2265 +f 2266 745 744 +f 2266 2079 2267 +f 2259 783 2268 +f 475 2269 476 +f 2270 114 2061 +f 2270 1264 2137 +f 1264 1257 2137 +f 1264 1258 1255 +f 2271 1112 2055 +f 2271 1937 1193 +f 305 301 300 +f 2100 346 302 +f 300 302 299 +f 2100 1569 349 +f 2272 2273 921 +f 2272 920 2114 +f 2273 2274 1498 +f 2272 921 920 +f 1354 2033 1355 +f 2103 1999 2104 +f 2090 1454 2275 +f 56 722 2189 +f 1824 1232 1825 +f 1233 1688 1230 +f 1339 1858 1006 +f 1802 1341 1340 +f 1007 1858 1857 +f 318 1342 321 +f 2276 1228 1227 +f 2276 1658 297 +f 2270 2061 1261 +f 114 1680 2061 +f 2277 2278 1442 +f 2277 19 662 +f 560 1364 2048 +f 559 561 557 +f 1248 711 990 +f 1248 1250 1942 +f 2091 1078 2279 +f 545 471 548 +f 1078 549 2280 +f 2091 2279 738 +f 1639 1726 569 +f 1855 161 1856 +f 569 1726 1725 +f 1951 691 2281 +f 570 569 572 +f 1727 1639 1855 +f 790 1506 721 +f 790 789 1506 +f 967 1856 1870 +f 967 1854 1856 +f 1715 2035 2070 +f 1713 1967 2035 +f 1046 221 256 +f 1045 2282 221 +f 2282 1395 1953 +f 2282 1047 1395 +f 2283 2284 2285 +f 2283 1284 353 +f 22 374 1437 +f 22 21 375 +f 2238 188 616 +f 2238 1426 188 +f 243 245 915 +f 244 144 878 +f 988 1189 1445 +f 1186 992 1910 +f 2038 1120 468 +f 2038 511 1120 +f 1091 468 1088 +f 2038 2037 511 +f 1088 468 2178 +f 1093 1092 1840 +f 2286 2287 215 +f 2286 1657 2287 +f 1622 1165 1166 +f 1622 1621 2288 +f 1621 2289 2288 +f 1620 1166 45 +f 1180 2138 1175 +f 1180 1947 2138 +f 518 514 519 +f 1299 520 1302 +f 1560 1600 624 +f 1131 1600 1129 +f 1129 1600 1124 +f 1126 1121 1127 +f 2290 2197 772 +f 2290 2015 2197 +f 1196 1112 1193 +f 1113 1197 1195 +f 1253 1724 1696 +f 1253 1866 1251 +f 1251 1866 1598 +f 1252 1724 1253 +f 1253 1059 1061 +f 997 999 2095 +f 1292 1293 1296 +f 1156 1155 1895 +f 778 1351 2291 +f 778 781 2292 +f 24 21 20 +f 24 1435 1457 +f 744 1118 1117 +f 1118 746 1409 +f 61 1308 59 +f 1307 2293 800 +f 61 60 1309 +f 1308 981 978 +f 2076 836 75 +f 833 1423 2294 +f 482 485 2295 +f 486 481 868 +f 815 1625 1388 +f 818 820 1163 +f 818 1163 1625 +f 1161 1162 37 +f 820 818 817 +f 1927 37 36 +f 17 14 13 +f 17 715 2296 +f 1954 1955 218 +f 250 254 2295 +f 1954 220 1860 +f 1955 256 218 +f 2297 2073 913 +f 2297 2003 2190 +f 435 1883 1882 +f 435 434 2268 +f 943 586 944 +f 830 587 521 +f 2053 1717 2054 +f 2053 1169 903 +f 1906 1908 2298 +f 1905 1907 2299 +f 2300 2298 2167 +f 1906 1101 1907 +f 2300 2167 1101 +f 2298 1908 2301 +f 1669 1509 1508 +f 1809 1668 1810 +f 1491 1493 2302 +f 1492 1807 1493 +f 748 2303 749 +f 748 2139 347 +f 1903 2303 1881 +f 1903 1901 749 +f 1903 1881 1880 +f 747 749 750 +f 1964 1732 2128 +f 1964 1963 1730 +f 2304 2299 1907 +f 2304 508 2217 +f 273 2305 2173 +f 274 1907 1101 +f 273 275 2305 +f 1905 2299 2217 +f 2304 1959 508 +f 2304 1907 1959 +f 244 247 144 +f 245 916 915 +f 243 915 918 +f 916 879 2102 +f 387 1888 383 +f 387 2306 2307 +f 1539 1862 1516 +f 2243 2244 1915 +f 1859 1863 1954 +f 1862 1540 1863 +f 1194 157 156 +f 1831 1197 2158 +f 157 154 153 +f 501 1195 1830 +f 2308 2309 1884 +f 2308 530 529 +f 1743 726 2275 +f 1743 2213 1744 +f 1946 1949 191 +f 1945 1947 1180 +f 2310 1996 2311 +f 2310 2312 1700 +f 1659 414 1452 +f 1659 2088 2281 +f 414 1068 1452 +f 412 690 413 +f 2018 1593 259 +f 2024 2136 260 +f 257 2024 260 +f 2024 2023 2136 +f 1355 382 379 +f 1355 2033 1706 +f 2257 2313 649 +f 2256 1040 1039 +f 1881 350 349 +f 1903 1880 1267 +f 2314 2315 2316 +f 2314 1629 2317 +f 2315 2317 771 +f 2315 792 2316 +f 2318 329 2319 +f 2318 2320 329 +f 1569 1641 1570 +f 1640 2250 425 +f 1721 2316 396 +f 1721 597 345 +f 603 605 1799 +f 1874 1434 1799 +f 2321 2322 1127 +f 2321 2312 2323 +f 1517 1519 2183 +f 1514 972 1519 +f 1929 1782 62 +f 1780 204 1781 +f 1782 461 1309 +f 1779 1783 1780 +f 604 985 1669 +f 603 1799 1798 +f 871 2324 1661 +f 869 813 870 +f 1922 2170 2169 +f 2325 1921 260 +f 2227 797 1103 +f 953 956 1801 +f 794 1324 793 +f 1325 1327 1329 +f 594 395 595 +f 1721 345 880 +f 1844 2319 2326 +f 1842 1845 1306 +f 1547 855 2099 +f 905 445 183 +f 97 93 92 +f 97 402 2327 +f 1940 95 2328 +f 93 1741 96 +f 1910 1188 1186 +f 1910 372 1367 +f 2062 2063 1804 +f 324 322 321 +f 1288 319 409 +f 1802 1340 320 +f 957 961 958 +f 960 2329 2292 +f 1511 2330 971 +f 1511 1509 1813 +f 1436 1437 1440 +f 32 27 2331 +f 1865 1442 2278 +f 1438 1436 1440 +f 1023 1025 1083 +f 454 456 457 +f 1080 1545 1796 +f 1083 1085 1023 +f 1023 1085 456 +f 1025 1545 1083 +f 1796 1545 1025 +f 1079 1081 1546 +f 212 216 208 +f 1797 1796 1035 +f 1797 1035 2241 +f 1082 1546 1084 +f 390 389 2332 +f 390 2115 388 +f 2329 1502 2333 +f 960 2292 961 +f 88 1327 794 +f 88 225 1329 +f 793 1324 1326 +f 1326 1325 1330 +f 2152 1014 1016 +f 2153 976 1015 +f 1013 1015 2169 +f 1930 2155 1928 +f 2334 1207 2335 +f 2334 1213 1207 +f 1864 283 728 +f 284 1266 2242 +f 299 302 2138 +f 298 295 1658 +f 862 864 2336 +f 863 866 1072 +f 1069 1072 1070 +f 866 868 1143 +f 1465 1464 439 +f 1465 859 889 +f 439 1464 403 +f 439 441 890 +f 857 890 860 +f 439 403 440 +f 438 440 2125 +f 403 402 1940 +f 438 2125 1291 +f 1940 402 97 +f 831 945 517 +f 944 2337 945 +f 1575 2337 562 +f 1575 2338 945 +f 2146 2027 2147 +f 2150 2339 681 +f 2109 873 2111 +f 1589 1733 1736 +f 824 828 1318 +f 824 249 827 +f 1057 1531 635 +f 1057 1056 1773 +f 1424 1427 232 +f 1426 615 2340 +f 2341 2170 1681 +f 2341 1016 1013 +f 1681 2171 1682 +f 1923 2169 1015 +f 1601 1603 1604 +f 1602 1527 2234 +f 721 791 790 +f 934 593 394 +f 874 2342 2343 +f 874 873 1735 +f 874 2343 879 +f 2342 1735 2247 +f 1419 2344 2345 +f 1419 1418 132 +f 2346 2072 983 +f 2346 1728 1166 +f 2146 2148 2151 +f 2147 2173 1237 +f 144 247 145 +f 244 878 245 +f 148 2347 151 +f 965 967 1536 +f 995 1614 2209 +f 994 996 2075 +f 1241 1787 807 +f 1188 1369 1189 +f 792 397 396 +f 792 2315 2011 +f 792 396 2316 +f 393 788 394 +f 1933 2158 2159 +f 1932 1931 499 +f 1933 2159 2326 +f 1843 1303 2162 +f 2348 703 775 +f 2348 2197 1396 +f 2237 9 8 +f 186 185 189 +f 995 994 1612 +f 2075 2055 1613 +f 1063 2349 1064 +f 1063 1068 414 +f 132 2344 1419 +f 132 2350 1603 +f 1417 2345 2234 +f 2344 132 1603 +f 2351 2352 2017 +f 2351 1567 1566 +f 1250 1248 1247 +f 1248 1942 708 +f 1187 987 992 +f 1247 989 2028 +f 1187 992 1186 +f 991 829 992 +f 2006 1430 2004 +f 2006 2353 1430 +f 2323 2310 1983 +f 2323 2312 2310 +f 40 1723 1722 +f 39 38 42 +f 1559 918 915 +f 1559 1558 918 +f 808 1719 1718 +f 1719 1791 1157 +f 1791 171 1157 +f 1791 41 1001 +f 315 2327 402 +f 315 317 1739 +f 316 402 1287 +f 314 1286 317 +f 191 193 1946 +f 190 192 194 +f 1205 1204 1201 +f 1205 2354 1679 +f 718 791 721 +f 788 1720 394 +f 2176 1193 1937 +f 1190 1192 2001 +f 2216 2355 1908 +f 2216 1839 2355 +f 330 2281 2088 +f 1951 161 1638 +f 459 461 1538 +f 460 110 109 +f 460 109 1309 +f 110 2354 111 +f 2148 2252 2151 +f 2172 2251 2252 +f 1235 2173 2305 +f 1236 2172 1237 +f 113 1682 2135 +f 1682 2171 2356 +f 113 2135 1680 +f 1680 2136 2061 +f 551 668 552 +f 669 711 671 +f 1289 1136 1472 +f 490 493 982 +f 2150 1957 2027 +f 1958 681 512 +f 1956 1958 504 +f 1958 1957 2150 +f 1954 1863 255 +f 1861 2357 1516 +f 2358 2359 1751 +f 2358 1181 1176 +f 1751 2359 1961 +f 1751 192 2360 +f 1751 1961 2361 +f 2359 2358 1176 +f 930 2116 931 +f 1675 1678 1676 +f 1455 1484 1456 +f 58 53 51 +f 644 1456 2214 +f 1483 1485 700 +f 201 202 331 +f 1392 1532 1393 +f 360 1872 101 +f 359 2074 361 +f 1871 1586 1872 +f 1871 1856 161 +f 1870 1872 362 +f 190 2089 193 +f 1868 362 2190 +f 360 101 98 +f 2191 2278 2277 +f 2191 2177 29 +f 1165 2362 1145 +f 1622 1166 1620 +f 1165 1145 1148 +f 2362 2363 2335 +f 927 1430 1821 +f 2004 1887 1823 +f 2364 2002 913 +f 2364 141 2002 +f 2365 1090 1893 +f 2194 1332 1330 +f 2366 2031 525 +f 2030 1943 1249 +f 2031 1312 2367 +f 2031 1315 1312 +f 2031 2367 525 +f 1314 805 1718 +f 912 2107 887 +f 907 909 1443 +f 1795 1794 2007 +f 1795 1664 1666 +f 2368 118 136 +f 2368 135 1231 +f 1071 1070 2143 +f 862 2369 865 +f 864 1976 2336 +f 1070 1074 2143 +f 840 2127 742 +f 838 726 1742 +f 979 1279 1394 +f 979 1015 976 +f 1008 1012 1009 +f 1138 1142 130 +f 1012 130 129 +f 1137 1011 1139 +f 707 712 671 +f 710 124 313 +f 210 2317 211 +f 2315 1523 2011 +f 811 813 871 +f 811 2339 2144 +f 2370 1405 2218 +f 2370 1406 1405 +f 1136 1290 1686 +f 1473 1472 1474 +f 1132 1686 1133 +f 1290 492 490 +f 2371 352 354 +f 2371 961 2372 +f 1653 1567 3 +f 1653 1568 1567 +f 1031 2373 479 +f 1030 734 1032 +f 1622 2288 1165 +f 2289 2374 2288 +f 867 486 868 +f 870 813 1503 +f 866 1074 1072 +f 484 486 870 +f 866 1143 1074 +f 484 531 528 +f 2258 1038 1037 +f 2258 1371 1038 +f 1878 1583 1582 +f 2186 2185 1749 +f 1772 1770 1769 +f 1770 1773 1056 +f 1174 1179 1177 +f 1178 2360 1949 +f 1071 1010 1073 +f 2143 1074 1141 +f 2336 2375 2369 +f 2336 1608 1605 +f 836 835 76 +f 836 2076 833 +f 835 1752 568 +f 835 832 1752 +f 2039 380 377 +f 2039 2376 381 +f 1630 1479 1633 +f 1632 2034 2377 +f 1200 1203 1201 +f 1202 2293 108 +f 821 828 829 +f 821 553 1318 +f 821 829 672 +f 822 826 823 +f 2233 2378 2330 +f 2233 2379 2234 +f 2233 2330 2380 +f 2378 2381 2382 +f 1030 2373 1031 +f 1032 731 2383 +f 723 1185 724 +f 2044 2046 1626 +f 479 478 1382 +f 477 1889 480 +f 752 755 2184 +f 754 1474 755 +f 754 753 1729 +f 755 1583 2184 +f 2384 2385 2386 +f 2384 2218 1282 +f 1280 308 1056 +f 2218 2384 2386 +f 1737 568 1597 +f 1737 1617 2387 +f 2080 2079 1117 +f 2077 45 47 +f 1644 1493 956 +f 2302 1646 1857 +f 1493 1807 1801 +f 1491 2302 1494 +f 1354 379 602 +f 382 405 323 +f 2188 1305 2388 +f 2188 2163 2162 +f 2389 1158 685 +f 2389 1160 1158 +f 278 281 941 +f 685 684 2389 +f 118 2390 116 +f 2368 136 133 +f 2284 352 2372 +f 2283 2285 1284 +f 351 353 1978 +f 2284 780 2285 +f 1626 2240 2047 +f 1020 2391 2240 +f 1626 2047 2044 +f 2240 2391 2211 +f 2392 2042 2393 +f 2392 1937 2271 +f 2394 2210 1246 +f 2394 2395 1939 +f 1246 807 1787 +f 43 2210 1245 +f 1611 2394 1939 +f 2394 1365 167 +f 928 1853 1678 +f 666 2249 2396 +f 2200 1062 1064 +f 1063 1062 1067 +f 2132 1968 2134 +f 2132 539 543 +f 2397 1117 1114 +f 2397 2081 2080 +f 2176 2174 2204 +f 1935 2393 2398 +f 314 316 1286 +f 315 402 316 +f 155 154 500 +f 155 1168 936 +f 1685 2049 1683 +f 925 2102 2343 +f 2379 2380 1811 +f 2230 2234 2231 +f 151 2347 968 +f 147 2203 150 +f 2203 572 150 +f 2200 2199 2202 +f 1537 138 142 +f 1868 1536 1870 +f 142 1784 1538 +f 141 140 1391 +f 494 496 137 +f 496 495 1407 +f 2299 2304 2217 +f 1959 2026 1957 +f 1459 21 24 +f 1459 1793 21 +f 1800 1801 357 +f 954 795 797 +f 1397 1396 2196 +f 1397 1395 704 +f 1766 1344 122 +f 1763 527 1764 +f 1734 1733 1589 +f 1675 2116 930 +f 2110 1735 873 +f 1736 2399 1875 +f 1969 1968 543 +f 1969 2093 1966 +f 1968 566 2134 +f 1966 2400 1967 +f 2131 2134 2133 +f 1712 1965 1967 +f 1529 2401 1526 +f 2382 2330 2378 +f 328 2402 329 +f 328 264 2402 +f 563 2057 566 +f 2019 2023 2024 +f 935 156 938 +f 2209 1111 156 +f 1544 1028 1027 +f 1544 83 1542 +f 83 1660 84 +f 1759 1029 2264 +f 1026 1028 1543 +f 1660 49 2225 +f 271 443 1333 +f 266 268 1053 +f 1333 444 1334 +f 442 272 1488 +f 2239 2265 1029 +f 2239 776 2265 +f 2264 2263 1760 +f 2239 2403 773 +f 1466 1468 1402 +f 1984 1472 1136 +f 7 10 5 +f 1987 1467 1985 +f 1641 425 1849 +f 349 1570 1881 +f 645 1271 647 +f 1271 1273 647 +f 321 1342 2039 +f 410 408 1876 +f 410 1876 1286 +f 407 404 324 +f 515 519 516 +f 513 1939 2395 +f 2325 2171 2170 +f 2325 260 2356 +f 273 2173 276 +f 2305 275 1238 +f 1476 1475 1728 +f 1470 493 1289 +f 453 2163 2188 +f 453 452 2320 +f 327 2388 2070 +f 2188 2162 1305 +f 9 2404 544 +f 187 9 2237 +f 2404 1425 544 +f 2404 187 588 +f 619 1239 434 +f 618 620 705 +f 619 434 433 +f 1239 476 2269 +f 1076 714 2280 +f 1077 2296 715 +f 1707 2166 1918 +f 1707 885 2166 +f 951 4 1 +f 951 1450 312 +f 108 111 1203 +f 107 109 110 +f 108 1203 1202 +f 111 108 107 +f 916 2102 917 +f 879 2343 2102 +f 1087 1086 1092 +f 1089 467 1413 +f 2405 2375 1607 +f 2405 1663 2375 +f 498 500 154 +f 1933 1932 2158 +f 501 154 157 +f 500 2326 155 +f 1461 1353 1352 +f 1461 1376 1762 +f 70 1462 66 +f 1501 1352 1502 +f 2406 2182 1516 +f 2406 2007 1794 +f 1513 2182 1794 +f 1514 973 972 +f 2406 1516 2357 +f 2182 1513 1512 +f 91 2407 94 +f 96 1740 2407 +f 1256 1162 820 +f 1255 1257 1264 +f 105 1828 1822 +f 103 1331 1893 +f 88 949 86 +f 1886 1893 1413 +f 2408 2409 2072 +f 2408 1549 883 +f 1284 2410 1978 +f 1283 424 427 +f 1848 424 1284 +f 1847 1270 1641 +f 415 1848 1849 +f 418 415 1849 +f 517 667 520 +f 517 945 2411 +f 517 2411 2412 +f 944 586 1674 +f 2411 2338 2413 +f 943 945 831 +f 574 1730 575 +f 1732 2414 2128 +f 1732 1730 436 +f 1963 2126 2213 +f 1121 1123 1127 +f 1561 2415 1123 +f 2322 2096 1127 +f 2322 1154 2096 +f 1106 444 442 +f 1107 1099 1108 +f 2294 1420 2379 +f 2294 1423 1420 +f 2416 2408 883 +f 2416 2409 2408 +f 576 620 617 +f 700 705 620 +f 306 308 1215 +f 1772 1771 2206 +f 2256 1825 2313 +f 1826 1877 1824 +f 1610 1060 1059 +f 1611 1245 2210 +f 1604 1603 2350 +f 1601 846 1602 +f 1419 2345 1417 +f 2345 1603 1602 +f 1602 846 845 +f 1604 2350 1950 +f 478 1888 1383 +f 383 914 386 +f 914 383 385 +f 387 386 1774 +f 2160 696 1547 +f 1988 1990 2417 +f 1504 1505 2309 +f 1503 531 870 +f 582 584 580 +f 764 2261 765 +f 900 899 2052 +f 896 898 1971 +f 926 923 2342 +f 926 1591 924 +f 81 2418 2086 +f 79 82 1592 +f 2086 2418 2391 +f 1542 457 2085 +f 2086 2391 458 +f 78 1183 81 +f 852 856 853 +f 692 696 693 +f 2245 1095 1443 +f 1920 855 1548 +f 1920 856 855 +f 2245 1443 909 +f 2345 1602 2234 +f 846 843 842 +f 1527 2231 2234 +f 1527 845 1528 +f 2231 1527 1524 +f 1417 2234 1420 +f 277 687 280 +f 1107 444 1106 +f 2187 526 525 +f 1952 1296 581 +f 523 527 524 +f 2187 2367 1298 +f 2274 2273 2272 +f 2273 1498 366 +f 2256 2313 2257 +f 1824 1877 1233 +f 392 389 388 +f 2273 366 365 +f 252 2268 784 +f 252 251 1883 +f 1116 2385 1554 +f 1116 2386 2385 +f 1594 1597 1595 +f 568 76 835 +f 568 1752 1597 +f 2076 75 1767 +f 2387 1617 1616 +f 2387 568 1737 +f 567 1616 1924 +f 241 632 242 +f 1777 386 914 +f 1778 2073 100 +f 478 1383 1380 +f 1888 384 383 +f 1888 387 2307 +f 387 1776 2419 +f 1553 2352 2374 +f 1551 1554 1552 +f 1356 602 599 +f 1355 1706 382 +f 1354 1356 2033 +f 2103 1356 2067 +f 105 1887 1885 +f 1823 2005 2004 +f 1369 1188 1367 +f 1189 1580 1445 +f 1946 193 2088 +f 1949 1179 1178 +f 191 2360 192 +f 1946 2088 1659 +f 1403 1215 1405 +f 497 494 1216 +f 963 964 2383 +f 962 733 1679 +f 168 172 169 +f 997 1001 998 +f 1440 1437 376 +f 1865 2278 2191 +f 2277 16 19 +f 32 28 25 +f 1381 1385 1382 +f 1384 1383 2307 +f 1384 2307 2215 +f 480 1889 2082 +f 363 365 366 +f 364 2376 1342 +f 121 524 1766 +f 1199 708 1944 +f 1891 1323 1316 +f 1891 1890 1449 +f 1891 1319 2208 +f 1891 1316 1319 +f 1890 2208 712 +f 1319 1318 553 +f 298 300 299 +f 303 1278 305 +f 2249 667 2412 +f 2249 666 665 +f 1978 2410 1285 +f 1978 354 351 +f 2114 920 922 +f 2114 2332 2274 +f 1358 1257 1832 +f 1360 115 112 +f 728 283 2142 +f 284 451 1266 +f 1411 1546 737 +f 1347 738 2279 +f 884 2166 885 +f 887 2107 888 +f 2165 2246 1918 +f 2108 2165 2104 +f 1709 1918 1500 +f 887 1708 2106 +f 483 1143 481 +f 1138 130 1012 +f 55 2224 56 +f 1455 58 1484 +f 428 430 431 +f 429 310 1496 +f 1573 1846 1591 +f 1576 1571 562 +f 27 2124 2331 +f 30 26 25 +f 1949 2360 191 +f 750 1175 751 +f 1553 2374 2420 +f 2352 1213 2334 +f 1474 1472 1986 +f 1473 1475 1471 +f 1362 1364 758 +f 1790 1557 1747 +f 560 2048 1684 +f 1785 1913 1453 +f 2127 94 2407 +f 1744 839 1742 +f 1756 1348 2280 +f 1816 457 456 +f 2216 1908 2217 +f 2355 1160 1908 +f 151 968 2122 +f 152 150 1854 +f 151 2122 149 +f 2121 1534 2124 +f 69 1219 1378 +f 69 68 73 +f 1217 73 72 +f 1218 1222 1993 +f 1217 72 947 +f 1219 1701 1378 +f 2156 198 2155 +f 2156 2421 199 +f 1319 670 2208 +f 668 672 669 +f 1025 1023 1022 +f 1085 1083 1082 +f 1025 1024 1034 +f 455 458 1020 +f 1054 1281 1055 +f 1550 2017 1553 +f 2287 1657 1244 +f 2286 215 1568 +f 871 1662 811 +f 2324 2375 1663 +f 509 508 507 +f 510 2217 508 +f 2280 549 1076 +f 2280 1348 1078 +f 657 2422 714 +f 657 1173 1172 +f 717 657 713 +f 2280 2087 1756 +f 1751 2361 1750 +f 1961 1776 2361 +f 1524 1526 2232 +f 969 973 970 +f 1616 631 1926 +f 1615 1617 632 +f 1595 1752 1670 +f 568 2387 567 +f 1592 80 79 +f 1184 724 1185 +f 233 232 1427 +f 234 1688 1585 +f 123 122 950 +f 123 4 124 +f 2 4 123 +f 3 1567 2016 +f 120 124 709 +f 951 2016 1450 +f 2419 2306 387 +f 2419 1902 2215 +f 1064 2349 688 +f 1062 2200 2202 +f 1359 115 1360 +f 1682 2356 2135 +f 1922 2325 2170 +f 1360 2137 1257 +f 1621 2081 2289 +f 2080 1117 2397 +f 1305 1648 851 +f 1648 2423 2001 +f 2061 2136 2023 +f 2270 1261 1264 +f 295 304 296 +f 302 348 2138 +f 745 293 2424 +f 2266 1117 2079 +f 290 2267 291 +f 743 2424 746 +f 1627 1629 882 +f 1628 211 2317 +f 534 259 258 +f 537 536 2132 +f 543 539 541 +f 543 1968 2132 +f 2131 2132 2134 +f 1966 2093 2400 +f 1598 1130 2425 +f 1600 1867 624 +f 1997 1225 2311 +f 1997 1705 1658 +f 1094 905 904 +f 693 696 1989 +f 1097 904 906 +f 180 179 2069 +f 800 981 1307 +f 799 801 980 +f 2041 199 2421 +f 2041 816 2040 +f 1864 728 449 +f 283 286 2142 +f 361 2297 2190 +f 361 2074 2073 +f 2002 2003 913 +f 2190 1869 1868 +f 359 362 360 +f 459 964 462 +f 2152 2155 1930 +f 198 204 1928 +f 667 931 520 +f 2249 2412 2396 +f 1684 1577 561 +f 2050 924 1846 +f 248 1789 1453 +f 248 918 1558 +f 143 145 628 +f 246 918 248 +f 248 1558 1789 +f 916 245 879 +f 622 1867 1599 +f 621 1061 624 +f 623 1866 1253 +f 1131 1599 1867 +f 843 1950 2207 +f 1602 845 1527 +f 2254 1036 1041 +f 1040 2013 2426 +f 1901 1904 1176 +f 1899 2427 1269 +f 2195 1329 225 +f 1850 2192 1838 +f 694 675 674 +f 1989 696 2160 +f 1661 1662 871 +f 677 679 506 +f 513 2395 167 +f 2394 1611 2210 +f 2425 1254 1251 +f 2425 1130 2096 +f 57 1761 1873 +f 49 1660 50 +f 701 1760 2262 +f 49 54 2225 +f 2201 170 2199 +f 1155 31 1895 +f 1890 712 1446 +f 2208 670 671 +f 2183 2253 1915 +f 2230 2232 2381 +f 2183 2244 1518 +f 2253 845 842 +f 915 917 1559 +f 2049 925 2050 +f 2028 989 986 +f 1250 1943 1942 +f 2428 2353 2006 +f 2428 2226 1431 +f 2263 776 702 +f 2264 1760 1759 +f 287 292 290 +f 755 1584 1583 +f 2185 1583 1878 +f 288 294 756 +f 643 2224 55 +f 2090 2275 726 +f 2275 2214 2213 +f 722 726 723 +f 511 2037 510 +f 511 680 1120 +f 509 511 510 +f 1091 2038 468 +f 649 1909 2257 +f 653 1673 654 +f 2292 1351 778 +f 2292 2333 1351 +f 2295 254 1914 +f 2295 485 251 +f 530 1884 251 +f 2308 2429 2309 +f 153 155 938 +f 155 2326 1168 +f 854 1708 2099 +f 854 909 2106 +f 907 1443 910 +f 855 854 2099 +f 2330 1812 2380 +f 1511 971 1510 +f 296 1068 1067 +f 304 299 1947 +f 2349 414 411 +f 1659 304 1947 +f 1063 414 2349 +f 690 2281 691 +f 1659 2281 690 +f 2088 2089 1587 +f 1948 1945 1180 +f 413 691 688 +f 1610 1723 40 +f 623 1061 621 +f 1058 1060 1302 +f 40 1001 41 +f 629 1926 631 +f 630 1481 1926 +f 632 631 1615 +f 1926 1925 1924 +f 238 630 241 +f 1480 1043 1265 +f 74 77 35 +f 1926 1836 1925 +f 2414 2430 1151 +f 2414 1731 1882 +f 925 2343 923 +f 879 245 877 +f 393 397 788 +f 2314 2316 882 +f 2413 1575 1852 +f 2413 2338 1575 +f 1998 1500 1918 +f 1497 366 1498 +f 1811 2294 2379 +f 2294 834 833 +f 936 938 155 +f 935 937 1898 +f 1184 80 1592 +f 1182 2047 2211 +f 1592 84 2189 +f 2418 2211 2391 +f 1789 1558 1555 +f 248 370 249 +f 873 876 2111 +f 874 1735 2342 +f 876 310 429 +f 875 878 146 +f 167 2395 2394 +f 167 1562 163 +f 2097 2066 1134 +f 2064 592 614 +f 131 1950 2350 +f 127 130 2207 +f 1843 2164 2318 +f 2161 2162 2163 +f 229 1424 232 +f 589 186 1426 +f 1425 1424 544 +f 1427 2340 233 +f 2392 2393 1935 +f 1975 1973 1972 +f 1631 1630 1634 +f 1479 1693 1477 +f 2313 1232 1234 +f 1826 2255 1878 +f 649 2313 650 +f 2257 2013 1040 +f 2431 1238 275 +f 2431 2229 2251 +f 2034 1711 2400 +f 2034 1632 1631 +f 1572 1846 1573 +f 1684 561 560 +f 557 561 1577 +f 559 758 1364 +f 757 562 2337 +f 1747 2048 1790 +f 2167 684 682 +f 1906 2300 1101 +f 2095 999 1254 +f 1002 172 171 +f 2319 329 2402 +f 1844 2326 1845 +f 717 716 376 +f 548 1076 549 +f 713 715 1441 +f 1078 1348 2279 +f 1526 2401 2232 +f 2401 1529 2382 +f 2417 769 676 +f 1990 2099 769 +f 797 796 640 +f 952 954 1488 +f 1578 1580 1189 +f 1579 1445 1580 +f 1704 1994 2250 +f 1992 1705 1997 +f 1782 60 62 +f 459 1784 2364 +f 1930 62 976 +f 1307 109 108 +f 673 675 1988 +f 674 1522 1521 +f 692 697 445 +f 674 676 768 +f 118 117 2340 +f 1231 118 2368 +f 1244 1243 1399 +f 2260 1657 1654 +f 2430 1565 1563 +f 2430 1884 2309 +f 1563 1564 2430 +f 2144 2339 2150 +f 2309 2429 1504 +f 2308 1884 530 +f 812 1565 1505 +f 861 1982 356 +f 810 1505 813 +f 2414 1151 1149 +f 1934 2175 1935 +f 1357 129 128 +f 2042 2392 2055 +f 2393 2432 2398 +f 1935 1937 2392 +f 2175 1422 2204 +f 2271 1193 1112 +f 2176 2204 1191 +f 2093 2433 2400 +f 2093 1815 2433 +f 1171 1665 773 +f 1048 1051 1049 +f 1478 1633 1479 +f 261 263 264 +f 1631 1634 1710 +f 1477 1694 263 +f 1378 71 69 +f 64 71 65 +f 2307 2306 2215 +f 479 1382 1031 +f 882 1629 2314 +f 882 881 1214 +f 609 2069 179 +f 2317 1629 1628 +f 882 2316 880 +f 771 2317 210 +f 2290 2010 2008 +f 2348 1396 703 +f 699 1396 704 +f 2348 775 2197 +f 703 702 776 +f 1397 2196 1953 +f 1005 1716 1645 +f 1857 1494 2302 +f 223 225 87 +f 224 942 2193 +f 153 938 156 +f 1168 2219 2434 +f 2222 1371 2258 +f 1118 2014 1115 +f 336 450 334 +f 814 816 817 +f 2207 1950 127 +f 1916 1338 1914 +f 842 844 2253 +f 2207 130 1142 +f 1766 524 527 +f 121 120 709 +f 815 1388 1387 +f 1927 1163 37 +f 1161 37 1163 +f 1263 1259 1260 +f 1256 819 1832 +f 1924 36 77 +f 2116 431 931 +f 432 2111 429 +f 688 2435 1064 +f 688 691 570 +f 90 1821 1820 +f 89 1885 1887 +f 376 716 1440 +f 376 2168 1173 +f 494 137 1216 +f 137 136 1216 +f 2082 1033 2383 +f 2082 1889 1033 +f 143 628 2206 +f 145 249 824 +f 2365 1893 1331 +f 1089 2178 469 +f 255 1863 254 +f 1860 2015 1861 +f 497 1215 495 +f 306 310 307 +f 1837 1841 1838 +f 1840 2037 1093 +f 2193 2195 224 +f 1328 1332 2365 +f 1850 1841 2194 +f 2193 942 1159 +f 88 1329 1327 +f 2365 2194 2198 +f 740 742 2127 +f 739 741 2046 +f 116 2390 1231 +f 118 2340 136 +f 891 958 1980 +f 1461 65 1379 +f 2416 343 2084 +f 883 881 880 +f 1846 1577 1683 +f 1577 1576 557 +f 923 2343 2342 +f 2102 2129 917 +f 9 187 2404 +f 2238 615 1426 +f 1981 860 1564 +f 1464 1463 403 +f 1313 1315 1445 +f 1312 1311 1314 +f 1240 1579 1578 +f 1310 805 1311 +f 1487 1486 576 +f 1483 1456 1484 +f 476 618 705 +f 1239 2269 434 +f 575 1487 573 +f 476 705 473 +f 1361 2421 2156 +f 1359 1016 2341 +f 1717 996 993 +f 1717 902 1973 +f 2075 996 1973 +f 1612 1613 1614 +f 1973 996 1717 +f 2042 1974 2393 +f 1198 2055 1112 +f 1972 902 1971 +f 1287 410 316 +f 646 408 407 +f 2070 2388 851 +f 2070 2035 1634 +f 404 408 406 +f 2063 1288 1808 +f 1915 254 1863 +f 1914 1338 482 +f 1169 1896 1167 +f 2209 1614 1111 +f 1110 1113 1195 +f 1196 1193 1190 +f 1111 1614 1613 +f 1194 1110 1195 +f 1897 2209 1898 +f 2271 2055 2392 +f 932 591 593 +f 719 770 591 +f 1399 1243 1400 +f 1401 2036 1244 +f 112 2137 1360 +f 1260 1192 1262 +f 1941 1943 2030 +f 1248 708 711 +f 447 450 448 +f 334 333 336 +f 2130 701 1485 +f 2239 1029 659 +f 2038 1091 1093 +f 1088 2178 1089 +f 712 2208 671 +f 1321 1448 1322 +f 762 1691 763 +f 765 2261 1655 +f 764 763 584 +f 1691 1952 585 +f 2187 525 2367 +f 522 524 1199 +f 2398 2432 1976 +f 2398 1073 1935 +f 2337 1575 945 +f 1575 562 1571 +f 2337 759 757 +f 517 2412 667 +f 2313 1234 650 +f 119 1230 117 +f 1234 1229 119 +f 1233 1877 1585 +f 1804 2063 1808 +f 318 1340 1339 +f 567 2387 1616 +f 1617 1737 632 +f 802 975 981 +f 974 1279 979 +f 1889 2373 1033 +f 385 2082 913 +f 2082 2383 964 +f 1033 2373 1030 +f 1882 1884 2430 +f 1883 2268 252 +f 2143 1139 1010 +f 867 865 869 +f 2362 2335 1207 +f 2363 2352 2334 +f 1366 372 1912 +f 1910 992 825 +f 1366 1912 1368 +f 372 825 827 +f 1243 583 1400 +f 584 763 585 +f 1242 583 1243 +f 660 2177 2191 +f 785 1400 583 +f 1398 737 1401 +f 660 579 578 +f 1894 1919 1293 +f 2105 910 2033 +f 908 2106 909 +f 2181 1283 427 +f 1284 2285 1848 +f 1044 784 782 +f 1044 1046 784 +f 1871 161 158 +f 965 1536 1535 +f 156 935 1898 +f 903 1169 899 +f 374 22 375 +f 22 2331 20 +f 1048 375 1792 +f 22 1437 2331 +f 2319 2402 2326 +f 453 2320 2164 +f 709 124 710 +f 1343 1344 1655 +f 891 1980 1977 +f 957 1502 2329 +f 1524 2232 2231 +f 2382 971 2330 +f 368 827 369 +f 1189 1581 1578 +f 1453 370 248 +f 826 825 829 +f 570 2435 688 +f 1064 572 2200 +f 2347 148 152 +f 688 2349 689 +f 297 1067 1228 +f 1065 2202 1153 +f 411 689 2349 +f 1067 1066 1228 +f 1128 1129 1126 +f 1131 1130 1598 +f 1606 1608 1970 +f 1606 2221 1607 +f 791 720 1720 +f 590 592 595 +f 98 2074 359 +f 1775 2361 1776 +f 327 326 2188 +f 329 452 325 +f 1751 2360 2358 +f 190 194 2089 +f 195 1745 1586 +f 1777 1775 1774 +f 375 21 1793 +f 1049 1051 1173 +f 1792 1793 1794 +f 24 23 2242 +f 1102 640 2229 +f 954 2227 2228 +f 1098 1100 1104 +f 638 796 641 +f 1211 1212 1627 +f 1210 1568 211 +f 279 1624 1052 +f 940 226 1624 +f 1900 1904 1901 +f 2419 2215 2306 +f 1961 1904 1902 +f 1176 1181 750 +f 1166 1164 2346 +f 2081 1621 1623 +f 492 1136 1289 +f 1834 1134 2098 +f 1134 596 2097 +f 1133 1686 1687 +f 987 986 990 +f 1445 1315 2028 +f 1444 991 711 +f 988 1186 1189 +f 806 1313 1240 +f 1444 711 669 +f 2366 2030 2031 +f 1240 1581 1241 +f 676 769 766 +f 673 2417 676 +f 1835 2098 2065 +f 1984 1833 1987 +f 2280 2422 2087 +f 713 1441 716 +f 2410 1284 1285 +f 1849 1848 1847 +f 1754 1285 1755 +f 427 424 417 +f 285 23 20 +f 728 2142 2071 +f 595 593 590 +f 594 1687 597 +f 364 601 2376 +f 365 921 2273 +f 1983 1154 2323 +f 1152 172 1002 +f 2379 1420 2234 +f 1668 1809 1813 +f 216 2241 741 +f 1619 215 2287 +f 1818 1821 1819 +f 86 949 89 +f 1521 697 674 +f 180 2069 181 +f 81 2086 1542 +f 1542 458 457 +f 257 1593 2024 +f 2021 2020 2133 +f 2018 259 2020 +f 541 540 542 +f 2222 2426 654 +f 1039 2254 2255 +f 1664 1795 774 +f 1792 1050 1048 +f 2008 2009 2290 +f 2406 1794 2182 +f 2009 2357 1861 +f 774 2008 2010 +f 2222 2258 2426 +f 1370 746 2424 +f 388 2115 391 +f 2113 2332 2114 +f 1300 1299 1609 +f 1302 1061 1058 +f 2085 457 1756 +f 50 1027 1029 +f 454 458 455 +f 81 1183 2418 +f 754 1475 1474 +f 44 294 291 +f 1042 1798 1434 +f 237 1043 238 +f 985 603 607 +f 1042 1433 1043 +f 1554 2385 1282 +f 1116 1553 2420 +f 1552 1281 634 +f 2218 2386 2370 +f 6 5 9 +f 11 1833 614 +f 722 2224 725 +f 56 2225 54 +f 681 1662 678 +f 1956 504 508 +f 1902 1900 1899 +f 1903 749 2303 +f 1317 2032 628 +f 1447 313 1450 +f 1530 1892 636 +f 1317 628 145 +f 144 146 878 +f 628 625 2206 +f 2095 1254 2096 +f 1696 998 1722 +f 1002 2095 1154 +f 2425 1251 1598 +f 347 2139 348 +f 350 1881 2303 +f 1879 1570 1270 +f 1903 2427 1899 +f 1267 2427 1903 +f 1269 1268 2215 +f 1776 1961 1902 +f 1269 2427 1267 +f 1086 1088 1089 +f 1090 1089 1893 +f 1651 2436 1650 +f 1651 1350 2436 +f 744 1117 2266 +f 1114 2420 2397 +f 1009 1012 129 +f 1141 1139 2143 +f 2305 1238 1235 +f 1098 1101 1099 +f 2011 789 792 +f 2179 2012 767 +f 2378 2233 2381 +f 1421 1423 1422 +f 2250 2025 426 +f 2250 1642 1704 +f 2157 1979 2025 +f 426 425 2250 +f 417 420 423 +f 1217 1979 1221 +f 1413 1893 1089 +f 1412 1414 1415 +f 2118 2120 2221 +f 2120 2219 2220 +f 1607 2375 1605 +f 1605 2375 2336 +f 2221 1606 2118 +f 2119 901 900 +f 880 2316 1721 +f 881 1146 1206 +f 293 292 2424 +f 287 294 288 +f 955 272 1647 +f 442 2228 1106 +f 2124 1637 2121 +f 32 2331 1437 +f 147 149 2203 +f 1635 1637 2123 +f 1486 1483 700 +f 642 644 2214 +f 1454 2214 2275 +f 48 52 54 +f 642 2214 1454 +f 2275 2213 1743 +f 1446 313 1447 +f 3 950 1653 +f 522 1199 1944 +f 1451 1450 637 +f 1119 1120 680 +f 469 467 1089 +f 774 2007 2008 +f 2007 2357 2009 +f 1306 1303 1843 +f 1304 1197 1196 +f 1714 565 850 +f 327 1634 265 +f 1404 1403 1406 +f 1496 1216 1618 +f 1405 1215 308 +f 1407 1404 1409 +f 1496 310 309 +f 429 428 432 +f 1215 309 306 +f 430 1618 431 +f 1225 1997 2276 +f 1997 2311 1996 +f 824 1318 145 +f 821 672 551 +f 723 840 742 +f 1744 2213 2126 +f 1013 2169 2341 +f 2325 1922 1921 +f 1878 1877 1826 +f 1582 1584 1467 +f 2313 1825 1232 +f 292 1372 1370 +f 102 1827 1828 +f 1415 1414 1823 +f 1822 1828 1416 +f 927 2004 1430 +f 106 1885 89 +f 105 1822 1887 +f 1408 2222 2223 +f 2426 2013 654 +f 1003 1005 365 +f 1644 1646 2302 +f 1864 284 283 +f 2242 1435 24 +f 1645 919 921 +f 267 272 271 +f 2016 1567 2351 +f 311 4 951 +f 474 1047 782 +f 2282 1953 221 +f 927 1821 90 +f 1430 2353 1428 +f 1819 1429 1624 +f 927 89 1887 +f 1158 686 685 +f 1159 1839 2193 +f 1710 1634 2035 +f 1632 2377 1693 +f 892 1462 959 +f 1377 71 1378 +f 1377 1379 71 +f 1762 1699 2437 +f 68 63 948 +f 958 961 2371 +f 1460 1501 1462 +f 2329 2333 2292 +f 1349 1353 1350 +f 1375 1701 1698 +f 1162 34 37 +f 2076 2204 1422 +f 1223 1226 1066 +f 1638 571 691 +f 303 1658 1705 +f 2276 1227 1225 +f 471 18 14 +f 547 546 2091 +f 736 547 738 +f 15 19 16 +f 982 491 490 +f 340 344 488 +f 32 1439 28 +f 1440 716 1441 +f 655 657 1172 +f 376 1437 374 +f 633 635 636 +f 1282 2385 2384 +f 634 633 637 +f 635 634 1057 +f 552 671 670 +f 706 708 709 +f 2274 2272 2114 +f 1645 1716 1646 +f 535 258 260 +f 2023 2060 2061 +f 603 985 604 +f 1809 1752 834 +f 1928 2155 198 +f 62 978 977 +f 1835 614 1833 +f 2064 596 595 +f 596 1133 594 +f 1834 2098 1835 +f 1373 1371 1370 +f 495 1404 1407 +f 1471 1475 1476 +f 1474 1584 755 +f 487 489 1290 +f 1476 1728 2346 +f 487 1290 490 +f 493 1476 2346 +f 786 1938 18 +f 1865 2191 28 +f 470 2296 1075 +f 1075 2296 1077 +f 2191 662 660 +f 1400 785 1398 +f 1905 2217 1908 +f 510 2037 1840 +f 2300 1906 2298 +f 1851 510 1840 +f 2056 1218 1993 +f 891 948 67 +f 1702 1996 1700 +f 1995 1992 1997 +f 1378 1701 1375 +f 1992 1991 1704 +f 2364 1784 141 +f 963 2383 731 +f 891 1977 1754 +f 1977 354 1978 +f 1431 2353 2428 +f 1428 268 1429 +f 2310 2311 1224 +f 1991 1993 1222 +f 2276 1997 1658 +f 2056 1702 1701 +f 1220 2056 1701 +f 1697 1127 1123 +f 1800 2252 641 +f 2147 2027 2026 +f 263 2402 264 +f 263 1694 2402 +f 1611 1609 1301 +f 167 1365 1362 +f 1363 1913 1785 +f 1241 807 806 +f 1128 1127 2096 +f 1123 1699 1697 +f 1940 2328 440 +f 95 92 91 +f 2297 913 2003 +f 1775 99 1750 +f 1585 1469 234 +f 1749 2185 2254 +f 231 234 235 +f 1986 1584 1474 +f 1148 1147 2408 +f 1145 2362 1207 +f 1206 1208 1214 +f 2334 2335 2363 +f 1549 2408 1147 +f 2409 2416 2083 +f 847 849 2060 +f 2001 2423 1190 +f 2259 2268 434 +f 782 1047 1044 +f 2096 1130 1128 +f 1252 1254 999 +f 1599 1866 622 +f 2321 1127 1700 +f 1709 1499 886 +f 389 1498 2274 +f 1345 1347 2279 +f 1084 1546 1411 +f 277 281 278 +f 940 939 224 +f 1285 1283 1755 +f 415 424 1848 +f 893 1755 2181 +f 419 418 425 +f 1974 2432 2393 +f 1971 902 896 +f 1231 135 1672 +f 1231 2390 118 +f 646 645 1876 +f 2117 209 741 +f 839 2128 1149 +f 1150 839 1149 +f 780 779 2285 +f 777 781 778 +f 1651 1649 2438 +f 1650 624 2285 +f 1650 2415 1561 +f 352 2284 353 +f 2327 1741 93 +f 1739 1740 96 +f 682 687 1108 +f 279 1052 1335 +f 119 2439 1234 +f 134 133 137 +f 2439 1672 1671 +f 2439 1231 1672 +f 2270 2137 114 +f 1361 1832 2421 +f 1557 1790 1788 +f 1790 2048 1364 +f 1976 2432 1974 +f 865 2369 871 +f 1976 1608 2336 +f 2043 2042 2055 +f 452 329 2320 +f 1843 2318 2180 +f 348 2139 2138 +f 346 2100 349 +f 751 2139 747 +f 1881 1570 1879 +f 1180 1175 1177 +f 2100 302 301 +f 381 2376 601 +f 2039 323 321 +f 906 910 1097 +f 2248 379 378 +f 1011 1138 1012 +f 1009 129 1357 +f 1465 889 1463 +f 1806 357 1801 +f 2125 2328 95 +f 403 401 400 +f 94 1291 95 +f 723 742 2045 +f 1465 439 890 +f 2127 2407 740 +f 398 401 399 +f 2407 91 96 +f 2328 2125 440 +f 441 1151 890 +f 410 1287 409 +f 2101 1982 2149 +f 262 261 265 +f 2320 2318 2164 +f 1679 1204 1205 +f 732 731 1032 +f 1766 122 121 +f 2 950 3 +f 214 2241 216 +f 1035 1626 2241 +f 533 540 536 +f 2152 1016 2154 +f 964 913 2082 +f 197 203 198 +f 2362 2288 2363 +f 756 46 1729 +f 2346 983 493 +f 1549 1146 881 +f 2411 945 2338 +f 514 1609 1939 +f 1236 1238 2431 +f 2173 2147 276 +f 2172 2431 2251 +f 2431 1104 2229 +f 2148 2172 2252 +f 1017 2229 640 +f 803 805 1313 +f 1312 1298 2367 +f 605 1874 1799 +f 1507 1511 1510 +f 617 437 576 +f 433 1731 436 +f 1351 2333 1349 +f 2292 781 2372 +f 2366 525 1944 +f 1297 2187 1295 +f 2030 2366 1941 +f 1766 1690 1344 +f 1549 1147 1144 +f 2416 2084 2083 +f 788 787 790 +f 1211 1214 1208 +f 1274 181 210 +f 382 323 377 +f 1169 1167 936 +f 1168 2326 2220 +f 2371 354 1980 +f 353 1284 1978 +f 958 2371 1980 +f 960 957 2329 +f 893 891 1754 +f 353 2284 2283 +f 6 544 228 +f 233 2340 1230 +f 1467 1469 1582 +f 1229 1233 1230 +f 588 1425 2404 +f 231 235 230 +f 627 1531 1773 +f 1553 2017 2352 +f 627 1773 2205 +f 1530 2032 1892 +f 1770 1056 307 +f 637 1450 2016 +f 325 327 265 +f 2402 1694 2326 +f 1807 1806 1801 +f 1805 1288 1287 +f 355 1806 1805 +f 1493 1801 956 +f 1678 2399 1736 +f 1852 2396 2413 +f 2156 199 198 +f 2041 819 817 +f 2117 1740 1541 +f 739 2046 2045 +f 1169 2052 899 +f 1607 2220 2405 +f 1110 1194 1111 +f 2052 2434 900 +f 1528 845 2253 +f 2230 2381 2233 +f 1144 1146 1549 +f 2315 2314 2317 +f 882 1214 1627 +f 1566 1213 2352 +f 1342 2376 2039 +f 1858 1341 1494 +f 381 601 600 +f 1006 365 364 +f 1173 1051 1170 +f 373 2168 376 +f 1666 1792 1794 +f 376 1173 717 +f 2324 1663 1661 +f 2405 2220 1663 +f 1734 2247 1735 +f 1589 1588 926 +f 874 877 872 +f 1559 2129 1556 +f 2342 2247 926 +f 2109 2110 873 +f 1875 2399 1678 +f 872 877 878 +f 853 909 854 +f 854 2106 1708 +f 184 697 610 +f 911 910 2105 +f 1861 2015 2009 +f 1955 255 253 +f 1514 1513 973 +f 2406 2357 2007 +f 2243 1863 1540 +f 1513 1794 1793 +f 168 1157 171 +f 2121 149 2122 +f 1921 540 535 +f 1814 801 1815 +f 981 800 802 +f 800 2293 801 +f 59 978 62 +f 1279 1815 2092 +f 1205 1203 2354 +f 1200 735 734 +f 1644 1643 1646 +f 2274 2332 389 +f 919 1962 270 +f 1492 1491 1495 +f 2315 771 1523 +f 177 182 1706 +f 1165 2288 2362 +f 44 1386 294 +f 215 217 1568 +f 217 208 207 +f 759 2337 944 +f 1852 1575 1574 +f 1679 2354 462 +f 108 2293 1307 +f 1895 31 30 +f 1155 1157 168 +f 2196 1396 2197 +f 1397 1953 1395 +f 1787 1913 1363 +f 368 372 827 +f 84 2225 2189 +f 1544 1027 1660 +f 1588 1590 1574 +f 664 929 931 +f 281 280 683 +f 1108 1335 1334 +f 2389 2301 1160 +f 1108 687 1335 +f 2295 251 250 +f 529 532 2429 +f 247 246 249 +f 1556 2129 1748 +f 1387 450 336 +f 1481 1836 1926 +f 448 450 1388 +f 282 23 285 +f 727 728 2071 +f 451 1265 1266 +f 746 1371 1409 +f 1370 2424 292 +f 745 2424 743 +f 2236 2186 292 +f 1443 1097 910 +f 445 697 184 +f 870 531 484 +f 811 1662 2339 +f 801 2293 1815 +f 1930 976 2153 +f 836 833 832 +f 132 128 125 +f 530 251 485 +f 435 1731 433 +f 2428 2005 2226 +f 1430 1429 1819 +f 1416 104 1886 +f 2006 2005 2428 +f 794 949 88 +f 269 2226 2112 +f 1006 364 1342 +f 378 600 2248 +f 363 601 364 +f 1492 1495 1804 +f 772 2010 2290 +f 1513 1793 1459 +f 773 776 2239 +f 2197 2015 219 +f 1171 2403 658 +f 776 775 703 +f 658 2403 2239 +f 1442 1441 16 +f 1171 773 2403 +f 714 2422 2280 +f 2134 566 2057 +f 543 542 2092 +f 1965 566 1968 +f 895 2020 259 +f 2194 2365 1332 +f 1823 1414 2226 +f 1086 1090 1092 +f 225 88 87 +f 488 491 340 +f 1687 594 1133 +f 1468 1987 10 +f 1687 344 597 +f 1106 2228 2227 +f 271 1335 1052 +f 888 2166 884 +f 884 886 1708 +f 1845 2159 1306 +f 1933 2326 500 +f 2060 850 847 +f 1967 2400 1711 +f 475 2259 2269 +f 2268 1883 435 +f 434 2269 2259 +f 1487 2212 2214 +f 252 784 1046 +f 473 705 704 +f 1045 1047 2282 +f 1732 1731 2414 +f 253 256 1955 +f 699 705 700 +f 662 2191 2277 +f 375 2168 373 +f 1556 1555 1559 +f 247 249 145 +f 1217 947 1979 +f 1753 422 420 +f 501 499 498 +f 993 2054 1717 +f 911 2107 912 +f 2166 2165 1918 +f 570 572 2435 +f 1064 2435 572 +f 1719 1157 1294 +f 172 2202 169 +f 1867 622 624 +f 998 1695 999 +f 788 791 1720 +f 595 592 2064 +f 1505 810 812 +f 678 1663 679 +f 1671 650 2439 +f 1231 2439 119 +f 1807 1492 1804 +f 798 641 796 +f 2302 1493 1644 +f 1018 2252 2251 +f 2080 2078 2079 +f 754 1729 1475 +f 1301 1060 1610 +f 1562 167 758 +f 1065 1153 1223 +f 2321 1154 2322 +f 1225 1227 1226 +f 690 412 1659 +f 303 1705 1703 +f 1153 2202 172 +f 1135 1834 1835 +f 2237 613 616 +f 2415 1650 2436 +f 2291 1651 2438 +f 2415 2436 2437 +f 777 779 780 +f 200 199 332 +f 1681 1359 2341 +f 2043 2055 2075 +f 1196 2423 1304 +f 545 1078 546 +f 1756 2087 2085 +f 1345 2279 1348 +f 547 736 471 +f 18 737 786 +f 1410 1817 1085 +f 1872 1586 101 +f 1725 1854 150 +f 1652 950 1343 +f 637 2017 1550 +f 914 913 1777 +f 2364 964 459 +f 1703 1642 1278 +f 1994 2157 2250 +f 901 898 897 +f 2398 1976 864 +f 1970 1608 1972 +f 1607 2221 2220 +f 939 281 942 +f 2389 684 2301 +f 747 2139 748 +f 1268 1270 1385 +f 1604 843 846 +f 844 1915 2253 +f 111 2354 1203 +f 1309 60 1782 +f 963 962 964 +f 1308 1307 981 +f 2286 1568 1653 +f 787 792 789 +f 809 807 1246 +f 1294 1157 1156 +f 761 763 764 +f 577 581 1919 +f 1915 2244 2183 +f 218 256 221 +f 377 323 2039 +f 2068 182 181 +f 1190 2423 1196 +f 2000 1261 2061 +f 2301 2167 2298 +f 268 1052 1429 +f 1727 1855 1854 +f 2364 913 964 +f 2049 2129 2102 +f 371 1913 1912 +f 1846 924 1591 +f 917 2129 1559 +f 1143 1141 1074 +f 481 1143 868 +f 869 865 871 +f 864 1073 2398 +f 1778 913 2073 +f 1384 2215 1385 +f 969 971 972 +f 1667 985 984 +f 1958 2150 681 +f 2145 1982 1981 +f 973 1513 1459 +f 1529 971 2382 +f 1540 1518 2244 +f 2401 2381 2232 +f 1528 1519 1525 +f 1516 1862 1861 +f 1478 1634 1633 +f 1714 850 2058 +f 555 1674 165 +f 587 517 516 +f 1277 1278 1642 +f 2310 1224 1983 +f 297 1228 2276 +f 1640 1642 2250 +f 2130 1873 1761 +f 1456 1487 2214 +f 1485 698 700 +f 2197 775 772 +f 1759 1761 50 +f 1239 618 476 +f 1623 45 2077 +f 2266 2267 293 +f 1206 1145 1207 +f 2289 2081 2397 +f 2363 2288 2352 +f 2408 2072 1148 +f 2351 2017 2016 +f 2374 2289 2397 +f 1554 1553 1116 +f 1729 1728 1475 +f 2365 2198 1090 +f 468 1120 466 +f 464 469 465 +f 2217 510 1851 +f 2382 2381 2401 +f 1603 2345 2344 +f 285 2331 2124 +f 1457 1435 1458 +f 1941 2366 1944 +f 1656 1690 1692 +f 1444 672 829 +f 1247 990 989 +f 1942 1941 708 +f 1310 1315 1313 +f 1115 2386 1116 +f 134 2223 1673 +f 934 1720 720 +f 597 344 345 +f 1506 789 2012 +f 1721 396 393 +f 2391 1020 458 +f 1021 1626 1035 +f 2227 1103 1106 +f 1102 2229 1104 +f 640 1102 797 +f 1098 1104 275 +f 442 1488 2228 +f 2112 922 269 +f 684 2167 2301 +f 798 953 1801 +f 126 130 127 +f 125 129 126 +f 1154 2321 2323 +f 1375 1699 1376 +f 1996 2310 1700 +f 1152 1154 1983 +f 1700 2312 2321 +f 1991 1222 1221 +f 1352 1501 1460 +f 1254 2425 2096 +f 1902 1269 2215 +f 1384 1381 1380 +f 1353 1762 2437 +f 68 948 946 +f 1033 1032 2383 +f 1031 1382 734 +f 1401 737 1546 +f 1656 762 765 +f 670 1319 553 +f 872 878 875 +f 2040 816 1389 +f 1361 2156 2154 +f 110 462 2354 +f 2141 2140 205 +f 817 816 2041 +f 139 1537 206 +f 820 819 1256 +f 2140 2142 286 +f 1511 1812 2330 +f 240 606 236 +f 1484 1873 1485 +f 1660 2225 84 +f 2416 883 343 +f 767 770 2179 +f 1522 768 611 +f 395 593 595 +f 1750 2361 1775 +f 1960 2359 1176 +f 1546 213 1401 +f 207 209 210 +f 2241 2046 741 +f 1019 1024 1022 +f 1546 1081 213 +f 1816 1817 1346 +f 1818 1624 226 +f 1838 2192 1839 +f 1965 1968 1966 +f 2433 2377 2034 +f 1242 2260 764 +f 1657 2286 1653 +f 1969 543 2093 +f 2092 1815 2093 +f 2113 390 2332 +f 1499 391 886 +f 1512 1514 1519 +f 844 1916 1915 +f 887 2106 912 +f 1547 2099 2160 +f 1739 1741 2327 +f 1149 2128 2414 +f 740 1740 2117 +f 2189 724 1592 +f 1350 1651 1351 +f 1350 2437 2436 +f 17 16 715 +f 25 29 30 +f 1339 1006 1342 +f 270 1962 1647 +f 367 366 2067 +f 2115 2226 391 +f 507 504 503 +f 2026 276 2147 +f 1478 265 1634 +f 1842 2180 1844 +f 2034 2400 2433 +f 2161 2164 1843 +f 342 2084 343 +f 1164 2072 2346 +f 1115 1406 2370 +f 290 293 2267 +f 1897 2054 995 +f 900 2434 2219 +f 1769 1768 1771 +f 134 496 2223 +f 1054 1056 1057 +f 1757 1758 2111 +f 2439 650 1234 +f 1672 135 1673 +f 1772 2205 1773 +f 875 1768 876 +f 710 707 706 +f 821 1318 828 +f 2206 2205 1772 +f 669 672 1444 +f 875 146 1771 +f 926 2247 1589 +f 307 1056 308 +f 823 826 828 +f 1198 1111 1613 +f 2219 1168 2220 +f 1803 1495 1494 +f 357 2101 2151 +f 317 1286 1876 +f 1962 1645 1647 +f 966 1854 967 +f 1951 2281 330 +f 170 2201 1636 +f 968 2347 152 +f 159 158 162 +f 1228 1066 1226 +f 529 2429 2308 +f 871 2369 2324 +f 837 723 726 +f 56 54 52 +f 2240 2211 2047 +f 2045 742 739 +f 2284 2372 780 +f 2181 422 1753 +f 772 776 773 +f 2263 702 2262 +f 1649 779 2438 +f 2292 2372 961 +f 39 1245 40 +f 1696 1059 1253 +f 2022 2021 2133 +f 2136 2135 260 +f 299 2138 1947 +f 1659 1947 1946 +f 423 422 2181 +f 418 1849 425 +f 1230 1688 233 +f 2013 651 654 +f 1183 2211 2418 +f 656 2087 2422 +f 1640 425 1641 +f 427 423 2181 +f 2067 599 367 +f 1706 182 382 +f 59 1308 978 +f 980 801 1814 +f 1574 1590 1853 +f 1573 1591 1588 +f 928 931 929 +f 431 136 520 +f 2413 2396 2412 +f 515 520 518 +f 367 598 601 +f 2033 910 906 +f 2327 315 1739 +f 1185 2045 2044 +f 602 2248 598 +f 93 97 2327 +f 1999 2103 2067 +f 1857 1646 1716 +f 407 324 405 +f 1800 357 2151 +f 250 252 256 +f 1565 2309 1505 +f 1504 2429 532 +f 2145 2144 2150 +f 2414 1882 2430 +f 1505 1504 1503 +f 1151 2430 1564 +f 2309 1565 2430 +f 1300 1609 514 +f 1245 1610 40 +f 673 1988 2417 +f 175 183 173 +f 1990 769 2417 +f 694 674 697 +f 609 1523 771 +f 2012 770 1506 +f 223 87 226 +f 2005 1823 2226 +f 224 226 940 +f 1428 2353 1431 +f 1840 1837 1851 +f 686 942 281 +f 1651 2291 1351 +f 779 1650 2285 +f 1123 2437 1699 +f 781 780 2372 +f 1426 189 188 +f 11 10 1833 +f 616 187 2237 +f 229 544 1424 +f 1767 2204 2076 +f 1896 2054 1897 +f 1976 1974 1972 +f 1896 2051 2054 +f 630 238 1481 +f 1432 1434 1458 +f 1872 360 362 +f 193 2089 2088 +f 1673 653 1671 +f 2257 1909 2013 +f 1116 2420 1114 +f 1209 1213 1210 +f 681 2339 1662 +f 1957 1956 1959 +f 2261 1654 1655 +f 2287 1244 2036 +f 666 2396 1852 +f 555 760 1674 +f 803 1313 806 +f 986 1445 2028 +f 555 757 760 +f 2411 2413 2412 +f 554 758 559 +f 369 827 249 +f 586 165 1674 +f 560 559 1364 +f 41 804 809 +f 1302 520 1061 +f 977 976 62 +f 2135 2356 260 +f 100 2074 98 +f 460 462 110 +f 1856 1871 1870 +f 729 731 732 +f 2072 2409 983 +f 608 1523 609 +f 342 341 2084 +f 2064 2097 596 +f 982 983 2083 +f 1721 395 597 +f 2336 2369 862 +f 506 679 680 +f 350 2303 748 +f 1889 479 2373 +f 1277 2100 301 +f 1178 1181 2358 +f 1708 391 2099 +f 1998 2246 1999 +f 1998 1918 2246 +f 2160 2099 1990 +f 2001 2000 848 +f 113 115 1681 +f 291 2267 47 +f 652 651 1909 +f 2165 2108 2246 +f 795 953 798 +f 1832 819 2421 +f 1265 448 1388 +f 2041 2421 819 +f 1489 34 1490 +f 1693 2377 1694 +f 2433 1815 1694 +f 536 539 2132 +f 2164 2163 453 +f 1797 2241 214 +f 1568 1210 1566 +f 2355 1839 1159 +f 2235 467 1414 +f 1868 1391 1390 +f 1745 101 1586 +f 2419 1776 1902 +f 1179 1949 1948 +f 2358 2360 1178 +f 2311 1225 1224 +f 1349 2333 1352 +f 2372 352 2371 +f 2437 1350 1353 +f 778 2291 2438 +f 2415 2437 1123 +f 1848 2285 1270 +f 752 288 753 +f 1427 1426 2340 +f 2233 2380 2379 +f 1813 1509 1669 +f 2370 2386 1115 +f 625 627 2205 +f 2397 2420 2374 +f 1733 1735 2110 +f 20 2331 285 +f 222 2196 219 +f 2095 1000 997 +f 41 1791 808 +f 1421 128 1418 +f 843 1604 1950 +f 836 76 74 +f 1263 1490 2094 +f 1304 2423 1648 +f 1809 834 1811 +f 2133 2057 2022 +f 2188 2388 327 +f 2059 850 2060 +f 851 2388 1305 +f 1303 1305 2162 +f 2159 1845 2326 +f 959 1502 957 +f 1641 1270 1570 +f 1883 251 1884 +f 483 1338 1142 +f 48 50 51 +f 725 2224 643 +f 13 15 16 +f 1938 19 18 +f 1907 276 1959 +f 2149 1982 2145 +f 82 84 1592 +f 1021 1024 1019 +f 776 2263 2265 +f 17 2296 470 +f 1032 734 732 +f 1382 1385 734 +f 335 728 727 +f 2277 1442 16 +f 1061 520 624 +f 1610 1059 1723 +f 1368 1913 1787 +f 1296 1295 1292 +f 1362 758 167 +f 1910 825 372 +f 429 2111 876 +f 514 518 1300 +f 1964 2128 2126 +f 841 1291 2127 +f 2119 900 2219 +f 2209 1897 995 +f 1389 332 2040 +f 727 331 202 +f 975 974 977 +f 1798 1042 237 +f 1014 2153 1015 +f 735 733 732 +f 2297 361 2073 +f 1901 1176 749 +f 2359 1960 1961 +f 1535 1536 1393 +f 522 1944 525 +f 1250 1249 1943 +f 6 9 544 +f 133 135 2368 +f 227 12 228 +f 2258 1040 2426 +f 2009 2015 2290 +f 1524 1528 1525 +f 714 1077 715 +f 2123 31 1635 +f 1919 578 577 +f 2287 2036 1619 +f 715 16 1441 +f 1764 1297 1765 +f 2294 1811 834 +f 1874 604 1508 +f 180 182 176 +f 1094 1096 905 +f 366 1497 1917 +f 1276 1272 405 +f 2324 2369 2375 +f 274 276 1907 +f 1422 1423 833 +f 903 1717 2053 +f 1765 1952 1691 +f 1050 1170 1051 +f 2014 1409 1406 +f 2288 2374 2352 +f 1331 1328 2365 +f 466 679 1414 +f 1908 1160 2301 +f 89 949 106 +f 942 686 1159 +f 2112 2226 2115 +f 274 1101 275 +f 1052 1624 1429 +f 1763 1692 1690 +f 2351 1566 2352 +f 1654 2261 2260 +f 1292 1295 1298 +f 2079 47 2267 +f 2083 983 2409 +f 2127 1291 94 +f 1756 1816 1346 +f 575 2213 2212 +f 222 1953 2196 +f 1964 2126 1963 +f 1346 1817 1410 +f 843 2207 844 +f 1119 679 466 +f 2218 308 1280 +f 1585 1877 1582 +f 239 242 240 +f 2174 2175 2204 +f 131 2350 132 +f 900 897 896 +f 2295 1914 482 +f 1936 1935 2175 +f 1357 1073 1010 +f 1069 1073 864 +f 1320 2032 1317 +f 1791 1001 171 +f 1561 624 1650 +f 829 825 992 +f 1510 1458 1434 +f 1594 984 242 +f 421 1979 1753 +f 779 778 2438 +f 286 1534 2140 +f 2341 2169 2170 +f 1174 750 1181 +f 64 63 68 +f 2061 849 2000 +f 2035 1967 1711 +f 2325 2356 2171 +f 2094 1259 1263 +f 1836 1482 1625 +f 2318 2319 2180 +f 1054 634 1281 +f 2223 654 1673 +f 3 2016 951 +f 1316 1323 1317 +f 337 345 344 +f 591 769 592 +f 419 426 421 +f 1226 1223 1225 +f 641 1018 639 +f 505 502 512 +f 311 313 124 +f 456 1085 1817 +f 1275 209 2117 +f 698 1485 701 +f 699 703 1396 +f 1433 1435 2242 +f 293 745 2266 +f 2340 615 136 +f 1618 1216 136 +f 614 592 615 +f 275 1104 2431 +f 272 443 271 +f 675 694 1989 +f 2172 1236 2431 +f 391 2226 2099 +f 1159 1160 2355 +f 218 220 1954 +f 1796 1025 1035 +f 1678 1736 1676 +f 1531 1057 1773 +f 1026 1029 1027 +f 1740 740 2407 +f 711 708 707 +f 931 431 520 +f 1703 1278 303 +f 160 1587 194 +f 1534 2121 1535 +f 1811 2380 1812 +f 237 606 1798 +f 1515 1520 1516 +f 1571 1576 1572 +f 1212 1628 1627 +f 889 858 356 +f 741 209 216 +f 1729 46 1728 +f 117 1230 2340 +f 783 784 2268 +f 2422 657 656 +f 1446 710 313 +f 1246 1365 2394 +f 1255 1259 1256 +f 1393 1532 1535 +f 2201 149 1636 +f 1560 624 1561 +f 2377 2433 1694 +f 2293 734 1815 +f 2307 1383 1888 +f 1502 1352 2333 +f 467 466 1414 +f 2252 1800 2151 +f 1738 1541 1740 +f 44 291 47 +f 1663 2220 679 +f 1142 1338 1916 +f 1411 737 738 +f 2260 1242 1244 +f 2434 2052 1168 +f 1260 1259 1258 +f 286 2124 1534 +f 572 2203 2200 +f 1844 2180 2319 +f 1394 1279 2092 +f 1202 734 2293 +f 1385 1270 734 diff --git a/Videos/olcPGEX_Graphics2D.h b/Videos/olcPGEX_Graphics2D.h new file mode 100644 index 0000000..de8c0f3 --- /dev/null +++ b/Videos/olcPGEX_Graphics2D.h @@ -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 +#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 \ No newline at end of file diff --git a/Videos/olcPGEX_Graphics3D.h b/Videos/olcPGEX_Graphics3D.h new file mode 100644 index 0000000..9c6dd80 --- /dev/null +++ b/Videos/olcPGEX_Graphics3D.h @@ -0,0 +1,1174 @@ +/* + olcPGEX_Graphics3D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | 3D Rendering - v0.1 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + support for software rendering 3D graphics. + + NOTE!!! This file is under development and may change! + + 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 + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_GFX3D +#define OLC_PGEX_GFX3D + +#include +#include +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX3D : public olc::PGEX + { + + public: + + struct vec2d + { + float x = 0; + float y = 0; + float z = 0; + }; + + struct vec3d + { + float x = 0; + float y = 0; + float z = 0; + float w = 1; // Need a 4th term to perform sensible matrix vector multiplication + }; + + struct triangle + { + vec3d p[3]; + vec2d t[3]; + olc::Pixel col; + }; + + struct mat4x4 + { + float m[4][4] = { 0 }; + }; + + struct mesh + { + std::vector tris; + }; + + class Math + { + public: + inline Math(); + public: + inline static vec3d Mat_MultiplyVector(mat4x4 &m, vec3d &i); + inline static mat4x4 Mat_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2); + inline static mat4x4 Mat_MakeIdentity(); + inline static mat4x4 Mat_MakeRotationX(float fAngleRad); + inline static mat4x4 Mat_MakeRotationY(float fAngleRad); + inline static mat4x4 Mat_MakeRotationZ(float fAngleRad); + inline static mat4x4 Mat_MakeScale(float x, float y, float z); + inline static mat4x4 Mat_MakeTranslation(float x, float y, float z); + inline static mat4x4 Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar); + inline static mat4x4 Mat_PointAt(vec3d &pos, vec3d &target, vec3d &up); + inline static mat4x4 Mat_QuickInverse(mat4x4 &m); // Only for Rotation/Translation Matrices + inline static mat4x4 Mat_Inverse(olc::GFX3D::mat4x4 &m); + + inline static vec3d Vec_Add(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Sub(vec3d &v1, vec3d &v2); + inline static vec3d Vec_Mul(vec3d &v1, float k); + inline static vec3d Vec_Div(vec3d &v1, float k); + inline static float Vec_DotProduct(vec3d &v1, vec3d &v2); + inline static float Vec_Length(vec3d &v); + inline static vec3d Vec_Normalise(vec3d &v); + inline static vec3d Vec_CrossProduct(vec3d &v1, vec3d &v2); + inline static vec3d Vec_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd, float &t); + + inline static int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2); + }; + + enum RENDERFLAGS + { + RENDER_WIRE = 0x01, + RENDER_FLAT = 0x02, + RENDER_TEXTURED = 0x04, + RENDER_CULL_CW = 0x08, + RENDER_CULL_CCW = 0x10, + RENDER_DEPTH = 0x20, + }; + + + class PipeLine + { + public: + PipeLine(); + + public: + void SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight); + void SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up); + void SetTransform(olc::GFX3D::mat4x4 &transform); + void SetTexture(olc::Sprite *texture); + void SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col); + uint32_t Render(std::vector &triangles, uint32_t flags = RENDER_CULL_CW | RENDER_TEXTURED | RENDER_DEPTH); + + private: + olc::GFX3D::mat4x4 matProj; + olc::GFX3D::mat4x4 matView; + olc::GFX3D::mat4x4 matWorld; + olc::Sprite *sprTexture; + float fViewX; + float fViewY; + float fViewW; + float fViewH; + }; + + + + public: + //static const int RF_TEXTURE = 0x00000001; + //static const int RF_ = 0x00000002; + + inline static void ConfigureDisplay(); + inline static void ClearDepth(); + inline static void AddTriangleToScene(olc::GFX3D::triangle &tri); + inline static void RenderScene(); + + inline static void DrawTriangleFlat(olc::GFX3D::triangle &tri); + inline static void DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col = olc::WHITE); + inline static void DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr); + inline static void TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr); + + // Draws a sprite with the transform applied + //inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + + private: + static float* m_DepthBuffer; + }; +} + + + + +namespace olc +{ + olc::GFX3D::Math::Math() + { + + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Mat_MultiplyVector(olc::GFX3D::mat4x4 &m, olc::GFX3D::vec3d &i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeIdentity() + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationX(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationY(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeRotationZ(float fAngleRad) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeScale(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = x; + matrix.m[1][1] = y; + matrix.m[2][2] = z; + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeTranslation(float x, float y, float z) + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_MultiplyMatrix(olc::GFX3D::mat4x4 &m1, olc::GFX3D::mat4x4 &m2) + { + olc::GFX3D::mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_PointAt(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &target, olc::GFX3D::vec3d &up) + { + // Calculate new forward direction + olc::GFX3D::vec3d newForward = Vec_Sub(target, pos); + newForward = Vec_Normalise(newForward); + + // Calculate new Up direction + olc::GFX3D::vec3d a = Vec_Mul(newForward, Vec_DotProduct(up, newForward)); + olc::GFX3D::vec3d newUp = Vec_Sub(up, a); + newUp = Vec_Normalise(newUp); + + // New Right direction is easy, its just cross product + olc::GFX3D::vec3d newRight = Vec_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_QuickInverse(olc::GFX3D::mat4x4 &m) // Only for Rotation/Translation Matrices + { + olc::GFX3D::mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + olc::GFX3D::mat4x4 olc::GFX3D::Math::Mat_Inverse(olc::GFX3D::mat4x4 &m) + { + double det; + + + mat4x4 matInv; + + matInv.m[0][0] = m.m[1][1] * m.m[2][2] * m.m[3][3] - m.m[1][1] * m.m[2][3] * m.m[3][2] - m.m[2][1] * m.m[1][2] * m.m[3][3] + m.m[2][1] * m.m[1][3] * m.m[3][2] + m.m[3][1] * m.m[1][2] * m.m[2][3] - m.m[3][1] * m.m[1][3] * m.m[2][2]; + matInv.m[1][0] = -m.m[1][0] * m.m[2][2] * m.m[3][3] + m.m[1][0] * m.m[2][3] * m.m[3][2] + m.m[2][0] * m.m[1][2] * m.m[3][3] - m.m[2][0] * m.m[1][3] * m.m[3][2] - m.m[3][0] * m.m[1][2] * m.m[2][3] + m.m[3][0] * m.m[1][3] * m.m[2][2]; + matInv.m[2][0] = m.m[1][0] * m.m[2][1] * m.m[3][3] - m.m[1][0] * m.m[2][3] * m.m[3][1] - m.m[2][0] * m.m[1][1] * m.m[3][3] + m.m[2][0] * m.m[1][3] * m.m[3][1] + m.m[3][0] * m.m[1][1] * m.m[2][3] - m.m[3][0] * m.m[1][3] * m.m[2][1]; + matInv.m[3][0] = -m.m[1][0] * m.m[2][1] * m.m[3][2] + m.m[1][0] * m.m[2][2] * m.m[3][1] + m.m[2][0] * m.m[1][1] * m.m[3][2] - m.m[2][0] * m.m[1][2] * m.m[3][1] - m.m[3][0] * m.m[1][1] * m.m[2][2] + m.m[3][0] * m.m[1][2] * m.m[2][1]; + matInv.m[0][1] = -m.m[0][1] * m.m[2][2] * m.m[3][3] + m.m[0][1] * m.m[2][3] * m.m[3][2] + m.m[2][1] * m.m[0][2] * m.m[3][3] - m.m[2][1] * m.m[0][3] * m.m[3][2] - m.m[3][1] * m.m[0][2] * m.m[2][3] + m.m[3][1] * m.m[0][3] * m.m[2][2]; + matInv.m[1][1] = m.m[0][0] * m.m[2][2] * m.m[3][3] - m.m[0][0] * m.m[2][3] * m.m[3][2] - m.m[2][0] * m.m[0][2] * m.m[3][3] + m.m[2][0] * m.m[0][3] * m.m[3][2] + m.m[3][0] * m.m[0][2] * m.m[2][3] - m.m[3][0] * m.m[0][3] * m.m[2][2]; + matInv.m[2][1] = -m.m[0][0] * m.m[2][1] * m.m[3][3] + m.m[0][0] * m.m[2][3] * m.m[3][1] + m.m[2][0] * m.m[0][1] * m.m[3][3] - m.m[2][0] * m.m[0][3] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[2][3] + m.m[3][0] * m.m[0][3] * m.m[2][1]; + matInv.m[3][1] = m.m[0][0] * m.m[2][1] * m.m[3][2] - m.m[0][0] * m.m[2][2] * m.m[3][1] - m.m[2][0] * m.m[0][1] * m.m[3][2] + m.m[2][0] * m.m[0][2] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[2][2] - m.m[3][0] * m.m[0][2] * m.m[2][1]; + matInv.m[0][2] = m.m[0][1] * m.m[1][2] * m.m[3][3] - m.m[0][1] * m.m[1][3] * m.m[3][2] - m.m[1][1] * m.m[0][2] * m.m[3][3] + m.m[1][1] * m.m[0][3] * m.m[3][2] + m.m[3][1] * m.m[0][2] * m.m[1][3] - m.m[3][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][2] = -m.m[0][0] * m.m[1][2] * m.m[3][3] + m.m[0][0] * m.m[1][3] * m.m[3][2] + m.m[1][0] * m.m[0][2] * m.m[3][3] - m.m[1][0] * m.m[0][3] * m.m[3][2] - m.m[3][0] * m.m[0][2] * m.m[1][3] + m.m[3][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][2] = m.m[0][0] * m.m[1][1] * m.m[3][3] - m.m[0][0] * m.m[1][3] * m.m[3][1] - m.m[1][0] * m.m[0][1] * m.m[3][3] + m.m[1][0] * m.m[0][3] * m.m[3][1] + m.m[3][0] * m.m[0][1] * m.m[1][3] - m.m[3][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][2] = -m.m[0][0] * m.m[1][1] * m.m[3][2] + m.m[0][0] * m.m[1][2] * m.m[3][1] + m.m[1][0] * m.m[0][1] * m.m[3][2] - m.m[1][0] * m.m[0][2] * m.m[3][1] - m.m[3][0] * m.m[0][1] * m.m[1][2] + m.m[3][0] * m.m[0][2] * m.m[1][1]; + matInv.m[0][3] = -m.m[0][1] * m.m[1][2] * m.m[2][3] + m.m[0][1] * m.m[1][3] * m.m[2][2] + m.m[1][1] * m.m[0][2] * m.m[2][3] - m.m[1][1] * m.m[0][3] * m.m[2][2] - m.m[2][1] * m.m[0][2] * m.m[1][3] + m.m[2][1] * m.m[0][3] * m.m[1][2]; + matInv.m[1][3] = m.m[0][0] * m.m[1][2] * m.m[2][3] - m.m[0][0] * m.m[1][3] * m.m[2][2] - m.m[1][0] * m.m[0][2] * m.m[2][3] + m.m[1][0] * m.m[0][3] * m.m[2][2] + m.m[2][0] * m.m[0][2] * m.m[1][3] - m.m[2][0] * m.m[0][3] * m.m[1][2]; + matInv.m[2][3] = -m.m[0][0] * m.m[1][1] * m.m[2][3] + m.m[0][0] * m.m[1][3] * m.m[2][1] + m.m[1][0] * m.m[0][1] * m.m[2][3] - m.m[1][0] * m.m[0][3] * m.m[2][1] - m.m[2][0] * m.m[0][1] * m.m[1][3] + m.m[2][0] * m.m[0][3] * m.m[1][1]; + matInv.m[3][3] = m.m[0][0] * m.m[1][1] * m.m[2][2] - m.m[0][0] * m.m[1][2] * m.m[2][1] - m.m[1][0] * m.m[0][1] * m.m[2][2] + m.m[1][0] * m.m[0][2] * m.m[2][1] + m.m[2][0] * m.m[0][1] * m.m[1][2] - m.m[2][0] * m.m[0][2] * m.m[1][1]; + + det = m.m[0][0] * matInv.m[0][0] + m.m[0][1] * matInv.m[1][0] + m.m[0][2] * matInv.m[2][0] + m.m[0][3] * matInv.m[3][0]; + // if (det == 0) return false; + + det = 1.0 / det; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + matInv.m[i][j] *= (float)det; + + return matInv; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Add(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Sub(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Mul(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Div(olc::GFX3D::vec3d &v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float olc::GFX3D::Math::Vec_DotProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z; + } + + float olc::GFX3D::Math::Vec_Length(olc::GFX3D::vec3d &v) + { + return sqrtf(Vec_DotProduct(v, v)); + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_Normalise(olc::GFX3D::vec3d &v) + { + float l = Vec_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_CrossProduct(olc::GFX3D::vec3d &v1, olc::GFX3D::vec3d &v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + olc::GFX3D::vec3d olc::GFX3D::Math::Vec_IntersectPlane(olc::GFX3D::vec3d &plane_p, olc::GFX3D::vec3d &plane_n, olc::GFX3D::vec3d &lineStart, olc::GFX3D::vec3d &lineEnd, float &t) + { + plane_n = Vec_Normalise(plane_n); + float plane_d = -Vec_DotProduct(plane_n, plane_p); + float ad = Vec_DotProduct(lineStart, plane_n); + float bd = Vec_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + olc::GFX3D::vec3d lineStartToEnd = Vec_Sub(lineEnd, lineStart); + olc::GFX3D::vec3d lineToIntersect = Vec_Mul(lineStartToEnd, t); + return Vec_Add(lineStart, lineToIntersect); + } + + + int olc::GFX3D::Math::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Math::Vec_Normalise(plane_n); + + out_tri1.t[0] = in_tri.t[0]; + out_tri2.t[0] = in_tri.t[0]; + out_tri1.t[1] = in_tri.t[1]; + out_tri2.t[1] = in_tri.t[1]; + out_tri1.t[2] = in_tri.t[2]; + out_tri2.t[2] = in_tri.t[2]; + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d &p) + { + vec3d n = Math::Vec_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Math::Vec_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.t[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.t[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.t[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.t[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.t[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.t[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col = olc::MAGENTA;// in_tri.col; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[1].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[1].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[1].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.t[2].x = t * (outside_tex[1]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[1]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[1]->z - inside_tex[0]->z) + inside_tex[0]->z; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col = olc::GREEN;// in_tri.col; + out_tri2.col = olc::RED;// in_tri.col; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.t[0] = *inside_tex[0]; + + out_tri1.p[1] = *inside_points[1]; + out_tri1.t[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.t[2].x = t * (outside_tex[0]->x - inside_tex[0]->x) + inside_tex[0]->x; + out_tri1.t[2].y = t * (outside_tex[0]->y - inside_tex[0]->y) + inside_tex[0]->y; + out_tri1.t[2].z = t * (outside_tex[0]->z - inside_tex[0]->z) + inside_tex[0]->z; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[1] = *inside_points[1]; + out_tri2.t[1] = *inside_tex[1]; + out_tri2.p[0] = out_tri1.p[2]; + out_tri2.t[0] = out_tri1.t[2]; + out_tri2.p[2] = Math::Vec_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.t[2].x = t * (outside_tex[0]->x - inside_tex[1]->x) + inside_tex[1]->x; + out_tri2.t[2].y = t * (outside_tex[0]->y - inside_tex[1]->y) + inside_tex[1]->y; + out_tri2.t[2].z = t * (outside_tex[0]->z - inside_tex[1]->z) + inside_tex[1]->z; + return 2; // Return two newly formed triangles which form a quad + } + + return 0; + } + + void GFX3D::DrawTriangleFlat(olc::GFX3D::triangle &tri) + { + pge->FillTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, tri.col); + } + + void GFX3D::DrawTriangleWire(olc::GFX3D::triangle &tri, olc::Pixel col) + { + pge->DrawTriangle(tri.p[0].x, tri.p[0].y, tri.p[1].x, tri.p[1].y, tri.p[2].x, tri.p[2].y, col); + } + + void GFX3D::TexturedTriangle(int x1, int y1, float u1, float v1, float w1, + int x2, int y2, float u2, float v2, float w2, + int x3, int y3, float u3, float v3, float w3, olc::Sprite* spr) + + { + if (y2 < y1) + { + std::swap(y1, y2); + std::swap(x1, x2); + std::swap(u1, u2); + std::swap(v1, v2); + std::swap(w1, w2); + } + + if (y3 < y1) + { + std::swap(y1, y3); + std::swap(x1, x3); + std::swap(u1, u3); + std::swap(v1, v3); + std::swap(w1, w3); + } + + if (y3 < y2) + { + std::swap(y2, y3); + std::swap(x2, x3); + std::swap(u2, u3); + std::swap(v2, v3); + std::swap(w2, w3); + } + + int dy1 = y2 - y1; + int dx1 = x2 - x1; + float dv1 = v2 - v1; + float du1 = u2 - u1; + float dw1 = w2 - w1; + + int dy2 = y3 - y1; + int dx2 = x3 - x1; + float dv2 = v3 - v1; + float du2 = u3 - u1; + float dw2 = w3 - w1; + + float tex_u, tex_v, tex_w; + + float dax_step = 0, dbx_step = 0, + du1_step = 0, dv1_step = 0, + du2_step = 0, dv2_step = 0, + dw1_step = 0, dw2_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dw2_step = dw2 / (float)abs(dy2); + + if (dy1) + { + for (int i = y1; i <= y2; i++) + { + int ax = x1 + (float)(i - y1) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u1 + (float)(i - y1) * du1_step; + float tex_sv = v1 + (float)(i - y1) * dv1_step; + float tex_sw = w1 + (float)(i - y1) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + + } + } + + dy1 = y3 - y2; + dx1 = x3 - x2; + dv1 = v3 - v2; + du1 = u3 - u2; + dw1 = w3 - w2; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + du1_step = 0, dv1_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dw1_step = dw1 / (float)abs(dy1); + + if (dy1) + { + for (int i = y2; i <= y3; i++) + { + int ax = x2 + (float)(i - y2) * dax_step; + int bx = x1 + (float)(i - y1) * dbx_step; + + float tex_su = u2 + (float)(i - y2) * du1_step; + float tex_sv = v2 + (float)(i - y2) * dv1_step; + float tex_sw = w2 + (float)(i - y2) * dw1_step; + + float tex_eu = u1 + (float)(i - y1) * du2_step; + float tex_ev = v1 + (float)(i - y1) * dv2_step; + float tex_ew = w1 + (float)(i - y1) * dw2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sw, tex_ew); + } + + tex_u = tex_su; + tex_v = tex_sv; + tex_w = tex_sw; + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0.0f; + + for (int j = ax; j < bx; j++) + { + tex_u = (1.0f - t) * tex_su + t * tex_eu; + tex_v = (1.0f - t) * tex_sv + t * tex_ev; + tex_w = (1.0f - t) * tex_sw + t * tex_ew; + + if (tex_w > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_u / tex_w, tex_v / tex_w)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_w; + } + t += tstep; + } + } + } + } + + + void GFX3D::DrawTriangleTex(olc::GFX3D::triangle &tri, olc::Sprite* spr) + { + if (tri.p[1].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[1].y); + std::swap(tri.p[0].x, tri.p[1].x); + std::swap(tri.t[0].x, tri.t[1].x); + std::swap(tri.t[0].y, tri.t[1].y); + std::swap(tri.t[0].z, tri.t[1].z); + } + + if (tri.p[2].y < tri.p[0].y) + { + std::swap(tri.p[0].y, tri.p[2].y); + std::swap(tri.p[0].x, tri.p[2].x); + std::swap(tri.t[0].x, tri.t[2].x); + std::swap(tri.t[0].y, tri.t[2].y); + std::swap(tri.t[0].z, tri.t[2].z); + } + + if (tri.p[2].y < tri.p[1].y) + { + std::swap(tri.p[1].y, tri.p[2].y); + std::swap(tri.p[1].x, tri.p[2].x); + std::swap(tri.t[1].x, tri.t[2].x); + std::swap(tri.t[1].y, tri.t[2].y); + std::swap(tri.t[1].z, tri.t[2].z); + } + + int dy1 = tri.p[1].y - tri.p[0].y; + int dx1 = tri.p[1].x - tri.p[0].x; + float dv1 = tri.t[1].y - tri.t[0].y; + float du1 = tri.t[1].x - tri.t[0].x; + float dz1 = tri.t[1].z - tri.t[0].z; + + int dy2 = tri.p[2].y - tri.p[0].y; + int dx2 = tri.p[2].x - tri.p[0].x; + float dv2 = tri.t[2].y - tri.t[0].y; + float du2 = tri.t[2].x - tri.t[0].x; + float dz2 = tri.t[2].z - tri.t[0].z; + + float tex_x, tex_y, tex_z; + + float du1_step = 0, dv1_step = 0, du2_step = 0, dv2_step = 0, dz1_step = 0, dz2_step = 0; + float dax_step = 0, dbx_step = 0; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy2) du2_step = du2 / (float)abs(dy2); + if (dy2) dv2_step = dv2 / (float)abs(dy2); + if (dy2) dz2_step = dz2 / (float)abs(dy2); + + + + if (dy1) + { + for (int i = tri.p[0].y; i <= tri.p[1].y; i++) + { + int ax = tri.p[0].x + (i - tri.p[0].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[0].x + (float)(i - tri.p[0].y) * du1_step; + float tex_sv = tri.t[0].y + (float)(i - tri.p[0].y) * dv1_step; + float tex_sz = tri.t[0].z + (float)(i - tri.p[0].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + t += tstep; + } + + + } + } + + dy1 = tri.p[2].y - tri.p[1].y; + dx1 = tri.p[2].x - tri.p[1].x; + dv1 = tri.t[2].y - tri.t[1].y; + du1 = tri.t[2].x - tri.t[1].x; + dz1 = tri.t[2].z - tri.t[1].z; + + if (dy1) dax_step = dx1 / (float)abs(dy1); + if (dy2) dbx_step = dx2 / (float)abs(dy2); + + + du1_step = 0, dv1_step = 0;// , dz1_step = 0;// , du2_step = 0, dv2_step = 0; + if (dy1) du1_step = du1 / (float)abs(dy1); + if (dy1) dv1_step = dv1 / (float)abs(dy1); + if (dy1) dz1_step = dz1 / (float)abs(dy1); + + if (dy1) + { + for (int i = tri.p[1].y; i <= tri.p[2].y; i++) + { + int ax = tri.p[1].x + (i - tri.p[1].y) * dax_step; + int bx = tri.p[0].x + (i - tri.p[0].y) * dbx_step; + + // Start and end points in texture space + float tex_su = tri.t[1].x + (float)(i - tri.p[1].y) * du1_step; + float tex_sv = tri.t[1].y + (float)(i - tri.p[1].y) * dv1_step; + float tex_sz = tri.t[1].z + (float)(i - tri.p[1].y) * dz1_step; + + float tex_eu = tri.t[0].x + (float)(i - tri.p[0].y) * du2_step; + float tex_ev = tri.t[0].y + (float)(i - tri.p[0].y) * dv2_step; + float tex_ez = tri.t[0].z + (float)(i - tri.p[0].y) * dz2_step; + + if (ax > bx) + { + std::swap(ax, bx); + std::swap(tex_su, tex_eu); + std::swap(tex_sv, tex_ev); + std::swap(tex_sz, tex_ez); + } + + tex_x = tex_su; + tex_y = tex_sv; + tex_z = tex_sz; + + + float tstep = 1.0f / ((float)(bx - ax)); + float t = 0; + + for (int j = ax; j < bx; j++) + { + tex_x = (1.0f - t) * tex_su + t * tex_eu; + tex_y = (1.0f - t) * tex_sv + t * tex_ev; + tex_z = (1.0f - t) * tex_sz + t * tex_ez; + + if (tex_z > m_DepthBuffer[i*pge->ScreenWidth() + j]) + { + pge->Draw(j, i, spr->Sample(tex_x / tex_z, tex_y / tex_z)); + m_DepthBuffer[i*pge->ScreenWidth() + j] = tex_z; + } + + t += tstep; + } + } + } + + } + + float* GFX3D::m_DepthBuffer = nullptr; + + void GFX3D::ConfigureDisplay() + { + m_DepthBuffer = new float[pge->ScreenWidth() * pge->ScreenHeight()]{ 0 }; + } + + + void GFX3D::ClearDepth() + { + memset(m_DepthBuffer, 0, pge->ScreenWidth() * pge->ScreenHeight() * sizeof(float)); + } + + + + + GFX3D::PipeLine::PipeLine() + { + + } + + void GFX3D::PipeLine::SetProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar, float fLeft, float fTop, float fWidth, float fHeight) + { + matProj = GFX3D::Math::Mat_MakeProjection(fFovDegrees, fAspectRatio, fNear, fFar); + fViewX = fLeft; + fViewY = fTop; + fViewW = fWidth; + fViewH = fHeight; + } + + void GFX3D::PipeLine::SetCamera(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &lookat, olc::GFX3D::vec3d &up) + { + matView = GFX3D::Math::Mat_PointAt(pos, lookat, up); + matView = GFX3D::Math::Mat_QuickInverse(matView); + } + + void GFX3D::PipeLine::SetTransform(olc::GFX3D::mat4x4 &transform) + { + matWorld = transform; + } + + void GFX3D::PipeLine::SetTexture(olc::Sprite *texture) + { + sprTexture = texture; + } + + void GFX3D::PipeLine::SetLightSource(olc::GFX3D::vec3d &pos, olc::GFX3D::vec3d &dir, olc::Pixel &col) + { + + } + + uint32_t GFX3D::PipeLine::Render(std::vector &triangles, uint32_t flags) + { + // Calculate Transformation Matrix + mat4x4 matWorldView = Math::Mat_MultiplyMatrix(matWorld, matView); + //matWorldViewProj = Math::Mat_MultiplyMatrix(matWorldView, matProj); + + // Store triangles for rastering later + std::vector vecTrianglesToRaster; + + int nTriangleDrawnCount = 0; + + // Process Triangles + for (auto &tri : triangles) + { + GFX3D::triangle triTransformed; + + // Just copy through texture coordinates + triTransformed.t[0] = { tri.t[0].x, tri.t[0].y, tri.t[0].z }; + triTransformed.t[1] = { tri.t[1].x, tri.t[1].y, tri.t[1].z }; + triTransformed.t[2] = { tri.t[2].x, tri.t[2].y, tri.t[2].z }; // Think! + + // Transform Triangle from object into projected space + triTransformed.p[0] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[0]); + triTransformed.p[1] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[1]); + triTransformed.p[2] = GFX3D::Math::Mat_MultiplyVector(matWorldView, tri.p[2]); + + // Calculate Triangle Normal in WorldView Space + GFX3D::vec3d normal, line1, line2; + line1 = GFX3D::Math::Vec_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = GFX3D::Math::Vec_Sub(triTransformed.p[2], triTransformed.p[0]); + normal = GFX3D::Math::Vec_CrossProduct(line1, line2); + normal = GFX3D::Math::Vec_Normalise(normal); + + // Cull triangles that face away from viewer + if (flags & RENDER_CULL_CW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) > 0.0f) continue; + if (flags & RENDER_CULL_CCW && GFX3D::Math::Vec_DotProduct(normal, triTransformed.p[0]) < 0.0f) continue; + + // If Lighting, calculate shading + triTransformed.col = olc::WHITE; + + // Clip triangle against near plane + int nClippedTriangles = 0; + triangle clipped[2]; + nClippedTriangles = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triTransformed, clipped[0], clipped[1]); + + // This may yield two new triangles + for (int n = 0; n < nClippedTriangles; n++) + { + triangle triProjected = clipped[n]; + + // Project new triangle + triProjected.p[0] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = GFX3D::Math::Mat_MultiplyVector(matProj, clipped[n].p[2]); + + // Apply Projection to Verts + triProjected.p[0].x = triProjected.p[0].x / triProjected.p[0].w; + triProjected.p[1].x = triProjected.p[1].x / triProjected.p[1].w; + triProjected.p[2].x = triProjected.p[2].x / triProjected.p[2].w; + + triProjected.p[0].y = triProjected.p[0].y / triProjected.p[0].w; + triProjected.p[1].y = triProjected.p[1].y / triProjected.p[1].w; + triProjected.p[2].y = triProjected.p[2].y / triProjected.p[2].w; + + triProjected.p[0].z = triProjected.p[0].z / triProjected.p[0].w; + triProjected.p[1].z = triProjected.p[1].z / triProjected.p[1].w; + triProjected.p[2].z = triProjected.p[2].z / triProjected.p[2].w; + + // Apply Projection to Tex coords + triProjected.t[0].x = triProjected.t[0].x / triProjected.p[0].w; + triProjected.t[1].x = triProjected.t[1].x / triProjected.p[1].w; + triProjected.t[2].x = triProjected.t[2].x / triProjected.p[2].w; + + triProjected.t[0].y = triProjected.t[0].y / triProjected.p[0].w; + triProjected.t[1].y = triProjected.t[1].y / triProjected.p[1].w; + triProjected.t[2].y = triProjected.t[2].y / triProjected.p[2].w; + + triProjected.t[0].z = 1.0f / triProjected.p[0].w; + triProjected.t[1].z = 1.0f / triProjected.p[1].w; + triProjected.t[2].z = 1.0f / triProjected.p[2].w; + + // Clip against viewport in screen space + // Clip triangles against all four screen edges, this could yield + // a bunch of triangles, so create a queue that we traverse to + // ensure we only test new triangles generated against planes + triangle sclipped[2]; + std::list listTriangles; + + + // Add initial triangle + listTriangles.push_back(triProjected); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 1: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ 0.0f, +1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 2: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + case 3: nTrisToAdd = GFX3D::Math::Triangle_ClipAgainstPlane({ +1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, sclipped[0], sclipped[1]); break; + } + + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(sclipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto &triRaster : listTriangles) + { + // Scale to viewport + /*triRaster.p[0].x *= -1.0f; + triRaster.p[1].x *= -1.0f; + triRaster.p[2].x *= -1.0f; + triRaster.p[0].y *= -1.0f; + triRaster.p[1].y *= -1.0f; + triRaster.p[2].y *= -1.0f;*/ + vec3d vOffsetView = { 1,1,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + triRaster.p[0].x *= 0.5f * fViewW; + triRaster.p[0].y *= 0.5f * fViewH; + triRaster.p[1].x *= 0.5f * fViewW; + triRaster.p[1].y *= 0.5f * fViewH; + triRaster.p[2].x *= 0.5f * fViewW; + triRaster.p[2].y *= 0.5f * fViewH; + vOffsetView = { fViewX,fViewY,0 }; + triRaster.p[0] = Math::Vec_Add(triRaster.p[0], vOffsetView); + triRaster.p[1] = Math::Vec_Add(triRaster.p[1], vOffsetView); + triRaster.p[2] = Math::Vec_Add(triRaster.p[2], vOffsetView); + + // For now, just draw triangle + + if (flags & RENDER_TEXTURED) + { + TexturedTriangle( + triRaster.p[0].x, triRaster.p[0].y, triRaster.t[0].x, triRaster.t[0].y, triRaster.t[0].z, + triRaster.p[1].x, triRaster.p[1].y, triRaster.t[1].x, triRaster.t[1].y, triRaster.t[1].z, + triRaster.p[2].x, triRaster.p[2].y, triRaster.t[2].x, triRaster.t[2].y, triRaster.t[2].z, + sprTexture); + } + + if (flags & RENDER_WIRE) + { + DrawTriangleWire(triRaster, olc::RED); + } + + if (flags & RENDER_FLAT) + { + DrawTriangleFlat(triRaster); + } + + nTriangleDrawnCount++; + } + } + } + + return nTriangleDrawnCount; + } +} + +#endif \ No newline at end of file diff --git a/Videos/olcPGEX_Sound.h b/Videos/olcPGEX_Sound.h new file mode 100644 index 0000000..41e47c3 --- /dev/null +++ b/Videos/olcPGEX_Sound.h @@ -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 +#include +#include + +#include +#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 +#endif + +#ifdef USE_OPENAL +#include +#include +#include +#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 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 func); + static void SetUserFilterFunction(std::function 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 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 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 m_bAudioThreadActive; + static std::atomic m_fGlobalTime; + static std::function funcUserSynth; + static std::function 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 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 func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function 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 SOUND::m_bAudioThreadActive{ false }; + std::atomic SOUND::m_fGlobalTime{ 0.0f }; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function 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 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 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 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 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(¶ms); + snd_pcm_hw_params_any(m_pPCM, params); + + // Set other parameters + snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); + snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); + snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0); + snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0); + + // Save these parameters + rc = snd_pcm_hw_params(m_pPCM, params); + if (rc < 0) + return DestroyAudio(); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + // Unsure if really needed, helped prevent underrun on my setup + snd_pcm_start(m_pPCM); + for (unsigned int i = 0; i < nBlocks; i++) + rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512); + + snd_pcm_start(m_pPCM); + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + m_AudioThread.join(); + snd_pcm_drain(m_pPCM); + snd_pcm_close(m_pPCM); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + short nNewSample = 0; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + snd_pcm_uframes_t nLeft = m_nBlockSamples; + short *pBlockPos = m_pBlockMemory; + while (nLeft > 0) + { + int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); + if (rc > 0) + { + pBlockPos += rc * m_nChannels; + nLeft -= rc; + } + if (rc == -EAGAIN) continue; + if (rc == -EPIPE) // an underrun occured, prepare the device for more data + snd_pcm_prepare(m_pPCM); + } + } + } + + snd_pcm_t* SOUND::m_pPCM = nullptr; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockSamples = 0; + short* SOUND::m_pBlockMemory = nullptr; +} + +#elif defined(USE_OPENAL) + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; + + // Open the device and create the context + m_pDevice = alcOpenDevice(NULL); + if (m_pDevice) + { + m_pContext = alcCreateContext(m_pDevice, NULL); + alcMakeContextCurrent(m_pContext); + } + else + return DestroyAudio(); + + // Allocate memory for sound data + alGetError(); + m_pBuffers = new ALuint[m_nBlockCount]; + alGenBuffers(m_nBlockCount, m_pBuffers); + alGenSources(1, &m_nSource); + + for (unsigned int i = 0; i < m_nBlockCount; i++) + m_qAvailableBuffers.push(m_pBuffers[i]); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + m_AudioThread.join(); + + alDeleteBuffers(m_nBlockCount, m_pBuffers); + delete[] m_pBuffers; + alDeleteSources(1, &m_nSource); + + alcMakeContextCurrent(NULL); + alcDestroyContext(m_pContext); + alcCloseDevice(m_pDevice); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + std::vector 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 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 \ No newline at end of file diff --git a/Videos/zombie.png b/Videos/zombie.png new file mode 100644 index 0000000..8c2e502 Binary files /dev/null and b/Videos/zombie.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_001.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_001.png new file mode 100644 index 0000000..45a766b Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_001.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_002.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_002.png new file mode 100644 index 0000000..070c7d8 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_002.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_003.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_003.png new file mode 100644 index 0000000..ac611d8 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_003.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_004.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_004.png new file mode 100644 index 0000000..7ddcf7c Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_004.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_005.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_005.png new file mode 100644 index 0000000..04e462a Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_005.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_01_006.png b/WikiPics/BreakOutTutorialPics/Tutorial_01_006.png new file mode 100644 index 0000000..96fb452 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_01_006.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_02_001.gif b/WikiPics/BreakOutTutorialPics/Tutorial_02_001.gif new file mode 100644 index 0000000..14c1631 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_02_001.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_02_002.gif b/WikiPics/BreakOutTutorialPics/Tutorial_02_002.gif new file mode 100644 index 0000000..fc9f434 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_02_002.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_02_003.gif b/WikiPics/BreakOutTutorialPics/Tutorial_02_003.gif new file mode 100644 index 0000000..25f4d2c Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_02_003.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_02_004.gif b/WikiPics/BreakOutTutorialPics/Tutorial_02_004.gif new file mode 100644 index 0000000..4265130 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_02_004.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_03_001.gif b/WikiPics/BreakOutTutorialPics/Tutorial_03_001.gif new file mode 100644 index 0000000..a7db763 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_03_001.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_03_002.gif b/WikiPics/BreakOutTutorialPics/Tutorial_03_002.gif new file mode 100644 index 0000000..d07346f Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_03_002.gif differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_04_001.png b/WikiPics/BreakOutTutorialPics/Tutorial_04_001.png new file mode 100644 index 0000000..3919e1c Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_04_001.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_04_002.png b/WikiPics/BreakOutTutorialPics/Tutorial_04_002.png new file mode 100644 index 0000000..16f3bb7 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_04_002.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_04_003.png b/WikiPics/BreakOutTutorialPics/Tutorial_04_003.png new file mode 100644 index 0000000..4093e02 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_04_003.png differ diff --git a/WikiPics/BreakOutTutorialPics/Tutorial_04_004.png b/WikiPics/BreakOutTutorialPics/Tutorial_04_004.png new file mode 100644 index 0000000..1e60a8b Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/Tutorial_04_004.png differ diff --git a/WikiPics/BreakOutTutorialPics/tut_tile.png b/WikiPics/BreakOutTutorialPics/tut_tile.png new file mode 100644 index 0000000..359630a Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/tut_tile.png differ diff --git a/WikiPics/BreakOutTutorialPics/tut_tiles.png b/WikiPics/BreakOutTutorialPics/tut_tiles.png new file mode 100644 index 0000000..8604c45 Binary files /dev/null and b/WikiPics/BreakOutTutorialPics/tut_tiles.png differ diff --git a/WikiPics/Drawing1.png b/WikiPics/Drawing1.png new file mode 100644 index 0000000..098a081 Binary files /dev/null and b/WikiPics/Drawing1.png differ diff --git a/WikiPics/Drawing10.png b/WikiPics/Drawing10.png new file mode 100644 index 0000000..5ec89da Binary files /dev/null and b/WikiPics/Drawing10.png differ diff --git a/WikiPics/Drawing11.png b/WikiPics/Drawing11.png new file mode 100644 index 0000000..eb33623 Binary files /dev/null and b/WikiPics/Drawing11.png differ diff --git a/WikiPics/Drawing12.png b/WikiPics/Drawing12.png new file mode 100644 index 0000000..ee643cf Binary files /dev/null and b/WikiPics/Drawing12.png differ diff --git a/WikiPics/Drawing13.png b/WikiPics/Drawing13.png new file mode 100644 index 0000000..5521b5b Binary files /dev/null and b/WikiPics/Drawing13.png differ diff --git a/WikiPics/Drawing2.png b/WikiPics/Drawing2.png new file mode 100644 index 0000000..8c6252c Binary files /dev/null and b/WikiPics/Drawing2.png differ diff --git a/WikiPics/Drawing3.png b/WikiPics/Drawing3.png new file mode 100644 index 0000000..f3bb0cd Binary files /dev/null and b/WikiPics/Drawing3.png differ diff --git a/WikiPics/Drawing4.png b/WikiPics/Drawing4.png new file mode 100644 index 0000000..0fccbd6 Binary files /dev/null and b/WikiPics/Drawing4.png differ diff --git a/WikiPics/Drawing5.png b/WikiPics/Drawing5.png new file mode 100644 index 0000000..72728fc Binary files /dev/null and b/WikiPics/Drawing5.png differ diff --git a/WikiPics/Drawing6.png b/WikiPics/Drawing6.png new file mode 100644 index 0000000..f86e413 Binary files /dev/null and b/WikiPics/Drawing6.png differ diff --git a/WikiPics/Drawing7.png b/WikiPics/Drawing7.png new file mode 100644 index 0000000..496e612 Binary files /dev/null and b/WikiPics/Drawing7.png differ diff --git a/WikiPics/Drawing8.png b/WikiPics/Drawing8.png new file mode 100644 index 0000000..e962fea Binary files /dev/null and b/WikiPics/Drawing8.png differ diff --git a/WikiPics/Drawing9.png b/WikiPics/Drawing9.png new file mode 100644 index 0000000..ea9e7d2 Binary files /dev/null and b/WikiPics/Drawing9.png differ diff --git a/WikiPics/Stub.txt b/WikiPics/Stub.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/WikiPics/Stub.txt @@ -0,0 +1 @@ + diff --git a/olcExampleProgram.cpp b/olcExampleProgram.cpp new file mode 100644 index 0000000..c9f3588 --- /dev/null +++ b/olcExampleProgram.cpp @@ -0,0 +1,37 @@ +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } +}; + + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + + return 0; +} diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index 39c9085..948941f 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -2,245 +2,309 @@ olcPixelGameEngine.h +-------------------------------------------------------------+ - | Unofficial Pixel Game Engine v1.13 | - | "Like the command prompt console one, but not..." - javidx9 | + | OneLoneCoder Pixel Game Engine v2.05 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + License (OLC-3) ~~~~~~~~~~~~~~~ - Copyright 2018 - 2019 OneLoneCoder.com + Copyright 2018 - 2020 OneLoneCoder.com - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: + 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. + 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. + 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. + 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 + 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 + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + Community: https://community.onelonecoder.com - 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. - Compiling in Linux ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: - g++ -o output YourSource.cpp -lX11 -lGL -lpthread -lpng - vblank_mode=0 ./output + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17 + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64. + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + Add these libraries to "Linker Options": + user32 gdi32 opengl32 gdiplus Shlwapi stdc++fs + + Set these compiler options: -std=c++17 + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Raspberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! Thanks ~~~~~~ - I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, + I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, IProgramInCPP JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice - Ralakus, Gorbit99, raoul & MagetzUb for advice, ideas and testing, and I'd like - to extend my appreciation to the 23K YouTube followers and 1.5K Discord server - members who give me the motivation to keep going with all this :D + Ralakus, Gorbit99, raoul, joshinils, benedani, Moros1138, SaladinAkara & MagetzUb + for advice, ideas and testing, and I'd like to extend my appreciation to the + 144K YouTube followers, 70+ Patreons and 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 + Gorbit99...........Bastion, Ori & The Blind Forest, Terraria + Marti Morta........Gris + Danicron...........Terraria + SaladinAkara.......Aseprite Special thanks to my Patreons too - I wont name you on here, but I've certainly enjoyed my tea and flapjacks :D Author - ~~~~~~ - David Barr, aka javidx9, �OneLoneCoder 2018, 2019 - Ben (plane000)#8618 + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 + + 2.01: Made renderer and platform static for multifile projects + 2.02: Added Decal destructor, optimised Pixel constructor + 2.03: Added FreeBSD flags, Added DrawStringDecal() + 2.04: Windows Full-Screen bug fixed + 2.05: Added DrawPartialWarpedDecal(), Added DrawPartialRotatedDecal() +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + + +// O------------------------------------------------------------------------------O +// | Example "Hello World" Program (main.cpp) | +// O------------------------------------------------------------------------------O +/* + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Override base class with your custom functionality +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + // Name you application + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand()% 256)); + return true; + } +}; + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} + */ #ifndef OLC_PGE_DEF #define OLC_PGE_DEF -#ifdef _WIN32 - // Link to libraries -#ifndef __MINGW32__ - #pragma comment(lib, "user32.lib") // Visual Studio Only - #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add - #pragma comment(lib, "opengl32.lib") // these libs to your linker input - #pragma comment(lib, "gdiplus.lib") -#else - // In Code::Blocks, Select C++14 in your build options, and add the - // following libs to your linker: user32 gdi32 opengl32 gdiplus -#endif - // Include WinAPI - #include - #include - - // OpenGL Extension - #include - typedef BOOL(WINAPI wglSwapInterval_t) (int interval); - static wglSwapInterval_t *wglSwapInterval; -#else - #include - #include - #include - #include - #include - typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); - static glSwapInterval_t *glSwapIntervalEXT; -#endif - - -// Standard includes +// O------------------------------------------------------------------------------O +// | STANDARD INCLUDES | +// O------------------------------------------------------------------------------O #include #include #include #include #include +#include #include #include #include #include #include -#include #include #include #include +#include +#include +#include -#undef min -#undef max +// O------------------------------------------------------------------------------O +// | COMPILER CONFIGURATION ODDITIES | +// O------------------------------------------------------------------------------O +#define USE_EXPERIMENTAL_FS -class Rect; +#if defined(_WIN32) + #if _MSC_VER >= 1920 && _MSVC_LANG >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif -namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +#if defined(__linux__) || defined(__MINGW32__) || defined(__EMSCRIPTEN__) || defined(__FreeBSD__) + #if __cplusplus >= 201703L + #undef USE_EXPERIMENTAL_FS + #endif +#endif + +#if defined(USE_EXPERIMENTAL_FS) + // C++14 + #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + #include + namespace _gfs = std::experimental::filesystem::v1; +#else + // C++17 + #include + namespace _gfs = std::filesystem; +#endif + +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define UNUSED(x) (void)(x) + + +#if !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) + #define OLC_GFX_OPENGL10 +#endif + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE DECLARATION | +// O------------------------------------------------------------------------------O +namespace olc { + class PixelGameEngine; + + // Pixel Game Engine Advanced Configuration + constexpr uint8_t nMouseButtons = 5; + constexpr uint8_t nDefaultAlpha = 0xFF; + constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); + enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; + + + + // O------------------------------------------------------------------------------O + // | olc::Pixel - Represents a 32-Bit RGBA colour | + // O------------------------------------------------------------------------------O struct Pixel { union { - uint32_t n = 0xFF000000; - struct - { - uint8_t r; uint8_t g; uint8_t b; uint8_t a; - }; + uint32_t n = nDefaultPixel; + struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; }; }; + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + Pixel(); - Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); Pixel(uint32_t p); - enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + bool operator==(const Pixel& p) const; + bool operator!=(const Pixel& p) const; }; - // Some constants for symbolic naming of Pixels + Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); + + + + // O------------------------------------------------------------------------------O + // | USEFUL CONSTANTS | + // O------------------------------------------------------------------------------O static const Pixel - WHITE(255, 255, 255), - GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), - RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), - YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), - GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), - CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), - BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), - MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), - BLACK(0, 0, 0), - BLANK(0, 0, 0, 0); - - enum rcode - { - FAIL = 0, - OK = 1, - NO_FILE = -1, - }; - - //============================================================= - - struct HWButton - { - bool bPressed = false; // Set once during the frame the event occurs - bool bReleased = false; // Set once during the frame the event occurs - bool bHeld = false; // Set tru for all frames between pressed and released events - }; - - //============================================================= - - class ResourcePack - { - public: - 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)); } - }; - - public: - olc::rcode AddToPack(std::string sFile); - - public: - olc::rcode SavePack(std::string sFile); - olc::rcode LoadPack(std::string sFile); - olc::rcode ClearPack(); - - public: - olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); - - private: - - std::map mapFiles; - }; - - //============================================================= - - // A bitmap-like structure that stores a 2D array of Pixels - class Sprite - { - public: - Sprite(); - Sprite(std::string sImageFile); - Sprite(std::string sImageFile, olc::ResourcePack *pack); - Sprite(int32_t w, int32_t h); - ~Sprite(); - - public: - olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); - olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); - olc::rcode SaveToPGESprFile(std::string sImageFile); - - public: - int32_t width = 0; - int32_t height = 0; - enum Mode { NORMAL, PERIODIC }; - - public: - void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); - Pixel GetPixel(int32_t x, int32_t y); - void SetPixel(int32_t x, int32_t y, Pixel p); - - Pixel Sample(float x, float y); - Pixel SampleBL(float u, float v); - Pixel* GetData(); - - private: - Pixel *pColData = nullptr; - Mode modeSample = Mode::NORMAL; - -#ifdef OLC_DBG_OVERDRAW - public: - static int nOverdrawCount; -#endif - - }; - - //============================================================= + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255),DARK_MAGENTA(128, 0, 128),VERY_DARK_MAGENTA(64, 0, 64), + WHITE(255, 255, 255),BLACK(0, 0, 0), BLANK(0, 0, 0, 0); enum Key { + NONE, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, @@ -248,27 +312,242 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, - NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD }; - //============================================================= + // O------------------------------------------------------------------------------O + // | olc::vX2d - A generic 2D vector type | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_VEC2D) + template + struct v2d_generic + { + T x = 0; + T y = 0; + inline v2d_generic() : x(0), y(0) { } + inline v2d_generic(T _x, T _y) : x(_x), y(_y) { } + inline v2d_generic(const v2d_generic& v) : x(v.x), y(v.y){ } + inline T mag() { return std::sqrt(x * x + y * y); } + inline T mag2() { return x * x + y * y; } + inline v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); } + inline v2d_generic perp() { return v2d_generic(-y, x); } + inline T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; } + inline T cross(const v2d_generic& rhs) { return this->x * rhs.y - this->y * rhs.x; } + inline v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y);} + inline v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y);} + inline v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + inline v2d_generic operator * (const v2d_generic& rhs) const { return v2d_generic(this->x * rhs.x, this->y * rhs.y);} + inline v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + inline v2d_generic operator / (const v2d_generic& rhs) const { return v2d_generic(this->x / rhs.x, this->y / rhs.y);} + inline v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + inline v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + 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 operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + inline operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + inline operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + // Note: joshinils has some good suggestions here, but they are complicated to implement at this moment, + // however they will appear in a future version of PGE + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) + { return v2d_generic((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); } + + typedef v2d_generic vi2d; + typedef v2d_generic vu2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; +#endif + + + + // O------------------------------------------------------------------------------O + // | olc::HWButton - Represents the state of a hardware button (mouse/key/joy) | + // O------------------------------------------------------------------------------O + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack - A virtual scrambled filesystem to pack your assets into | + // O------------------------------------------------------------------------------O + struct ResourceBuffer : public std::streambuf + { + ResourceBuffer(std::ifstream &ifs, uint32_t offset, uint32_t size); + std::vector vMemory; + }; + + class ResourcePack : public std::streambuf + { + public: + ResourcePack(); + ~ResourcePack(); + bool AddFile(const std::string& sFile); + bool LoadPack(const std::string& sFile, const std::string& sKey); + bool SavePack(const std::string& sFile, const std::string& sKey); + ResourceBuffer GetFileBuffer(const std::string& sFile); + bool Loaded(); + private: + struct sResourceFile { uint32_t nSize; uint32_t nOffset; }; + std::map mapFiles; + std::ifstream baseFile; + std::vector scramble(const std::vector& data, const std::string& key); + std::string makeposix(const std::string& path); + }; + + + + // O------------------------------------------------------------------------------O + // | olc::Sprite - An image represented by a 2D array of olc::Pixel | + // O------------------------------------------------------------------------------O + class Sprite + { + public: + Sprite(); + Sprite(const std::string& sImageFile, olc::ResourcePack *pack = nullptr); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(const std::string& sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(const std::string& sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + enum Flip { NONE = 0, HORIZ = 1, VERT = 2 }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + bool SetPixel(int32_t x, int32_t y, Pixel p); + Pixel GetPixel(const olc::vi2d& a); + bool SetPixel(const olc::vi2d& a, Pixel p); + Pixel Sample(float x, float y); + Pixel SampleBL(float u, float v); + Pixel* GetData(); + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + }; + + // O------------------------------------------------------------------------------O + // | olc::Decal - A GPU resident storage of an olc::Sprite | + // O------------------------------------------------------------------------------O + class Decal + { + public: + Decal(olc::Sprite* spr); + virtual ~Decal(); + void Update(); + + public: // But dont touch + int32_t id = -1; + olc::Sprite* sprite = nullptr; + olc::vf2d vUVScale = { 1.0f, 1.0f }; + }; + + // O------------------------------------------------------------------------------O + // | Auxilliary components internal to engine | + // O------------------------------------------------------------------------------O + + struct DecalInstance + { + olc::Decal* decal = nullptr; + olc::vf2d pos[4] = {{ 0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}}; + olc::vf2d uv[4] = {{ 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}}; + float w[4] = { 1, 1, 1, 1 }; + olc::Pixel tint; + }; + + struct LayerDesc + { + olc::vf2d vOffset = { 0, 0 }; + olc::vf2d vScale = { 1, 1 }; + bool bShow = false; + bool bUpdate = false; + olc::Sprite* pDrawTarget = nullptr; + uint32_t nResID = 0; + std::vector vecDecalInstance; + olc::Pixel tint = olc::WHITE; + std::function funcHook = nullptr; + }; + + class Renderer + { + public: + virtual void PrepareDevice() = 0; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) = 0; + virtual olc::rcode DestroyDevice() = 0; + virtual void DisplayFrame() = 0; + virtual void PrepareDrawing() = 0; + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0; + virtual void DrawDecalQuad(const olc::DecalInstance& decal) = 0; + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height) = 0; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual uint32_t DeleteTexture(const uint32_t id) = 0; + virtual void ApplyTexture(uint32_t id) = 0; + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) = 0; + virtual void ClearBuffer(olc::Pixel p, bool bDepth) = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class Platform + { + public: + virtual olc::rcode ApplicationStartUp() = 0; + virtual olc::rcode ApplicationCleanUp() = 0; + virtual olc::rcode ThreadStartUp() = 0; + virtual olc::rcode ThreadCleanUp() = 0; + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) = 0; + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) = 0; + virtual olc::rcode SetWindowTitle(const std::string& s) = 0; + virtual olc::rcode StartSystemEventLoop() = 0; + virtual olc::rcode HandleSystemEvent() = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + static std::unique_ptr renderer; + static std::unique_ptr platform; + static std::map mapKeys; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine - The main BASE class for your application | + // O------------------------------------------------------------------------------O class PixelGameEngine { public: PixelGameEngine(); - + virtual ~PixelGameEngine(); public: - olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h); - olc::rcode Start(); + olc::rcode Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, + bool full_screen = false, bool vsync = false); + olc::rcode Start(); - public: // Override Interfaces + public: // User Override Interfaces // Called once on application startup, use to load your resources virtual bool OnUserCreate(); // Called every frame, and provides you with a time per frame value virtual bool OnUserUpdate(float fElapsedTime); - // Called once on application termination, so you can be a clean coder + // Called once on application termination, so you can be one clean coder virtual bool OnUserDestroy(); public: // Hardware Interfaces @@ -282,23 +561,42 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace int32_t GetMouseX(); // Get Mouse Y coordinate in "pixel" space int32_t GetMouseY(); + // Get Mouse Wheel Delta + int32_t GetMouseWheel(); public: // Utility // Returns the width of the screen in "pixels" - int32_t ScreenWidth(); + const int32_t ScreenWidth(); // Returns the height of the screen in "pixels" - int32_t ScreenHeight(); + const int32_t ScreenHeight(); // Returns the width of the currently selected drawing target in "pixels" int32_t GetDrawTargetWidth(); // Returns the height of the currently selected drawing target in "pixels" int32_t GetDrawTargetHeight(); // Returns the currently active draw target - Sprite* GetDrawTarget(); - - public: // Draw Routines + olc::Sprite* GetDrawTarget(); + // Resize the primary screen sprite + void SetScreenSize(int w, int h); // Specify which Sprite should be the target of drawing functions, use nullptr // to specify the primary screen void SetDrawTarget(Sprite *target); + // Gets the current Frames Per Second + uint32_t GetFPS(); + + public: // CONFIGURATION ROUTINES + // Layer targeting functions + void SetDrawTarget(uint8_t layer); + void EnableLayer(uint8_t layer, bool b); + void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); + void SetLayerOffset(uint8_t layer, float x, float y); + void SetLayerScale(uint8_t layer, const olc::vf2d& scale); + void SetLayerScale(uint8_t layer, float x, float y); + void SetLayerTint(uint8_t layer, const olc::Pixel& tint); + void SetLayerCustomRenderFunction(uint8_t layer, std::function f); + + std::vector& GetLayers(); + uint32_t CreateLayer(); + // Change the pixel mode for different optimisations // olc::Pixel::NORMAL = No transparency // olc::Pixel::MASK = Transparent if alpha is < 255 @@ -310,118 +608,155 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace // Change the blend factor form between 0.0f to 1.0f; void SetPixelBlend(float fBlend); // Offset texels by sub-pixel amount (advanced, do not use) + [[deprecated]] void SetSubPixelOffset(float ox, float oy); + + public: // DRAWING ROUTINES // Draws a single Pixel - virtual void Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + bool Draw(const olc::vi2d& pos, Pixel p = olc::WHITE); // Draws a line from (x1,y1) to (x2,y2) - void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE); + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); // Draws a circle located at (x,y) with radius - void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); // Fills a circle located at (x,y) with radius void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE); // Draws a rectangle at (x,y) to (x+w,y+h) void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); - void DrawRect(Rect rect, Pixel p = olc::WHITE); + void DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); // Fills a rectangle at (x,y) to (x+w,y+h) void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); - void FillRect(Rect rect, Pixel p = olc::WHITE); + void FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); - // Draws an entire sprite at location (x,y) - void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + void FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Draws an entire sprite at well in my defencelocation (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawSprite(const olc::vi2d& pos, Sprite *sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); // Draws an area of a sprite at location (x,y), where the // selected area is (ox,oy) to (ox+w,oy+h) - void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vi2d& pos, Sprite *sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal *decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + + // Draws a single line of text - void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); // Clears entire draw target to Pixel void Clear(Pixel p); + // Clears the rendering back buffer + void ClearBuffer(Pixel p, bool bDepth = true); + public: // Branding std::string sAppName; private: // Inner mysterious workings - Sprite *pDefaultDrawTarget = nullptr; - Sprite *pDrawTarget = nullptr; - Pixel::Mode nPixelMode = Pixel::NORMAL; - float fBlendFactor = 1.0f; - uint32_t nScreenWidth = 256; - uint32_t nScreenHeight = 240; - uint32_t nPixelWidth = 4; - uint32_t nPixelHeight = 4; - int32_t nMousePosX = 0; - int32_t nMousePosY = 0; - float fPixelX = 1.0f; - float fPixelY = 1.0f; - float fSubPixelOffsetX = 0.0f; - float fSubPixelOffsetY = 0.0f; - bool bHasInputFocus = false; - bool bHasMouseFocus = false; - float fFrameTimer = 1.0f; - int nFrameCount = 0; - Sprite *fontSprite = nullptr; + Sprite* pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + olc::vi2d vScreenSize = { 256, 240 }; + olc::vf2d vInvScreenSize = { 1.0f / 256.0f, 1.0f / 240.0f }; + olc::vi2d vPixelSize = { 4, 4 }; + olc::vi2d vMousePos = { 0, 0 }; + int32_t nMouseWheelDelta = 0; + olc::vi2d vMousePosCache = { 0, 0 }; + int32_t nMouseWheelDeltaCache = 0; + olc::vi2d vWindowSize = { 0, 0 }; + olc::vi2d vViewPos = { 0, 0 }; + olc::vi2d vViewSize = { 0,0 }; + bool bFullScreen = false; + olc::vf2d vPixel = { 1.0f, 1.0f }; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite* fontSprite = nullptr; + Decal* fontDecal = nullptr; + Sprite* pDefaultDrawTarget = nullptr; + std::vector vLayers; + uint8_t nTargetLayer = 0; + uint32_t nLastFPS = 0; std::function funcPixelMode; + std::chrono::time_point m_tp1, m_tp2; - static std::map mapKeys; + // State of keyboard bool pKeyNewState[256]{ 0 }; bool pKeyOldState[256]{ 0 }; - HWButton pKeyboardState[256]; + HWButton pKeyboardState[256]{ 0 }; - bool pMouseNewState[5]{ 0 }; - bool pMouseOldState[5]{ 0 }; - HWButton pMouseState[5]; - -#ifdef _WIN32 - HDC glDeviceContext = nullptr; - HGLRC glRenderContext = nullptr; -#else - GLXContext glDeviceContext = nullptr; - GLXContext glRenderContext = nullptr; -#endif - GLuint glBuffer; + // State of mouse + bool pMouseNewState[nMouseButtons]{ 0 }; + bool pMouseOldState[nMouseButtons]{ 0 }; + HWButton pMouseState[nMouseButtons]{ 0 }; + // The main engine thread void EngineThread(); + // At the very end of this file, chooses which + // components to compile + void olc_ConfigureSystem(); + // If anything sets this flag to false, the engine // "should" shut down gracefully static std::atomic bAtomActive; - // Common initialisation functions + public: + // "Break In" Functions void olc_UpdateMouse(int32_t x, int32_t y); - bool olc_OpenGLCreate(); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); void olc_ConstructFontSheet(); + void olc_CoreUpdate(); + void olc_PrepareEngine(); + void olc_UpdateMouseState(int32_t button, bool state); + void olc_UpdateKeyState(int32_t key, bool state); + void olc_UpdateMouseFocus(bool state); + void olc_UpdateKeyFocus(bool state); + void olc_Terminate(); -#ifdef _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 - // Non-Windows specific window handling - Display* olc_Display = nullptr; - Window olc_WindowRoot; - Window olc_Window; - XVisualInfo* olc_VisualInfo; - Colormap olc_ColourMap; - XSetWindowAttributes olc_SetWindowAttribs; - Display* olc_WindowCreate(); -#endif - + // NOTE: Items Here are to be deprecated, I have left them in for now + // in case you are using them, but they will be removed. + // olc::vf2d vSubPixelOffset = { 0.0f, 0.0f }; }; + + // O------------------------------------------------------------------------------O + // | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension | + // O------------------------------------------------------------------------------O class PGEX { friend class olc::PixelGameEngine; protected: static PixelGameEngine* pge; }; - - //============================================================= } #endif // OLC_PGE_DEF @@ -438,15 +773,7 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace to including this file must have OLC_PGE_APPLICATION defined before it. This prevents the definitions being duplicated. - Consider the following project structure: - - Class1.h - Includes olcPixelGameEngine.h, overrides olc::PixelGameEngine - Class1.cpp - #define OLC_PGE_APPLICATION #include "Class1.h" - Class2.h - Includes Class1.h, which includes olcPixelGameEngine.h - Class2.cpp - #define OLC_PGE_APPLICATION #include "Class2.h" - main.cpp - Includes Class1.h and Class2.h - - If all else fails, create a file called "olcPixelGameEngine.cpp" with the following + If all else fails, create a file called "olcPixelGameEngine.cpp" with the following two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying about defining things. Dont forget to include that cpp file as part of your build! @@ -455,58 +782,51 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace */ + +// O------------------------------------------------------------------------------O +// | START OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O #ifdef OLC_PGE_APPLICATION #undef OLC_PGE_APPLICATION +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | +// | Note: The core implementation is platform independent | +// O------------------------------------------------------------------------------O namespace olc { + // O------------------------------------------------------------------------------O + // | olc::Pixel IMPLEMENTATION | + // O------------------------------------------------------------------------------O Pixel::Pixel() - { - r = 0; g = 0; b = 0; a = 255; - } + { r = 0; g = 0; b = 0; a = nDefaultAlpha; } Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) - { - r = red; g = green; b = blue; a = alpha; - } + { n = red | (green << 8) | (blue << 16) | (alpha << 24); } // Thanks jarekpelczar + Pixel::Pixel(uint32_t p) + { n = p; } + + bool Pixel::operator==(const Pixel& p) const + { return n == p.n; } + + bool Pixel::operator!=(const Pixel& p) const + { return n != p.n; } + + Pixel PixelF(float red, float green, float blue, float alpha) { - n = p; - } - - //========================================================== - - std::wstring ConvertS2W(std::string s) - { -#ifdef _WIN32 - 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); - std::wstring w(buffer); - delete[] buffer; - return w; -#else - return L"SVN FTW!"; -#endif + return Pixel(uint8_t(red * 255.0f), uint8_t(green * 255.0f), uint8_t(blue * 255.0f), uint8_t(alpha * 255.0f)); } + // O------------------------------------------------------------------------------O + // | olc::Sprite IMPLEMENTATION | + // O------------------------------------------------------------------------------O Sprite::Sprite() - { - pColData = nullptr; - width = 0; - height = 0; - } + { pColData = nullptr; width = 0; height = 0; } - Sprite::Sprite(std::string sImageFile) - { - LoadFromFile(sImageFile); - } - - Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) - { - LoadFromPGESprFile(sImageFile, pack); - } + Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack *pack) + { LoadFromFile(sImageFile, pack); } Sprite::Sprite(int32_t w, int32_t h) { @@ -518,20 +838,18 @@ namespace olc } Sprite::~Sprite() - { - if (pColData) delete pColData; - } + { if (pColData) delete[] pColData; } - olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + + olc::rcode Sprite::LoadFromPGESprFile(const std::string& sImageFile, olc::ResourcePack *pack) { if (pColData) delete[] pColData; - auto ReadData = [&](std::istream &is) { is.read((char*)&width, sizeof(int32_t)); is.read((char*)&height, sizeof(int32_t)); pColData = new Pixel[width * height]; - is.read((char*)pColData, width * height * sizeof(uint32_t)); + is.read((char*)pColData, (size_t)width * (size_t)height * sizeof(uint32_t)); }; // These are essentially Memory Surfaces represented by olc::Sprite @@ -550,16 +868,15 @@ namespace olc } else { - auto streamBuffer = pack->GetStreamBuffer(sImageFile); - std::istream is(&streamBuffer); + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); ReadData(is); + return olc::OK; } - - return olc::FAIL; - } + } - olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + olc::rcode Sprite::SaveToPGESprFile(const std::string& sImageFile) { if (pColData == nullptr) return olc::FAIL; @@ -569,7 +886,7 @@ namespace olc { ofs.write((char*)&width, sizeof(int32_t)); ofs.write((char*)&height, sizeof(int32_t)); - ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.write((char*)pColData, (size_t)width * (size_t)height * sizeof(uint32_t)); ofs.close(); return olc::OK; } @@ -577,122 +894,14 @@ namespace olc return olc::FAIL; } - olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) - { -#ifdef _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 - Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); - if (bmp == nullptr) - return olc::NO_FILE; - - width = bmp->GetWidth(); - height = bmp->GetHeight(); - pColData = new Pixel[width * height]; - - for(int x=0; xGetPixel(x, y, &c); - SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); - } - delete bmp; - return olc::OK; -#else - //////////////////////////////////////////////////////////////////////////// - // Use libpng, Thanks to Guillaume Cottenceau - // https://gist.github.com/niw/5963798 - png_structp png; - png_infop info; - - FILE *f = fopen(sImageFile.c_str(), "rb"); - if (!f) return olc::NO_FILE; - - png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png) goto fail_load; - - info = png_create_info_struct(png); - if (!info) goto fail_load; - - if (setjmp(png_jmpbuf(png))) goto fail_load; - - png_init_io(png, f); - png_read_info(png, info); - - png_byte color_type; - png_byte bit_depth; - png_bytep *row_pointers; - width = png_get_image_width(png, info); - height = png_get_image_height(png, info); - color_type = png_get_color_type(png, info); - bit_depth = png_get_bit_depth(png, info); - -#ifdef _DEBUG - std::cout << "Loading PNG: " << sImageFile << "\n"; - std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; -#endif - - if (bit_depth == 16) png_set_strip_16(png); - if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); - if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); - if (color_type == PNG_COLOR_TYPE_RGB || - color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_PALETTE) - png_set_filler(png, 0xFF, PNG_FILLER_AFTER); - if (color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); - - png_read_update_info(png, info); - row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); - for (int y = 0; y < height; y++) { - row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); - } - png_read_image(png, row_pointers); - //////////////////////////////////////////////////////////////////////////// - - // Create sprite array - pColData = new Pixel[width * height]; - - // Iterate through image rows, converting into sprite format - for (int y = 0; y < height; y++) - { - png_bytep row = row_pointers[y]; - for (int x = 0; x < width; x++) - { - png_bytep px = &(row[x * 4]); - SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); - } - } - - fclose(f); - return olc::OK; - - fail_load: - width = 0; - height = 0; - fclose(f); - pColData = nullptr; - return olc::FAIL; -#endif - } - void Sprite::SetSampleMode(olc::Sprite::Mode mode) - { - modeSample = mode; - } + { modeSample = mode; } + Pixel Sprite::GetPixel(const olc::vi2d& a) + { return GetPixel(a.x, a.y); } + + bool Sprite::SetPixel(const olc::vi2d& a, Pixel p) + { return SetPixel(a.x, a.y, p); } Pixel Sprite::GetPixel(int32_t x, int32_t y) { @@ -704,26 +913,26 @@ namespace olc return Pixel(0, 0, 0, 0); } else - { + { return pColData[abs(y%height)*width + abs(x%width)]; } } - void Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) { - -#ifdef OLC_DBG_OVERDRAW - nOverdrawCount++; -#endif - if (x >= 0 && x < width && y >= 0 && y < height) + { pColData[y*width + x] = p; + return true; + } + else + return false; } Pixel Sprite::Sample(float x, float y) { - int32_t sx = (int32_t)((x * (float)width) - 0.5f); - int32_t sy = (int32_t)((y * (float)height) - 0.5f); + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); return GetPixel(sx, sy); } @@ -731,17 +940,17 @@ namespace olc { u = u * width - 0.5f; v = v * height - 0.5f; - int x = (int)u; - int y = (int)v; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils float u_ratio = u - x; float v_ratio = v - y; float u_opposite = 1 - u_ratio; float v_opposite = 1 - v_ratio; - olc::Pixel p1 = GetPixel(x, y); - olc::Pixel p2 = GetPixel(x+1, y); - olc::Pixel p3 = GetPixel(x, y+1); - olc::Pixel p4 = GetPixel(x+1, y+1); + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); return olc::Pixel( (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), @@ -749,230 +958,356 @@ namespace olc (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); } - Pixel* Sprite::GetData() { return pColData; } + Pixel* Sprite::GetData() + { return pColData; } - //========================================================== - ResourcePack::ResourcePack() + // O------------------------------------------------------------------------------O + // | olc::Decal IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Decal::Decal(olc::Sprite* spr) { - + id = -1; + if (spr == nullptr) return; + sprite = spr; + id = renderer->CreateTexture(sprite->width, sprite->height); + Update(); } - ResourcePack::~ResourcePack() + void Decal::Update() { - ClearPack(); + if (sprite == nullptr) return; + vUVScale = { 1.0f / float(sprite->width), 1.0f / float(sprite->height) }; + renderer->ApplyTexture(id); + renderer->UpdateTexture(id, sprite); } - olc::rcode ResourcePack::AddToPack(std::string sFile) + Decal::~Decal() { - std::ifstream ifs(sFile, std::ifstream::binary); - if (!ifs.is_open()) return olc::FAIL; - - // Get File Size - std::streampos p = 0; - p = ifs.tellg(); - ifs.seekg(0, std::ios::end); - p = ifs.tellg() - p; - ifs.seekg(0, std::ios::beg); - - // Create entry - sEntry e; - e.data = nullptr; - e.nFileSize = (uint32_t)p; - - // Read file into memory - e.data = new uint8_t[(uint32_t)e.nFileSize]; - ifs.read((char*)e.data, e.nFileSize); - ifs.close(); - - // Add To Map - mapFiles[sFile] = e; - return olc::OK; - } - - olc::rcode ResourcePack::SavePack(std::string sFile) - { - std::ofstream ofs(sFile, std::ofstream::binary); - if (!ofs.is_open()) return olc::FAIL; - - // 1) Write Map - size_t nMapSize = mapFiles.size(); - ofs.write((char*)&nMapSize, sizeof(size_t)); - for (auto &e : mapFiles) + if (id != -1) { - size_t nPathSize = e.first.size(); - ofs.write((char*)&nPathSize, sizeof(size_t)); - ofs.write(e.first.c_str(), nPathSize); - ofs.write((char*)&e.second.nID, sizeof(uint32_t)); - ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); - ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + renderer->DeleteTexture(id); + id = -1; } - - // 2) Write Data - std::streampos offset = ofs.tellp(); - for (auto &e : mapFiles) - { - e.second.nFileOffset = (uint32_t)offset; - ofs.write((char*)e.second.data, e.second.nFileSize); - offset += e.second.nFileSize; - } - - // 3) Rewrite Map (it has been updated with offsets now) - ofs.seekp(std::ios::beg); - ofs.write((char*)&nMapSize, sizeof(size_t)); - for (auto &e : mapFiles) - { - size_t nPathSize = e.first.size(); - ofs.write((char*)&nPathSize, sizeof(size_t)); - ofs.write(e.first.c_str(), nPathSize); - ofs.write((char*)&e.second.nID, sizeof(uint32_t)); - ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); - ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); - } - ofs.close(); - - return olc::OK; } - olc::rcode ResourcePack::LoadPack(std::string sFile) - { - std::ifstream ifs(sFile, std::ifstream::binary); - if (!ifs.is_open()) return olc::FAIL; - // 1) Read Map - size_t nMapEntries; - ifs.read((char*)&nMapEntries, sizeof(size_t)); - for (size_t i = 0; i < nMapEntries; i++) + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack IMPLEMENTATION | + // O------------------------------------------------------------------------------O + + + //============================================================= + // Resource Packs - Allows you to store files in one large + // scrambled file - Thanks MaGetzUb for debugging a null char in std::stringstream bug + ResourceBuffer::ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size) + { + vMemory.resize(size); + ifs.seekg(offset); ifs.read(vMemory.data(), vMemory.size()); + setg(vMemory.data(), vMemory.data(), vMemory.data() + size); + } + + ResourcePack::ResourcePack() { } + ResourcePack::~ResourcePack() { baseFile.close(); } + + bool ResourcePack::AddFile(const std::string& sFile) + { + const std::string file = makeposix(sFile); + + if (_gfs::exists(file)) { - size_t nFilePathSize = 0; - ifs.read((char*)&nFilePathSize, sizeof(size_t)); + sResourceFile e; + e.nSize = (uint32_t)_gfs::file_size(file); + e.nOffset = 0; // Unknown at this stage + mapFiles[file] = e; + return true; + } + return false; + } + + bool ResourcePack::LoadPack(const std::string& sFile, const std::string& sKey) + { + // Open the resource file + baseFile.open(sFile, std::ifstream::binary); + if (!baseFile.is_open()) return false; + + // 1) Read Scrambled index + uint32_t nIndexSize = 0; + baseFile.read((char*)&nIndexSize, sizeof(uint32_t)); + + std::vector buffer(nIndexSize); + for (uint32_t j = 0; j < nIndexSize; j++) + buffer[j] = baseFile.get(); + + std::vector decoded = scramble(buffer, sKey); + size_t pos = 0; + auto read = [&decoded, &pos](char* dst, size_t size) { + memcpy((void*)dst, (const void*)(decoded.data() + pos), size); + pos += size; + }; + + auto get = [&read]() -> int { + char c; + read(&c, 1); + return c; + }; + + // 2) Read Map + uint32_t nMapEntries = 0; + read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + read((char*)&nFilePathSize, sizeof(uint32_t)); std::string sFileName(nFilePathSize, ' '); - for (size_t j = 0; j < nFilePathSize; j++) - sFileName[j] = ifs.get(); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = get(); - sEntry e; - e.data = nullptr; - ifs.read((char*)&e.nID, sizeof(uint32_t)); - ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); - ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + sResourceFile e; + read((char*)&e.nSize, sizeof(uint32_t)); + read((char*)&e.nOffset, sizeof(uint32_t)); mapFiles[sFileName] = e; } - // 2) Read Data - for (auto &e : mapFiles) + // Don't close base file! we will provide a stream + // pointer when the file is requested + return true; + } + + bool ResourcePack::SavePack(const std::string& sFile, const std::string& sKey) + { + // Create/Overwrite the resource file + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return false; + + // Iterate through map + uint32_t nIndexSize = 0; // Unknown for now + ofs.write((char*)&nIndexSize, sizeof(uint32_t)); + uint32_t nMapSize = uint32_t(mapFiles.size()); + ofs.write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) { - e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; - ifs.seekg(e.second.nFileOffset); - ifs.read((char*)e.second.data, e.second.nFileSize); - e.second._config(); + // Write the path of the file + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(uint32_t)); + ofs.write(e.first.c_str(), nPathSize); + + // Write the file entry properties + ofs.write((char*)&e.second.nSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nOffset, sizeof(uint32_t)); } - ifs.close(); - return olc::OK; - } - - olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) - { - return mapFiles[sFile]; - } - - olc::rcode ResourcePack::ClearPack() - { - for (auto &e : mapFiles) + // 2) Write the individual Data + std::streampos offset = ofs.tellp(); + nIndexSize = (uint32_t)offset; + for (auto& e : mapFiles) { - if (e.second.data != nullptr) - delete[] e.second.data; + // Store beginning of file offset within resource pack file + e.second.nOffset = (uint32_t)offset; + + // Load the file to be added + std::vector vBuffer(e.second.nSize); + std::ifstream i(e.first, std::ifstream::binary); + i.read((char*)vBuffer.data(), e.second.nSize); + i.close(); + + // Write the loaded file into resource pack file + ofs.write((char*)vBuffer.data(), e.second.nSize); + offset += e.second.nSize; } - mapFiles.clear(); - return olc::OK; + // 3) Scramble Index + std::vector stream; + auto write = [&stream](const char* data, size_t size) { + size_t sizeNow = stream.size(); + stream.resize(sizeNow + size); + memcpy(stream.data() + sizeNow, data, size); + }; + + // Iterate through map + write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + write((char*)&nPathSize, sizeof(uint32_t)); + write(e.first.c_str(), nPathSize); + + // Write the file entry properties + write((char*)&e.second.nSize, sizeof(uint32_t)); + write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + std::vector sIndexString = scramble(stream, sKey); + uint32_t nIndexStringLen = uint32_t(sIndexString.size()); + // 4) Rewrite Map (it has been updated with offsets now) + // at start of file + ofs.seekp(0, std::ios::beg); + ofs.write((char*)&nIndexStringLen, sizeof(uint32_t)); + ofs.write(sIndexString.data(), nIndexStringLen); + ofs.close(); + return true; } - //========================================================== + ResourceBuffer ResourcePack::GetFileBuffer(const std::string& sFile) + { return ResourceBuffer(baseFile, mapFiles[sFile].nOffset, mapFiles[sFile].nSize); } + bool ResourcePack::Loaded() + { return baseFile.is_open(); } + + std::vector ResourcePack::scramble(const std::vector& data, const std::string& key) + { + if (key.empty()) return data; + std::vector o; + size_t c = 0; + for (auto s : data) o.push_back(s ^ key[(c++) % key.size()]); + return o; + }; + + std::string ResourcePack::makeposix(const std::string& path) + { + std::string o; + for (auto s : path) o += std::string(1, s == '\\' ? '/' : s); + return o; + }; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine IMPLEMENTATION | + // O------------------------------------------------------------------------------O PixelGameEngine::PixelGameEngine() { sAppName = "Undefined"; olc::PGEX::pge = this; + + // Bring in relevant Platform & Rendering systems depending + // on compiler parameters + olc_ConfigureSystem(); } - olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h) + PixelGameEngine::~PixelGameEngine() + {} + + + olc::rcode PixelGameEngine::Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, bool full_screen, bool vsync) { - nScreenWidth = screen_w; - nScreenHeight = screen_h; - nPixelWidth = pixel_w; - nPixelHeight = pixel_h; + vScreenSize = { screen_w, screen_h }; + vInvScreenSize = { 1.0f / float(screen_w), 1.0f / float(screen_h) }; + vPixelSize = { pixel_w, pixel_h }; + vWindowSize = vScreenSize * vPixelSize; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + vPixel = 2.0f / vScreenSize; - fPixelX = 2.0f / (float)(nScreenWidth); - fPixelY = 2.0f / (float)(nScreenHeight); - - if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + if (vPixelSize.x <= 0 || vPixelSize.y <= 0 || vScreenSize.x <= 0 || vScreenSize.y <= 0) return olc::FAIL; + -#ifdef _WIN32 -#ifdef UNICODE -#ifndef __MINGW32__ - wsAppName = ConvertS2W(sAppName); -#endif -#endif -#endif - // Load the default font sheet - olc_ConstructFontSheet(); - - // Create a sprite that represents the primary drawing target - pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); - SetDrawTarget(nullptr); return olc::OK; } + + void PixelGameEngine::SetScreenSize(int w, int h) + { + vScreenSize = { w, h }; + for (auto& layer : vLayers) + { + delete layer.pDrawTarget; // Erase existing layer sprites + layer.pDrawTarget = new Sprite(vScreenSize.x, vScreenSize.y); + layer.bUpdate = true; + } + SetDrawTarget(nullptr); + + renderer->ClearBuffer(olc::BLACK, true); + renderer->DisplayFrame(); + renderer->ClearBuffer(olc::BLACK, true); + renderer->UpdateViewport(vViewPos, vViewSize); + } + +#if !defined(PGE_USE_CUSTOM_START) olc::rcode PixelGameEngine::Start() { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + // Construct the window - if (!olc_WindowCreate()) - return olc::FAIL; + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); - // 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 - // Handle Windows Message Loop - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -#endif + // Some implementations may form an event loop here + platform->StartSystemEventLoop(); // Wait for thread to be exited t.join(); + + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + return olc::OK; } +#endif void PixelGameEngine::SetDrawTarget(Sprite *target) { if (target) + { pDrawTarget = target; + } else - pDrawTarget = pDefaultDrawTarget; + { + nTargetLayer = 0; + pDrawTarget = vLayers[0].pDrawTarget; + } + } + + void PixelGameEngine::SetDrawTarget(uint8_t layer) + { + if (layer < vLayers.size()) + { + pDrawTarget = vLayers[layer].pDrawTarget; + vLayers[layer].bUpdate = true; + nTargetLayer = layer; + } + } + + void PixelGameEngine::EnableLayer(uint8_t layer, bool b) + { if(layer < vLayers.size()) vLayers[layer].bShow = b; } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, const olc::vf2d& offset) + { SetLayerOffset(layer, offset.x, offset.y); } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vOffset = { x, y }; } + + void PixelGameEngine::SetLayerScale(uint8_t layer, const olc::vf2d& scale) + { SetLayerScale(layer, scale.x, scale.y); } + + void PixelGameEngine::SetLayerScale(uint8_t layer, float x, float y) + { if (layer < vLayers.size()) vLayers[layer].vScale = { x, y }; } + + void PixelGameEngine::SetLayerTint(uint8_t layer, const olc::Pixel& tint) + { if (layer < vLayers.size()) vLayers[layer].tint = tint; } + + void PixelGameEngine::SetLayerCustomRenderFunction(uint8_t layer, std::function f) + { if (layer < vLayers.size()) vLayers[layer].funcHook = f; } + + std::vector& PixelGameEngine::GetLayers() + { return vLayers; } + + uint32_t PixelGameEngine::CreateLayer() + { + LayerDesc ld; + ld.pDrawTarget = new olc::Sprite(vScreenSize.x, vScreenSize.y); + ld.nResID = renderer->CreateTexture(vScreenSize.x, vScreenSize.y); + renderer->UpdateTexture(ld.nResID, ld.pDrawTarget); + vLayers.push_back(ld); + return uint32_t(vLayers.size()) - 1; } Sprite* PixelGameEngine::GetDrawTarget() - { - return pDrawTarget; - } + { return pDrawTarget; } int32_t PixelGameEngine::GetDrawTargetWidth() { @@ -990,57 +1325,50 @@ namespace olc return 0; } + uint32_t PixelGameEngine::GetFPS() + { return nLastFPS; } + bool PixelGameEngine::IsFocused() - { - return bHasInputFocus; - } + { return bHasInputFocus; } HWButton PixelGameEngine::GetKey(Key k) - { - return pKeyboardState[k]; - } + { return pKeyboardState[k]; } HWButton PixelGameEngine::GetMouse(uint32_t b) - { - return pMouseState[b]; - } + { return pMouseState[b]; } int32_t PixelGameEngine::GetMouseX() - { - return nMousePosX; - } + { return vMousePos.x; } int32_t PixelGameEngine::GetMouseY() - { - return nMousePosY; - } + { return vMousePos.y; } - int32_t PixelGameEngine::ScreenWidth() - { - return nScreenWidth; - } + int32_t PixelGameEngine::GetMouseWheel() + { return nMouseWheelDelta; } - int32_t PixelGameEngine::ScreenHeight() - { - return nScreenHeight; - } + const int32_t PixelGameEngine::ScreenWidth() + { return vScreenSize.x; } - void PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) - { - if (!pDrawTarget) return; + const int32_t PixelGameEngine::ScreenHeight() + { return vScreenSize.y; } + bool PixelGameEngine::Draw(const olc::vi2d& pos, Pixel p) + { return Draw(pos.x, pos.y, p); } + + // This is it, the critical function that plots a pixel + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; if (nPixelMode == Pixel::NORMAL) { - pDrawTarget->SetPixel(x, y, p); - return; + return pDrawTarget->SetPixel(x, y, p); } if (nPixelMode == Pixel::MASK) { if(p.a == 255) - pDrawTarget->SetPixel(x, y, p); - return; + return pDrawTarget->SetPixel(x, y, p); } if (nPixelMode == Pixel::ALPHA) @@ -1051,42 +1379,45 @@ namespace olc float r = a * (float)p.r + c * (float)d.r; float g = a * (float)p.g + c * (float)d.g; float b = a * (float)p.b + c * (float)d.b; - pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); - return; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b/*, (uint8_t)(p.a * fBlendFactor)*/)); } if (nPixelMode == Pixel::CUSTOM) { - pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); - return; + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); } + + return false; } void PixelGameEngine::SetSubPixelOffset(float ox, float oy) { - fSubPixelOffsetX = ox * fPixelX; - fSubPixelOffsetY = oy * fPixelY; + //vSubPixelOffset.x = ox * vPixel.x; + //vSubPixelOffset.y = oy * vPixel.y; } - void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p) + void PixelGameEngine::DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p, uint32_t pattern) + { DrawLine(pos1.x, pos1.y, pos2.x, pos2.y, p, pattern); } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) { int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; dx = x2 - x1; dy = y2 - y1; + auto rol = [&](void){ pattern = (pattern << 1) | (pattern >> 31); return pattern & 1; }; + // straight lines idea by gurkanctn if (dx == 0) // Line is vertical { if (y2 < y1) std::swap(y1, y2); - for (y = y1; y <= y2; y++) - Draw(x1, y, p); + for (y = y1; y <= y2; y++) if (rol()) Draw(x1, y, p); return; } if (dy == 0) // Line is horizontal { if (x2 < x1) std::swap(x1, x2); - for (x = x1; x <= x2; x++) - Draw(x, y1, p); + for (x = x1; x <= x2; x++) if (rol()) Draw(x, y1, p); return; } @@ -1096,15 +1427,11 @@ namespace olc if (dy1 <= dx1) { if (dx >= 0) - { - x = x1; y = y1; xe = x2; - } + { x = x1; y = y1; xe = x2; } else - { - x = x2; y = y2; xe = x1; - } + { x = x2; y = y2; xe = x1; } - Draw(x, y, p); + if (rol()) Draw(x, y, p); for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; px = px + 2 * (dy1 - dx1); } - Draw(x, y, p); + if (rol()) Draw(x, y, p); } } else { if (dy >= 0) - { - x = x1; y = y1; ye = y2; - } + { x = x1; y = y1; ye = y2; } else - { - x = x2; y = y2; ye = y1; - } + { x = x2; y = y2; ye = y1; } - Draw(x, y, p); + if (rol()) Draw(x, y, p); for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; py = py + 2 * (dx1 - dy1); } - Draw(x, y, p); + if (rol()) Draw(x, y, p); } } } - void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + void PixelGameEngine::DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p, uint8_t mask) + { DrawCircle(pos.x, pos.y, radius, p, mask);} + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) { int x0 = 0; int y0 = radius; @@ -1156,19 +1482,22 @@ namespace olc while (y0 >= x0) // only formulate 1/8 of circle { - Draw(x - x0, y - y0, p);//upper left left - Draw(x - y0, y - x0, p);//upper upper left - Draw(x + y0, y - x0, p);//upper upper right - Draw(x + x0, y - y0, p);//upper right right - Draw(x - x0, y + y0, p);//lower left left - Draw(x - y0, y + x0, p);//lower lower left - Draw(x + y0, y + x0, p);//lower lower right - Draw(x + x0, y + y0, p);//lower right right + if (mask & 0x01) Draw(x + x0, y - y0, p); + if (mask & 0x02) Draw(x + y0, y - x0, p); + if (mask & 0x04) Draw(x + y0, y + x0, p); + if (mask & 0x08) Draw(x + x0, y + y0, p); + if (mask & 0x10) Draw(x - x0, y + y0, p); + if (mask & 0x20) Draw(x - y0, y + x0, p); + if (mask & 0x40) Draw(x - y0, y - x0, p); + if (mask & 0x80) Draw(x - x0, y - y0, p); if (d < 0) d += 4 * x0++ + 6; else d += 4 * (x0++ - y0--) + 10; } } + void PixelGameEngine::FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p) + { FillCircle(pos.x, pos.y, radius, p); } + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) { // Taken from wikipedia @@ -1195,55 +1524,54 @@ namespace olc } } + void PixelGameEngine::DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { DrawRect(pos.x, pos.y, size.x, size.y, p); } + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) { - w--; h--; DrawLine(x, y, x+w, y, p); DrawLine(x+w, y, x+w, y+h, p); DrawLine(x+w, y+h, x, y+h, p); DrawLine(x, y+h, x, y, p); } - void PixelGameEngine::DrawRect(Rect rect, Pixel p) - { - DrawRect(rect.x, rect.y, rect.w -= 1, rect.h -= 1, p) - } - void PixelGameEngine::Clear(Pixel p) { int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); Pixel* m = GetDrawTarget()->GetData(); - for (int i = 0; i < pixels; i++) - m[i] = p; -#ifdef OLC_DBG_OVERDRAW - olc::Sprite::nOverdrawCount += pixels; -#endif + for (int i = 0; i < pixels; i++) m[i] = p; } + void PixelGameEngine::ClearBuffer(Pixel p, bool bDepth) + { + renderer->ClearBuffer(p, bDepth); + } + + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { FillRect(pos.x, pos.y, size.x, size.y, p); } + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) - { + { int32_t x2 = x + w; int32_t y2 = y + h; if (x < 0) x = 0; - if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (x >= (int32_t)GetDrawTargetWidth()) x = (int32_t)GetDrawTargetWidth(); if (y < 0) y = 0; - if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + if (y >= (int32_t)GetDrawTargetHeight()) y = (int32_t)GetDrawTargetHeight(); if (x2 < 0) x2 = 0; - if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (x2 >= (int32_t)GetDrawTargetWidth()) x2 = (int32_t)GetDrawTargetWidth(); if (y2 < 0) y2 = 0; - if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + if (y2 >= (int32_t)GetDrawTargetHeight()) y2 = (int32_t)GetDrawTargetHeight(); for (int i = x; i < x2; i++) for (int j = y; j < y2; j++) Draw(i, j, p); } - void PixelGameEngine::FillRect(Rect rect, Pixel p) - { - FillRect(rect.x, rect.y, rect.w -= 1, rect.h -= 1, p) - } + void PixelGameEngine::DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { DrawTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) { @@ -1252,10 +1580,12 @@ namespace olc DrawLine(x3, y3, x1, y1, p); } + void PixelGameEngine::FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { FillTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); } + // https://www.avrfreaks.net/sites/default/files/triangles.c void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) { - auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; int t1x, t2x, y, minx, maxx, t1xp, t2xp; @@ -1264,27 +1594,21 @@ namespace olc int signx1, signx2, dx1, dy1, dx2, dy2; int e1, e2; // Sort vertices - if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } - if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } - if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + if (y1>y2) {std::swap(y1, y2); std::swap(x1, x2); } + if (y1>y3) {std::swap(y1, y3); std::swap(x1, x3); } + if (y2>y3) {std::swap(y2, y3); std::swap(x2, x3); } t1x = t2x = x1; y = y1; // Starting points - dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } - else signx1 = 1; + dx1 = (int)(x2 - x1); + if (dx1<0) { dx1 = -dx1; signx1 = -1; } else signx1 = 1; dy1 = (int)(y2 - y1); - dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } - else signx2 = 1; + dx2 = (int)(x3 - x1); + if (dx2<0) { dx2 = -dx2; signx2 = -1; } else signx2 = 1; dy2 = (int)(y3 - y1); - if (dy1 > dx1) { // swap values - SWAP(dx1, dy1); - changed1 = true; - } - if (dy2 > dx2) { // swap values - SWAP(dy2, dx2); - changed2 = true; - } + if (dy1 > dx1) { std::swap(dx1, dy1); changed1 = true; } + if (dy2 > dx2) { std::swap(dy2, dx2); changed2 = true; } e2 = (int)(dx2 >> 1); // Flat top, just process the second half @@ -1321,8 +1645,10 @@ namespace olc else t2x += signx2; } next2: - if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; - if (maxxt1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values - SWAP(dy1, dx1); + std::swap(dy1, dx1); changed1 = true; } else changed1 = false; @@ -1378,8 +1704,10 @@ namespace olc } next4: - if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; - if (maxxt1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxxwidth - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = sprite->height - 1; fym = -1; } + if (scale > 1) { - for (int32_t i = 0; i < sprite->width; i++) - for (int32_t j = 0; j < sprite->height; j++) + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) for (uint32_t is = 0; is < scale; is++) for (uint32_t js = 0; js < scale; js++) - Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(fx, fy)); + } } else { - for (int32_t i = 0; i < sprite->width; i++) - for (int32_t j = 0; j < sprite->height; j++) - Draw(x + i, y + j, sprite->GetPixel(i, j)); + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx, fy)); + } } } - void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + void PixelGameEngine::DrawPartialSprite(const olc::vi2d& pos, Sprite *sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale, uint8_t flip) + { DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip); } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale, uint8_t flip) { if (sprite == nullptr) return; + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = w - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = h - 1; fym = -1; } + if (scale > 1) { - for (int32_t i = 0; i < w; i++) - for (int32_t j = 0; j < h; j++) + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) for (uint32_t is = 0; is < scale; is++) for (uint32_t js = 0; js < scale; js++) - Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(fx + ox, fy + oy)); + } } else { - for (int32_t i = 0; i < w; i++) - for (int32_t j = 0; j < h; j++) - Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx + ox, fy + oy)); + } } } - void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * source_size.x * vInvScreenSize.x) * scale.x, + vScreenSpacePos.y - (2.0f * source_size.y * vInvScreenSize.y) * scale.y + }; + + DecalInstance di; di.decal = decal; di.tint = tint; + + di.pos[0] = { vScreenSpacePos.x, vScreenSpacePos.y }; + di.pos[1] = { vScreenSpacePos.x, vScreenSpaceDim.y }; + di.pos[2] = { vScreenSpaceDim.x, vScreenSpaceDim.y }; + di.pos[3] = { vScreenSpaceDim.x, vScreenSpacePos.y }; + + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y }; + di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y }; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawDecal(const olc::vf2d& pos, olc::Decal *decal, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * (float(decal->sprite->width) * vInvScreenSize.x)) * scale.x, + vScreenSpacePos.y - (2.0f * (float(decal->sprite->height) * vInvScreenSize.y)) * scale.y + }; + + DecalInstance di; + di.decal = decal; + di.tint = tint; + di.pos[0] = { vScreenSpacePos.x, vScreenSpacePos.y }; + di.pos[1] = { vScreenSpacePos.x, vScreenSpaceDim.y }; + di.pos[2] = { vScreenSpaceDim.x, vScreenSpaceDim.y }; + di.pos[3] = { vScreenSpaceDim.x, vScreenSpacePos.y }; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.tint = tint; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, float(decal->sprite->height)) - center) * scale; + di.pos[2] = (olc::vf2d(float(decal->sprite->width), float(decal->sprite->height)) - center) * scale; + di.pos[3] = (olc::vf2d(float(decal->sprite->width), 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + } + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.tint = tint; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, source_size.y) - center) * scale; + di.pos[2] = (olc::vf2d(source_size.x, source_size.y) - center) * scale; + di.pos[3] = (olc::vf2d(source_size.x, 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + } + + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y }; + di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y }; + + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.tint = tint; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y }; + di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y }; + + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) + { + // Thanks Nathan Reed, a brilliant article explaining whats going on here + // http://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/ + DecalInstance di; + di.decal = decal; + di.tint = tint; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { DrawWarpedDecal(decal, pos.data(), tint); } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { DrawWarpedDecal(decal, &pos[0], tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); } + + void PixelGameEngine::DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x += 8.0f * scale.x; + } + } + } + + void PixelGameEngine::DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { DrawString(pos.x, pos.y, sText, col, scale); } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) { int32_t sx = 0; int32_t sy = 0; Pixel::Mode m = nPixelMode; - if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + // Thanks @tucna, spotted bug with col.ALPHA :P + if(col.a != 255) SetPixelMode(Pixel::ALPHA); else SetPixelMode(Pixel::MASK); for (auto c : sText) { @@ -1463,9 +2009,9 @@ namespace olc { for (uint32_t i = 0; i < 8; i++) for (uint32_t j = 0; j < 8; j++) - if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) Draw(x + sx + i, y + sy + j, col); - } + } sx += 8 * scale; } } @@ -1473,14 +2019,10 @@ namespace olc } void PixelGameEngine::SetPixelMode(Pixel::Mode m) - { - nPixelMode = m; - } + { nPixelMode = m; } Pixel::Mode PixelGameEngine::GetPixelMode() - { - return nPixelMode; - } + { return nPixelMode; } void PixelGameEngine::SetPixelMode(std::function pixelMode) { @@ -1498,243 +2040,225 @@ namespace olc // User must override these functions as required. I have not made // them abstract because I do need a default behaviour to occur if // they are not overwritten + bool PixelGameEngine::OnUserCreate() { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) - { return false; } + { UNUSED(fElapsedTime); return false; } + bool PixelGameEngine::OnUserDestroy() { return true; } ////////////////////////////////////////////////////////////////// + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = vScreenSize.x * vPixelSize.x; + int32_t wh = vScreenSize.y * vPixelSize.y; + float wasp = (float)ww / (float)wh; + + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / wasp); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * wasp); + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + vWindowSize = { x, y }; + olc_UpdateViewport(); + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { nMouseWheelDeltaCache += delta; } + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) { // Mouse coords come in screen space // But leave in pixel space - nMousePosX = x / (int32_t)nPixelWidth; - nMousePosY = y / (int32_t)nPixelHeight; - if (nMousePosX >= (int32_t)nScreenWidth) - nMousePosX = nScreenWidth - 1; - if (nMousePosY >= (int32_t)nScreenHeight) - nMousePosY = nScreenHeight - 1; - - if (nMousePosX < 0) - nMousePosX = 0; - if (nMousePosY < 0) - nMousePosY = 0; + // Full Screen mode may have a weird viewport we must clamp to + x -= vViewPos.x; + y -= vViewPos.y; + vMousePosCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vMousePosCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vMousePosCache.x >= (int32_t)vScreenSize.x) vMousePosCache.x = vScreenSize.x - 1; + if (vMousePosCache.y >= (int32_t)vScreenSize.y) vMousePosCache.y = vScreenSize.y - 1; + if (vMousePosCache.x < 0) vMousePosCache.x = 0; + if (vMousePosCache.y < 0) vMousePosCache.y = 0; } + void PixelGameEngine::olc_UpdateMouseState(int32_t button, bool state) + { pMouseNewState[button] = state; } + + void PixelGameEngine::olc_UpdateKeyState(int32_t key, bool state) + { pKeyNewState[key] = state; } + + void PixelGameEngine::olc_UpdateMouseFocus(bool state) + { bHasMouseFocus = state; } + + void PixelGameEngine::olc_UpdateKeyFocus(bool state) + { bHasInputFocus = state; } + + void PixelGameEngine::olc_Terminate() + { bAtomActive = false; } + void PixelGameEngine::EngineThread() { - // Start OpenGL, the context is owned by the game thread - olc_OpenGLCreate(); - - // Create Screen Texture - disable filtering - glEnable(GL_TEXTURE_2D); - glGenTextures(1, &glBuffer); - glBindTexture(GL_TEXTURE_2D, glBuffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + // Allow platform to do stuff here if needed, since its now in the + // context of this thread + if (platform->ThreadStartUp() == olc::FAIL) return; + // Do engine context specific initialisation + olc_PrepareEngine(); // Create user resources as part of this thread - if (!OnUserCreate()) - bAtomActive = false; - - auto tp1 = std::chrono::system_clock::now(); - auto tp2 = std::chrono::system_clock::now(); + if (!OnUserCreate()) bAtomActive = false; while (bAtomActive) { // Run as fast as possible - while (bAtomActive) - { - // Handle Timing - tp2 = std::chrono::system_clock::now(); - std::chrono::duration elapsedTime = tp2 - tp1; - tp1 = tp2; - - // Our time per frame coefficient - float fElapsedTime = elapsedTime.count(); - -#ifndef _WIN32 - // 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 - XEvent xev; - while (XPending(olc_Display)) - { - XNextEvent(olc_Display, &xev); - if (xev.type == Expose) - { - XWindowAttributes gwa; - XGetWindowAttributes(olc_Display, olc_Window, &gwa); - glViewport(0, 0, gwa.width, gwa.height); - } - else if (xev.type == KeyPress) - { - KeySym sym = XLookupKeysym(&xev.xkey, 0); - pKeyNewState[mapKeys[sym]] = true; - } - else if (xev.type == KeyRelease) - { - KeySym sym = XLookupKeysym(&xev.xkey, 0); - pKeyNewState[mapKeys[sym]] = false; - } - else if (xev.type == ButtonPress) - { - pMouseNewState[xev.xbutton.button-1] = true; - } - else if (xev.type == ButtonRelease) - { - pMouseNewState[xev.xbutton.button-1] = false; - } - else if (xev.type == MotionNotify) - { - XWindowAttributes gwa; - XGetWindowAttributes(olc_Display, xev.xmotion.window, &gwa); - olc_UpdateMouse((xev.xmotion.x * nScreenWidth * nPixelWidth) / gwa.width, - (xev.xmotion.y * nScreenHeight * nPixelHeight) / gwa.height); - // olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); - } - else if (xev.type == FocusIn) - { - bHasInputFocus = true; - } - else if (xev.type == FocusOut) - { - bHasInputFocus = false; - } - else if (xev.type == ClientMessage) - { - bAtomActive = false; - } - } -#endif - - // Handle User Input - Keyboard - for (int i = 0; i < 256; i++) - { - pKeyboardState[i].bPressed = false; - pKeyboardState[i].bReleased = false; - - if (pKeyNewState[i] != pKeyOldState[i]) - { - if (pKeyNewState[i]) - { - pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; - pKeyboardState[i].bHeld = true; - } - else - { - pKeyboardState[i].bReleased = true; - pKeyboardState[i].bHeld = false; - } - } - - pKeyOldState[i] = pKeyNewState[i]; - } - - // Handle User Input - Mouse - for (int i = 0; i < 5; i++) - { - pMouseState[i].bPressed = false; - pMouseState[i].bReleased = false; - - if (pMouseNewState[i] != pMouseOldState[i]) - { - if (pMouseNewState[i]) - { - pMouseState[i].bPressed = !pMouseState[i].bHeld; - pMouseState[i].bHeld = true; - } - else - { - pMouseState[i].bReleased = true; - pMouseState[i].bHeld = false; - } - } - - pMouseOldState[i] = pMouseNewState[i]; - } - -#ifdef OLC_DBG_OVERDRAW - olc::Sprite::nOverdrawCount = 0; -#endif - - // Handle Frame Update - if (!OnUserUpdate(fElapsedTime)) - bAtomActive = false; - - // Display Graphics - - // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) - // Copy pixel array into texture - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); - - // Display texture on screen - glBegin(GL_QUADS); - glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); - glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); - glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); - glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); - glEnd(); - - // Present Graphics to screen -#ifdef _WIN32 - SwapBuffers(glDeviceContext); -#else - glXSwapBuffers(olc_Display, olc_Window); -#endif - - // Update Title Bar - fFrameTimer += fElapsedTime; - nFrameCount++; - if (fFrameTimer >= 1.0f) - { - fFrameTimer -= 1.0f; - - std::string sTitle = sAppName + " - FPS: " + std::to_string(nFrameCount); -#ifdef _WIN32 -#ifdef UNICODE - SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); -#else - SetWindowText(olc_hWnd, sTitle.c_str()); -#endif -#else - XStoreName(olc_Display, olc_Window, sTitle.c_str()); -#endif - nFrameCount = 0; - } - } + while (bAtomActive) { olc_CoreUpdate(); } // Allow the user to free resources if they have overrided the destroy function - if (OnUserDestroy()) - { - // User has permitted destroy, so exit and clean up - } - else + if (!OnUserDestroy()) { // User denied destroy for some reason, so continue running bAtomActive = true; } } -#ifdef _WIN32 - wglDeleteContext(glRenderContext); - PostMessage(olc_hWnd, WM_DESTROY, 0, 0); -#else - glXMakeCurrent(olc_Display, None, NULL); - glXDestroyContext(olc_Display, glDeviceContext); - XDestroyWindow(olc_Display, olc_Window); - XCloseDisplay(olc_Display); -#endif - + platform->ThreadCleanUp(); } + void PixelGameEngine::olc_PrepareEngine() + { + // Start OpenGL, the context is owned by the game thread + if (platform->CreateGraphics(bFullScreen, bEnableVSYNC, vViewPos, vViewSize) == olc::FAIL) return; + + // Construct default font sheet + olc_ConstructFontSheet(); + + // Create Primary Layer "0" + CreateLayer(); + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDrawTarget(nullptr); + + m_tp1 = std::chrono::system_clock::now(); + m_tp2 = std::chrono::system_clock::now(); + } + + + void PixelGameEngine::olc_CoreUpdate() + { + // Handle Timing + m_tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = m_tp2 - m_tp1; + m_tp1 = m_tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + + // Some platforms will need to check for events + platform->HandleSystemEvent(); + + // Compare hardware input states from previous frame + auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) + { + for (uint32_t i = 0; i < nKeyCount; i++) + { + pKeys[i].bPressed = false; + pKeys[i].bReleased = false; + if (pStateNew[i] != pStateOld[i]) + { + if (pStateNew[i]) + { + pKeys[i].bPressed = !pKeys[i].bHeld; + pKeys[i].bHeld = true; + } + else + { + pKeys[i].bReleased = true; + pKeys[i].bHeld = false; + } + } + pStateOld[i] = pStateNew[i]; + } + }; + + ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); + ScanHardware(pMouseState, pMouseOldState, pMouseNewState, nMouseButtons); + + // Cache mouse coordinates so they remain consistent during frame + vMousePos = vMousePosCache; + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + + renderer->ClearBuffer(olc::BLACK, true); + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Frame + renderer->UpdateViewport(vViewPos, vViewSize); + renderer->ClearBuffer(olc::BLACK, true); + + // Layer 0 must always exist + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + renderer->PrepareDrawing(); + + for (auto layer = vLayers.rbegin(); layer != vLayers.rend(); ++layer) + { + if (layer->bShow) + { + if (layer->funcHook == nullptr) + { + renderer->ApplyTexture(layer->nResID); + if (layer->bUpdate) + { + renderer->UpdateTexture(layer->nResID, layer->pDrawTarget); + layer->bUpdate = false; + } + + renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); + + // Display Decals in order for this layer + for (auto& decal : layer->vecDecalInstance) + renderer->DrawDecalQuad(decal); + layer->vecDecalInstance.clear(); + } + else + { + // Mwa ha ha.... Have Fun!!! + layer->funcHook(); + } + } + } + + // Present Graphics to screen + renderer->DisplayFrame(); + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + nLastFPS = nFrameCount; + fFrameTimer -= 1.0f; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); + platform->SetWindowTitle(sTitle); + nFrameCount = 0; + } + } void PixelGameEngine::olc_ConstructFontSheet() { @@ -1773,694 +2297,856 @@ namespace olc if (++py == 48) { px++; py = 0; } } } + + fontDecal = new olc::Decal(fontSprite); } -#ifdef _WIN32 - HWND PixelGameEngine::olc_WindowCreate() - { - WNDCLASS wc; - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpfnWndProc = olc_WindowEvent; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.lpszMenuName = nullptr; - wc.hbrBackground = nullptr; -#ifdef UNICODE - wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; -#else - wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; -#endif - - RegisterClass(&wc); - - // Define window furniture - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE; - RECT rWndRect = { 0, 0, (LONG)nScreenWidth * (LONG)nPixelWidth, (LONG)nScreenHeight * (LONG)nPixelHeight }; - - // Keep client size as requested - AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - - int width = rWndRect.right - rWndRect.left; - int height = rWndRect.bottom - rWndRect.top; - -#ifdef UNICODE - olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, - 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); -#else - olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, - 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); -#endif - - // Create Keyboard Mapping - mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; - mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; - mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; - mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; - mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; - mapKeys[0x5A] = Key::Z; - - mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; - mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; - mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; - - mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; - mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; - - mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; - mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; - mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; - mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; - mapKeys[VK_SPACE] = Key::SPACE; - - mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; - mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; - - mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; - mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; - mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; - - return olc_hWnd; - } - - bool PixelGameEngine::olc_OpenGLCreate() - { - // Create Device Context - glDeviceContext = GetDC(olc_hWnd); - PIXELFORMATDESCRIPTOR pfd = - { - sizeof(PIXELFORMATDESCRIPTOR), 1, - PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - PFD_MAIN_PLANE, 0, 0, 0, 0 - }; - - int pf = 0; - if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; - SetPixelFormat(glDeviceContext, pf, &pfd); - - if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; - wglMakeCurrent(glDeviceContext, glRenderContext); - - // Remove Frame cap - wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); - wglSwapInterval(0); - return true; - } - - // Windows Event Handler - LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - static PixelGameEngine *sge; - switch (uMsg) - { - case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; - case WM_MOUSEMOVE: - { - uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) - uint16_t y = (lParam >> 16) & 0xFFFF; - int16_t ix = *(int16_t*)&x; - int16_t iy = *(int16_t*)&y; - sge->olc_UpdateMouse(ix, iy); - return 0; - } - case WM_MOUSELEAVE: sge->bHasMouseFocus = false; - case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; - case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; - case WM_KEYDOWN: sge->pKeyNewState[mapKeys[(uint16_t)wParam]] = true; return 0; - case WM_KEYUP: sge->pKeyNewState[mapKeys[(uint16_t)wParam]] = false; return 0; - case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; - case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; - case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; - case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; - case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; - case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; - case WM_CLOSE: bAtomActive = false; return 0; - case WM_DESTROY: PostQuitMessage(0); return 0; - } - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } -#else - // Do the Linux stuff! - Display* PixelGameEngine::olc_WindowCreate() - { - XInitThreads(); - - // Grab the deafult display and window - olc_Display = XOpenDisplay(NULL); - olc_WindowRoot = DefaultRootWindow(olc_Display); - - // Based on the display capabilities, configure the appearance of the window - GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; - olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); - olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); - olc_SetWindowAttribs.colormap = olc_ColourMap; - - // Register which events we are interested in receiving - olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; - - // Create the window - olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); - - Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); - XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); - - XMapWindow(olc_Display, olc_Window); - XStoreName(olc_Display, olc_Window, ""); - - // Create Keyboard Mapping - mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; - mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; - mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; - mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; - mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; - mapKeys[0x7A] = Key::Z; - - mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; - mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; - mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; - - mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; - mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; - - mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; - mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; - mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; - mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; - mapKeys[XK_space] = Key::SPACE; - - mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; - mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; - - mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; - mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; - mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; - - return olc_Display; - } - - bool PixelGameEngine::olc_OpenGLCreate() - { - glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); - glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); - - XWindowAttributes gwa; - XGetWindowAttributes(olc_Display, olc_Window, &gwa); - glViewport(0, 0, gwa.width, gwa.height); - - glSwapIntervalEXT = nullptr; - glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); - if (glSwapIntervalEXT) - glSwapIntervalEXT(olc_Display, olc_Window, 0); - else - { - 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"); - } - - return true; - } - -#endif - // Need a couple of statics as these are singleton instances // read from multiple locations std::atomic PixelGameEngine::bAtomActive{ false }; - std::map PixelGameEngine::mapKeys; olc::PixelGameEngine* olc::PGEX::pge = nullptr; -#ifdef OLC_DBG_OVERDRAW - int olc::Sprite::nOverdrawCount = 0; -#endif -} - -#endif - - - -#ifndef MATHS_H_ -#define MATHS_H_ - -const float DEG2RAD = 0.01745329251994329576923690768f; -const float RAD2DEG = 57.2957795130823208767981548141f; - -inline float ToRadian(const float Degree) { - return (Degree * DEG2RAD); -} - -inline float ToDegree(const float Radian) { - return (Radian * RAD2DEG); -} - -template -struct Vec4 { - T x, y, z, w; - template - Vec4(P x, P y, P z, P w) : x(x), y(y), z(z), w(w) {} - template - Vec4(P all) : x(all), y(all), z(all), w(all) {} - Vec4() : x(0), y(0), z(0), w(0) {} - inline Vec4& dot(const Vec4& v) { - return (x * v.x + y * v.y + z * v.z + w * v.w); - } - inline const Vec4& operator+() { - return *this; - } - inline Vec4& operator-() { - return Vec4(-x, -y, -z, -w); - } - inline Vec4& operator+(const Vec4& v) { - return new Vec4(x + v.x, y + v.y, z + v.z, w + v.w); - } - inline Vec4& operator-(const Vec4& v) { - return new Vec4(x - v.x, y - v.y, z - v.z, w - v.w); - } - inline Vec4& operator*(const Vec4& v) { - return new Vec4(x * v.x, y * v.y, z * v.z, w * v.w); - } - inline Vec4& operator/(const Vec4& v) { - return new Vec4(x / v.x, y / v.y, z / v.z, w / v.w); - } - inline Vec4& operator+=(const Vec4& v) { - x+=v.x; y+=v.y; z+=v.z; w+=v.w; - return *this; - } - inline Vec4& operator-=(const Vec4& v) { - x-=v.x; y-=v.y; z-=v.z; w-=v.w; - return *this; - } - inline Vec4& operator*=(const Vec4& v) { - x*=v.x; y*=v.y; z*=v.z; w*=v.w; - return *this; - } - inline Vec4& operator/=(const Vec4& v) { - x/=v.x; y/=v.y; z/=v.z; w/=v.w; - return *this; - } - template - inline Vec4& operator+=(P s) { - x+=s; y+=s; z+=s; w+=s; - return *this; - } - template - inline Vec4& operator-=(P s) { - x-=s; y-=s; z-=s; w-=s; - return *this; - } - template - inline Vec4& operator*=(P s) { - x*=s; y*=s; z*=s; w*=s; - return *this; - } - template - inline Vec4& operator/=(P s) { - x/=s; y/=s; z/=s; w/=s; - return *this; - } + olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr; + olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr; }; -template -struct Vec3 { - T x, y, z; - template - Vec3(P x, P y, P z) : x(x), y(y), z(z) {} - template - Vec3(P all) : x(all), y(all), z(all) {} - Vec3() : x(0), y(0), z(0) {} - inline Vec3& cross(const Vec3& v) { - return new Vec3( - (y * v.z - z * v.y), - (x * v.z - z * v.x), - (x * v.y - y * v.x) - ); - } - inline Vec3& dot(const Vec3& v) { - return (x * v.x + y * v.y + z * v.z); - } - inline const Vec3& operator+() { - return *this; - } - inline Vec3& operator-() { - return Vec3(-x, -y, -z); - } - inline Vec3& operator+(const Vec3& v) { - return new Vec3(x + v.x, y + v.y, z + v.z); - } - inline Vec3& operator-(const Vec3& v) { - return new Vec3(x - v.x, y - v.y, z - v.z); - } - inline Vec3& operator*(const Vec3& v) { - return new Vec3(x * v.x, y * v.y, z * v.z); - } - inline Vec3& operator/(const Vec3& v) { - return new Vec3(x / v.x, y / v.y, z / v.z); - } - inline Vec3& operator+=(const Vec3& v) { - x+=v.x; y+=v.y; z+=v.z; - return *this; - } - inline Vec3& operator-=(const Vec3& v) { - x-=v.x; y-=v.y; z-=v.z; - return *this; - } - inline Vec3& operator*=(const Vec3& v) { - x*=v.x; y*=v.y; z*=v.z; - return *this; - } - inline Vec3& operator/=(const Vec3& v) { - x/=v.x; y/=v.y; z/=v.z; - return *this; - } - template - inline Vec3& operator+=(P s) { - x+=s; y+=s; z+=s; - return *this; - } - template - inline Vec3& operator-=(P s) { - x-=s; y-=s; z-=s; - return *this; - } - template - inline Vec3& operator*=(P s) { - x*=s; y*=s; z*=s; - return *this; - } - template - inline Vec3& operator/=(P s) { - x/=s; y/=s; z/=s; - return *this; - } -}; -template -struct Vec2 { - T x, y; - template - Vec2(P x, P y) : x(x), y(y) {} - template - Vec2(P all) : x(all), y(all) {} - Vec2() : x(0), y(0) {} - inline const Vec2& operator+() { - return *this; - } - inline Vec2& dot(const Vec3& v) { - return (x * v.x + y * v.y); - } - inline Vec2& operator-() { - return Vec3(-x, -y); - } - inline Vec2& operator+(const Vec2& v) { - return new Vec2(x + v.x, y + v.y); - } - inline Vec2& operator-(const Vec2& v) { - return new Vec2(x - v.x, y - v.y); - } - inline Vec2& operator*(const Vec2& v) { - return new Vec2(x * v.x, y * v.y); - } - inline Vec2& operator/(const Vec2& v) { - return new Vec2(x / v.x, y / v.y); - } - inline Vec2& operator+=(const Vec2& v) { - x+=v.x; y+=v.y; - return *this; - } - inline Vec2& operator-=(const Vec2& v) { - x-=v.x; y-=v.y; - return *this; - } - inline Vec2& operator*=(const Vec2& v) { - x*=v.x; y*=v.y; - return *this; - } - inline Vec2& operator/=(const Vec2& v) { - x/=v.x; y/=v.y; - return *this; - } - template - inline Vec2& operator+=(P s) { - x+=s; y+=s; - return *this; - } - template - inline Vec2& operator-=(P s) { - x-=s; y-=s; - return *this; - } - template - inline Vec2& operator*=(P s) { - x*=s; y*=s; - return *this; - } - template - inline Vec2& operator/=(P s) { - x/=s; y/=s; - return *this; - } -}; +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine PLATFORM SPECIFIC IMPLEMENTATIONS | +// O------------------------------------------------------------------------------O + +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL10) +#if defined(_WIN32) + #include + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t* wglSwapInterval = nullptr; + typedef HDC glDeviceContext_t; + typedef HGLRC glRenderContext_t; #endif -#ifndef RECT_H_ -#define RECT_H_ - -#include - -class Rect { -public: - Rect(); - Rect(int x, int y, int w, int h); - void Clear(); - - static Rect CreateRect(int x, int y, int w, int h) { - Rect tempRect(x, y, w, h); - return tempRect; +#if defined(__linux__) || defined(__FreeBSD__) + #include + namespace X11 + { + #include + #include + #include } - - Rect operator+(Rect* rect) { - return Rect(this->x + rect->x, this->y + this->x, w, h); - } - Rect operator-(Rect* rect) { - return Rect(this->x - rect->x, this->y - this->x, w, h); - } - bool operator==(const Rect* rect) { - return (x == rect->x && y == rect->y && w == rect->w && h == rect->h); - } - bool operator!=(const Rect* rect) { - return !(x == rect->x && y == rect->y && w == rect->w && h == rect->h); - } - - std::string ToString(); - - bool Intersects(Rect* rect); - // bool Intersects(int x, int y, int w, int h); - - bool Contains(Rect* rect); - bool Contains(Vec2* point); - bool Contains(Vec2 point); - bool Contains(int x, int y, int w, int h); - - Vec2* Position(); - Vec2* Center(); - int CenterX(); - int CenterY(); - - int Left(); - int Right(); - int Top(); - int Bottom(); - int Perimiter(); - int Area(); - - int GetX(); - int GetY(); - int GetW(); - int GetH(); - - void SetRect(int x, int y, int w, int h); - void SetSize(Vec2* size); - void SetPos(Vec2* pos); - void SetPos(int x, int y); - void Translate(Vec2* offset); - void TranslateX(int x); - void TranslateY(int y); - - int x, y, w, h; - - virtual ~Rect(); -private: -}; - + #include + typedef int(glSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); + static glSwapInterval_t* glSwapIntervalEXT; + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; #endif -#ifdef OLC_PGE_APPLICATION -#undef OLC_PGE_APPLICATION +namespace olc +{ + class Renderer_OGL10 : public olc::Renderer + { + private: + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; -Rect::Rect() { - Clear(); + #if defined(__linux__) || defined(__FreeBSD__) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; + #endif + + public: + void PrepareDevice() override + { } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { + #if defined(_WIN32) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bVSYNC) wglSwapInterval(0); + #endif + + #if defined(__linux__) || defined(__FreeBSD__) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + if (glSwapIntervalEXT == nullptr && !bVSYNC) + { + 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 && !bVSYNC) + glSwapIntervalEXT(olc_Display, *olc_Window, 0); + #endif + + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { + #if defined(_WIN32) + wglDeleteContext(glRenderContext); + #endif + + #if defined(__linux__) || defined(__FreeBSD__) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + #endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { + #if defined(_WIN32) + SwapBuffers(glDeviceContext); + #endif + + #if defined(__linux__) || defined(__FreeBSD__) + X11::glXSwapBuffers(olc_Display, *olc_Window); + #endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + glBegin(GL_QUADS); + glColor4ub(tint.r, tint.g, tint.b, tint.a); + glTexCoord2f(0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glEnd(); + } + + void DrawDecalQuad(const olc::DecalInstance& decal) override + { + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + glBegin(GL_QUADS); + glColor4ub(decal.tint.r, decal.tint.g, decal.tint.b, decal.tint.a); + glTexCoord4f(decal.uv[0].x, decal.uv[0].y, 0.0f, decal.w[0]); glVertex2f(decal.pos[0].x, decal.pos[0].y); + glTexCoord4f(decal.uv[1].x, decal.uv[1].y, 0.0f, decal.w[1]); glVertex2f(decal.pos[1].x, decal.pos[1].y); + glTexCoord4f(decal.uv[2].x, decal.uv[2].y, 0.0f, decal.w[2]); glVertex2f(decal.pos[2].x, decal.pos[2].y); + glTexCoord4f(decal.uv[3].x, decal.uv[3].y, 0.0f, decal.w[3]); glVertex2f(decal.pos[3].x, decal.pos[3].y); + glEnd(); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height) override + { + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; } - -Rect::Rect(int x, int y, int w, int h) { - SetRect(x, y, w, h); -} - -void Rect::Clear() { - SetRect(0, 0, 0, 0); -} - -std::string Rect::ToString() { - std::string res = "("; - res += std::to_string(x); - res += ", "; - res += std::to_string(y); - res += ", "; - res += std::to_string(w); - res += ", "; - res += std::to_string(h); - res += ")"; - return res; -} - -bool Rect::Intersects(Rect* rect) { - int leftA = x; - int rightA = x + w; - int topA = y; - int bottomA = y + h; - - int leftB = rect->x; - int rightB = rect->x + rect->w; - int topB = rect->y; - int bottomB = rect->y + rect->h; - - if (bottomA <= topB) return false; - if (topA >= bottomB) return false; - if (rightA <= leftB) return false; - if (leftA >= rightB) return false; - - return true; -} - -// bool Rect::Intersects(int x, int y, int w, int h) { -// return Intersects(&CreateRect(x, y, w, h)); -// } - -bool Rect::Contains(Rect* rect) { - return (rect->x >= x && rect->Right() <= (x + w) && rect->y >= y && rect->Bottom() <= (y + h)); -} - -bool Rect::Contains(Vec2* point) { - return (point->x >= x && point->x <= (x + w) && point->y >= y && point->y <= (y + h)); -} - - -bool Rect::Contains(Vec2 point) { - return (point.x >= x && point.x <= (x + w) && point.y >= y && point.y <= (y + h)); -} - -bool Rect::Contains(int x, int y, int w, int h) { - Rect tempRect(x, y, w, h); - return Contains(&tempRect); -} - -Vec2* Rect::Position() { - Vec2* res = new Vec2(x, y); - return res; -} - -Vec2* Rect::Center() { - Vec2* res = new Vec2(x + (w / 2), y + (h / 2)); - return res; -} - -int Rect::CenterX() { - return (x + (w / 2)); -} - -int Rect::CenterY() { - return (y + (h / 2)); -} - -int Rect::Left() { - return x; -} - -int Rect::Right() { - return (x + w); -} - -int Rect::Top() { - return y; -} - -int Rect::Bottom() { - return y + h; -} - -int Rect::Perimiter() { - return (w + w + h + h); -} - -int Rect::Area() { - return (w + h); -} - -int Rect::GetX() { - return x; -} - -int Rect::GetY() { - return y; -} - -int Rect::GetW() { - return w; -} - -int Rect::GetH() { - return h; -} - -void Rect::SetRect(int x, int y, int w, int h) { - this->x = x; - this->y = y; - this->w = w; - this->h = h; -} - -void Rect::SetSize(Vec2* size) { - this->x = size->x; - this->y = size->y; -} - -void Rect::SetPos(Vec2* pos) { - this->w = pos->x; - this->h = pos->y; -} - -void Rect::SetPos(int x, int y) { - this->w = x; - this->h = y; -} - -void Rect::Translate(Vec2* offset) { - this->x += offset->x; - this->y += offset->y; -} - -void Rect::TranslateX(int x) { - this->x += x; -} - -void Rect::TranslateY(int y) { - this->y += y; -} - -Rect::~Rect() { - -} - #endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O + + +// O------------------------------------------------------------------------------O +// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#if defined(_WIN32) +#if !defined(__MINGW32__) + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") + #pragma comment(lib, "Shlwapi.lib") +#else + // In Code::Blocks + #if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif +#endif + +// Include WinAPI +#if !defined(NOMINMAX) + #define NOMINMAX +#endif +#define VC_EXTRALEAN +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +namespace olc +{ + // Little utility function to convert from char to wchar in Windows environments + // depending upon how the compiler is configured. This should not be necessary + // on linux platforms + std::wstring ConvertS2W(std::string s) + { +#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); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); + }; + } gdistartup; + + + class Platform_Windows : public olc::Platform + { + private: + HWND olc_hWnd = nullptr; + std::wstring wsAppName; + + public: + virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() override { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_hWnd }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d &vWindowSize, bool bFullScreen) override + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE"); + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + olc::vi2d vTopLeft = vWindowPos; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; + vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + + // Keep client size as requested + RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + #ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(s).c_str()); + #else + SetWindowText(olc_hWnd, s.c_str()); + #endif + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override { return olc::rcode::FAIL; } + + // Windows Event Handler - this is statically connected to the windows event system + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_MOUSEMOVE: + { + // Thanks @ForAbby (Discord) + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; + case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; + case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; + case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_KEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_LBUTTONDOWN:ptrPGE->olc_UpdateMouseState(0, true); return 0; + case WM_LBUTTONUP: ptrPGE->olc_UpdateMouseState(0, false); return 0; + case WM_RBUTTONDOWN:ptrPGE->olc_UpdateMouseState(1, true); return 0; + case WM_RBUTTONUP: ptrPGE->olc_UpdateMouseState(1, false); return 0; + case WM_MBUTTONDOWN:ptrPGE->olc_UpdateMouseState(2, true); return 0; + case WM_MBUTTONUP: ptrPGE->olc_UpdateMouseState(2, false); return 0; + case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; + + // On Windows load images using GDI+ library + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack *pack) + { + UNUSED(pack); + Gdiplus::Bitmap *bmp = nullptr; + if (pack != nullptr) + { + // Load sprite from input stream + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)rb.vMemory.data(), UINT(rb.vMemory.size()))); + } + else + { + // Load sprite from file + bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str()); + } + + if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::NO_FILE; + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; + } +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O + + + + + +// O------------------------------------------------------------------------------O +// | START PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#if defined(__linux__) || defined(__FreeBSD__) +namespace olc +{ + class Platform_Linux : public olc::Platform + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::Window olc_Window; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + public: + virtual olc::rcode ApplicationStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ApplicationCleanUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ThreadStartUp() override + { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_Display, &olc_Window, olc_VisualInfo }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + using namespace X11; + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, vWindowPos.x, vWindowPos.y, + vWindowSize.x, vWindowSize.y, + 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, + CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + vWindowSize.x = gwa.width; + vWindowSize.y = gwa.height; + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + X11::XStoreName(olc_Display, olc_Window, s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { return olc::OK; } + + virtual olc::rcode HandleSystemEvent() override + { + using namespace X11; + // 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 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + ptrPGE->olc_UpdateWindowSize(xce.width, xce.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + XKeyEvent* e = (XKeyEvent*)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + XKeyEvent* e = (XKeyEvent*)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, true); break; + case 2: ptrPGE->olc_UpdateMouseState(2, true); break; + case 3: ptrPGE->olc_UpdateMouseState(1, true); break; + case 4: ptrPGE->olc_UpdateMouseWheel(120); break; + case 5: ptrPGE->olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, false); break; + case 2: ptrPGE->olc_UpdateMouseState(2, false); break; + case 3: ptrPGE->olc_UpdateMouseState(1, false); break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + ptrPGE->olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + ptrPGE->olc_UpdateKeyFocus(true); + } + else if (xev.type == FocusOut) + { + ptrPGE->olc_UpdateKeyFocus(false); + } + else if (xev.type == ClientMessage) + { + ptrPGE->olc_Terminate(); + } + } + return olc::OK; + } + }; + + void pngReadStream(png_structp pngPtr, png_bytep data, png_size_t length) + { + png_voidp a = png_get_io_ptr(pngPtr); + ((std::istream*)a)->read((char*)data, length); + } + + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack) + { + UNUSED(pack); + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + + png_structp png; + png_infop info; + + auto loadPNG = [&]() + { + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + png_bytep* row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + // Create sprite array + pColData = new Pixel[width * height]; + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + for (int y = 0; y < height; y++) // Thanks maksym33 + free(row_pointers[y]); + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + }; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + if (pack == nullptr) + { + FILE* f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + png_init_io(png, f); + loadPNG(); + fclose(f); + } + else + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); + png_set_read_fn(png, (png_voidp)&is, pngReadStream); + loadPNG(); + } + + return olc::OK; + + fail_load: + width = 0; + height = 0; + pColData = nullptr; + return olc::FAIL; + } +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: LINUX | +// O------------------------------------------------------------------------------O + +namespace olc +{ + void PixelGameEngine::olc_ConfigureSystem() + { +#if defined(_WIN32) + platform = std::make_unique(); +#endif + +#if defined(__linux__) || defined(__FreeBSD__) + platform = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL33) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX10) + renderer = std::make_unique(); +#endif + + //// Associate components with PGE instance + platform->ptrPGE = this; + renderer->ptrPGE = this; + } +} + +#endif // End olc namespace + +// O------------------------------------------------------------------------------O +// | END OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +