diff --git a/olcPGEX_Graphics2D.h b/Extensions/olcPGEX_Graphics2D.h similarity index 97% rename from olcPGEX_Graphics2D.h rename to Extensions/olcPGEX_Graphics2D.h index 261a5c0..de8c0f3 100644 --- a/olcPGEX_Graphics2D.h +++ b/Extensions/olcPGEX_Graphics2D.h @@ -1,313 +1,313 @@ -/* - olcPGEX_Graphics2D.h - - +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine Extension | - | Advanced 2D Rendering - v0.4 | - +-------------------------------------------------------------+ - - What is this? - ~~~~~~~~~~~~~ - This is an extension to the olcPixelGameEngine, which provides - advanced olc::Sprite manipulation and drawing routines. To use - it, simply include this header file. - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 - 2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - -/* - Matrices stored as [Column][Row] (i.e. x, y) - - |C0R0 C1R0 C2R0| | x | | x'| - |C0R1 C1R1 C2R1| * | y | = | y'| - |C0R2 C1R2 C2R2| |1.0| | - | -*/ - - - -#ifndef OLC_PGEX_GFX2D -#define OLC_PGEX_GFX2D - -#include -#undef min -#undef max - -namespace olc -{ - // Container class for Advanced 2D Drawing functions - class GFX2D : public olc::PGEX - { - // A representation of an affine transform, used to rotate, scale, offset & shear space - public: - class Transform2D - { - public: - inline Transform2D(); - - public: - // Set this transformation to unity - inline void Reset(); - // Append a rotation of fTheta radians to this transform - inline void Rotate(float fTheta); - // Append a translation (ox, oy) to this transform - inline void Translate(float ox, float oy); - // Append a scaling operation (sx, sy) to this transform - inline void Scale(float sx, float sy); - // Append a shear operation (sx, sy) to this transform - inline void Shear(float sx, float sy); - - inline void Perspective(float ox, float oy); - // Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) - inline void Forward(float in_x, float in_y, float &out_x, float &out_y); - // Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) - inline void Backward(float in_x, float in_y, float &out_x, float &out_y); - // Regenerate the Inverse Transformation - inline void Invert(); - - private: - inline void Multiply(); - float matrix[4][3][3]; - int nTargetMatrix; - int nSourceMatrix; - bool bDirty; - }; - - public: - // Draws a sprite with the transform applied - inline static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); - }; -} - - -#ifdef OLC_PGE_GRAPHICS2D -#undef OLC_PGE_GRAPHICS2D - -namespace olc -{ - void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform) - { - if (sprite == nullptr) - return; - - // Work out bounding rectangle of sprite - float ex, ey; - float sx, sy; - float px, py; - - transform.Forward(0.0f, 0.0f, sx, sy); - px = sx; py = sy; - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward((float)sprite->width, (float)sprite->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward(0.0f, (float)sprite->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward((float)sprite->width, 0.0f, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - // Perform inversion of transform if required - transform.Invert(); - - if (ex < sx) - std::swap(ex, sx); - if (ey < sy) - std::swap(ey, sy); - - // Iterate through render space, and sample Sprite from suitable texel location - for (float i = sx; i < ex; i++) - { - for (float j = sy; j < ey; j++) - { - float ox, oy; - transform.Backward(i, j, ox, oy); - pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f))); - } - } - } - - olc::GFX2D::Transform2D::Transform2D() - { - Reset(); - } - - void olc::GFX2D::Transform2D::Reset() - { - nTargetMatrix = 0; - nSourceMatrix = 1; - bDirty = true; - - // Columns Then Rows - - // Matrices 0 & 1 are used as swaps in Transform accumulation - matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f; - matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f; - matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f; - - matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f; - matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f; - matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f; - - // Matrix 2 is a cache matrix to hold the immediate transform operation - // Matrix 3 is a cache matrix to hold the inverted transform - } - - void olc::GFX2D::Transform2D::Multiply() - { - for (int c = 0; c < 3; c++) - { - for (int r = 0; r < 3; r++) - { - matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] + - matrix[2][1][r] * matrix[nSourceMatrix][c][1] + - matrix[2][2][r] * matrix[nSourceMatrix][c][2]; - } - } - - std::swap(nTargetMatrix, nSourceMatrix); - bDirty = true; // Any transform multiply dirties the inversion - } - - void olc::GFX2D::Transform2D::Rotate(float fTheta) - { - // Construct Rotation Matrix - matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f; - matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Scale(float sx, float sy) - { - // Construct Scale Matrix - matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Shear(float sx, float sy) - { - // Construct Shear Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Translate(float ox, float oy) - { - // Construct Translate Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Perspective(float ox, float oy) - { - // Construct Translate Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y) - { - out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0]; - out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1]; - float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2]; - if (out_z != 0) - { - out_x /= out_z; - out_y /= out_z; - } - } - - void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y) - { - out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0]; - out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1]; - float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2]; - if (out_z != 0) - { - out_x /= out_z; - out_y /= out_z; - } - } - - void olc::GFX2D::Transform2D::Invert() - { - if (bDirty) // Obviously costly so only do if needed - { - float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) - - matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) + - matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]); - - float idet = 1.0f / det; - matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet; - matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet; - matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet; - matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet; - matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet; - matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet; - matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet; - matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet; - matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet; - bDirty = false; - } - } -} - -#endif +/* + olcPGEX_Graphics2D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Advanced 2D Rendering - v0.4 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + advanced olc::Sprite manipulation and drawing routines. To use + it, simply include this header file. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +/* + Matrices stored as [Column][Row] (i.e. x, y) + + |C0R0 C1R0 C2R0| | x | | x'| + |C0R1 C1R1 C2R1| * | y | = | y'| + |C0R2 C1R2 C2R2| |1.0| | - | +*/ + + + +#ifndef OLC_PGEX_GFX2D +#define OLC_PGEX_GFX2D + +#include +#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/CarCrimeCity/Part1/olcPGEX_Graphics3D.h b/Extensions/olcPGEX_Graphics3D.h similarity index 97% rename from CarCrimeCity/Part1/olcPGEX_Graphics3D.h rename to Extensions/olcPGEX_Graphics3D.h index 954e776..9c6dd80 100644 --- a/CarCrimeCity/Part1/olcPGEX_Graphics3D.h +++ b/Extensions/olcPGEX_Graphics3D.h @@ -1,1174 +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; - } -} - +/* + 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/olcPGEX_Sound.h b/Extensions/olcPGEX_Sound.h similarity index 96% rename from olcPGEX_Sound.h rename to Extensions/olcPGEX_Sound.h index 0b4dcf3..41e47c3 100644 --- a/olcPGEX_Sound.h +++ b/Extensions/olcPGEX_Sound.h @@ -1,892 +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 +/* + 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/CarCrimeCity/Part1/City_Roads1_mip0.png b/Videos/CarCrimeCity/Part1/City_Roads1_mip0.png similarity index 100% rename from CarCrimeCity/Part1/City_Roads1_mip0.png rename to Videos/CarCrimeCity/Part1/City_Roads1_mip0.png diff --git a/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp b/Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp similarity index 97% rename from CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp rename to Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp index 8a4c7a7..3febabc 100644 --- a/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp +++ b/Videos/CarCrimeCity/Part1/OneLoneCoder_CarCrimeCity1.cpp @@ -1,670 +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; +/* + 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/CarCrimeCity/Part1/car_top.png b/Videos/CarCrimeCity/Part1/car_top.png similarity index 100% rename from CarCrimeCity/Part1/car_top.png rename to Videos/CarCrimeCity/Part1/car_top.png diff --git a/CarCrimeCity/Part1/car_top1.png b/Videos/CarCrimeCity/Part1/car_top1.png similarity index 100% rename from CarCrimeCity/Part1/car_top1.png rename to Videos/CarCrimeCity/Part1/car_top1.png diff --git a/CarCrimeCity/Part1/example1.city b/Videos/CarCrimeCity/Part1/example1.city similarity index 100% rename from CarCrimeCity/Part1/example1.city rename to Videos/CarCrimeCity/Part1/example1.city diff --git a/olcPGEX_Graphics3D.h b/Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h similarity index 97% rename from olcPGEX_Graphics3D.h rename to Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h index 954e776..9c6dd80 100644 --- a/olcPGEX_Graphics3D.h +++ b/Videos/CarCrimeCity/Part1/olcPGEX_Graphics3D.h @@ -1,1174 +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; - } -} - +/* + 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/CarCrimeCity/Part1/olcPixelGameEngine.h b/Videos/CarCrimeCity/Part1/olcPixelGameEngine.h similarity index 96% rename from CarCrimeCity/Part1/olcPixelGameEngine.h rename to Videos/CarCrimeCity/Part1/olcPixelGameEngine.h index 37c3ae8..0c524ac 100644 --- a/CarCrimeCity/Part1/olcPixelGameEngine.h +++ b/Videos/CarCrimeCity/Part1/olcPixelGameEngine.h @@ -1,2067 +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 - //============================================================= -} - +/* + 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/CarCrimeCity/Part2/Lua533/include/lauxlib.h b/Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h similarity index 100% rename from CarCrimeCity/Part2/Lua533/include/lauxlib.h rename to Videos/CarCrimeCity/Part2/Lua533/include/lauxlib.h diff --git a/CarCrimeCity/Part2/Lua533/include/lua.h b/Videos/CarCrimeCity/Part2/Lua533/include/lua.h similarity index 100% rename from CarCrimeCity/Part2/Lua533/include/lua.h rename to Videos/CarCrimeCity/Part2/Lua533/include/lua.h diff --git a/CarCrimeCity/Part2/Lua533/include/lua.hpp b/Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp similarity index 100% rename from CarCrimeCity/Part2/Lua533/include/lua.hpp rename to Videos/CarCrimeCity/Part2/Lua533/include/lua.hpp diff --git a/CarCrimeCity/Part2/Lua533/include/luaconf.h b/Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h similarity index 100% rename from CarCrimeCity/Part2/Lua533/include/luaconf.h rename to Videos/CarCrimeCity/Part2/Lua533/include/luaconf.h diff --git a/CarCrimeCity/Part2/Lua533/include/lualib.h b/Videos/CarCrimeCity/Part2/Lua533/include/lualib.h similarity index 100% rename from CarCrimeCity/Part2/Lua533/include/lualib.h rename to Videos/CarCrimeCity/Part2/Lua533/include/lualib.h diff --git a/CarCrimeCity/Part2/Lua533/liblua53.a b/Videos/CarCrimeCity/Part2/Lua533/liblua53.a similarity index 100% rename from CarCrimeCity/Part2/Lua533/liblua53.a rename to Videos/CarCrimeCity/Part2/Lua533/liblua53.a diff --git a/CarCrimeCity/Part2/Lua533/lua53.dll b/Videos/CarCrimeCity/Part2/Lua533/lua53.dll similarity index 100% rename from CarCrimeCity/Part2/Lua533/lua53.dll rename to Videos/CarCrimeCity/Part2/Lua533/lua53.dll diff --git a/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj similarity index 100% rename from CarCrimeCity/Part2/assets/buildings/udxs_building1.obj rename to Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.obj diff --git a/CarCrimeCity/Part2/assets/buildings/udxs_building1.png b/Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png similarity index 100% rename from CarCrimeCity/Part2/assets/buildings/udxs_building1.png rename to Videos/CarCrimeCity/Part2/assets/buildings/udxs_building1.png diff --git a/CarCrimeCity/Part2/assets/buildings/unit_building.blend b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend similarity index 100% rename from CarCrimeCity/Part2/assets/buildings/unit_building.blend rename to Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend diff --git a/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 similarity index 100% rename from CarCrimeCity/Part2/assets/buildings/unit_building.blend1 rename to Videos/CarCrimeCity/Part2/assets/buildings/unit_building.blend1 diff --git a/CarCrimeCity/Part2/assets/buildings/unit_building.obj b/Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj similarity index 100% rename from CarCrimeCity/Part2/assets/buildings/unit_building.obj rename to Videos/CarCrimeCity/Part2/assets/buildings/unit_building.obj diff --git a/CarCrimeCity/Part2/assets/cities/example1.city b/Videos/CarCrimeCity/Part2/assets/cities/example1.city similarity index 100% rename from CarCrimeCity/Part2/assets/cities/example1.city rename to Videos/CarCrimeCity/Part2/assets/cities/example1.city diff --git a/CarCrimeCity/Part2/assets/config.lua b/Videos/CarCrimeCity/Part2/assets/config.lua similarity index 97% rename from CarCrimeCity/Part2/assets/config.lua rename to Videos/CarCrimeCity/Part2/assets/config.lua index ec9e790..bf19e0e 100644 --- a/CarCrimeCity/Part2/assets/config.lua +++ b/Videos/CarCrimeCity/Part2/assets/config.lua @@ -1,50 +1,50 @@ - - --- Size of pixel -PixelWidth = 2 -PixelHeight = 2 - --- Size of display window in pixels -ScreenWidth = 768 -ScreenHeight = 480 ---ScreenWidth = 384 ---ScreenHeight = 240 - -FullScreen = false - --- Default city parameters -DefaultMapWidth = 64 -DefaultMapHeight = 32 ---DefaultCityFile = "assets/cities/example1.city" - - --- Textures used by various game systems -Textures = {} -Textures[1] = {"Grass", "assets/system/grass1.png"} -Textures[2] = {"AllRoads", "assets/system/roads4.png"} -Textures[3] = {"Water", "assets/system/water1.png"} -Textures[4] = {"Clouds", "assets/system/clouds2.png"} -Textures[5] = {"WaterSide", "assets/system/waterside1.png"} -Textures[6] = {"Smoke", "assets/system/skidsmoke1.png"} - --- Buildings -Buildings = {} -Buildings[1] = {"javidx9", "UnitBuilding_1", "assets/buildings/unit_building.obj", "", - 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0} -Buildings[2] = {"UDXS", "Apartments_1", "assets/buildings/udxs_building1.obj", "assets/buildings/udxs_building1.png", - 0.0, 0.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, 0.0} - -Vehicles = {} -Vehicles[1] = {"JustinRM", "Sedan", "assets/vehicles/CarCrime_Sedan.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 1.5708, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} -Vehicles[2] = {"JustinRM", "SUV", "assets/vehicles/CarCrime_SUV.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} -Vehicles[3] = {"JustinRM", "TruckCab", "assets/vehicles/CarCrime_Truck_Cab.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} -Vehicles[4] = {"JustinRM", "TruckTrailer", "assets/vehicles/CarCrime_Truck_Trailer.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} -Vehicles[5] = {"JustinRM", "UTE", "assets/vehicles/CarCrime_Ute.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} -Vehicles[6] = {"JustinRM", "Wagon", "assets/vehicles/CarCrime_Wahon.obj", "assets/vehicles/CarTex_256.png", - 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} - + + +-- Size of pixel +PixelWidth = 2 +PixelHeight = 2 + +-- Size of display window in pixels +ScreenWidth = 768 +ScreenHeight = 480 +--ScreenWidth = 384 +--ScreenHeight = 240 + +FullScreen = false + +-- Default city parameters +DefaultMapWidth = 64 +DefaultMapHeight = 32 +--DefaultCityFile = "assets/cities/example1.city" + + +-- Textures used by various game systems +Textures = {} +Textures[1] = {"Grass", "assets/system/grass1.png"} +Textures[2] = {"AllRoads", "assets/system/roads4.png"} +Textures[3] = {"Water", "assets/system/water1.png"} +Textures[4] = {"Clouds", "assets/system/clouds2.png"} +Textures[5] = {"WaterSide", "assets/system/waterside1.png"} +Textures[6] = {"Smoke", "assets/system/skidsmoke1.png"} + +-- Buildings +Buildings = {} +Buildings[1] = {"javidx9", "UnitBuilding_1", "assets/buildings/unit_building.obj", "", + 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0} +Buildings[2] = {"UDXS", "Apartments_1", "assets/buildings/udxs_building1.obj", "assets/buildings/udxs_building1.png", + 0.0, 0.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0, 0.0} + +Vehicles = {} +Vehicles[1] = {"JustinRM", "Sedan", "assets/vehicles/CarCrime_Sedan.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 1.5708, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} +Vehicles[2] = {"JustinRM", "SUV", "assets/vehicles/CarCrime_SUV.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} +Vehicles[3] = {"JustinRM", "TruckCab", "assets/vehicles/CarCrime_Truck_Cab.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} +Vehicles[4] = {"JustinRM", "TruckTrailer", "assets/vehicles/CarCrime_Truck_Trailer.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} +Vehicles[5] = {"JustinRM", "UTE", "assets/vehicles/CarCrime_Ute.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} +Vehicles[6] = {"JustinRM", "Wagon", "assets/vehicles/CarCrime_Wahon.obj", "assets/vehicles/CarTex_256.png", + 0.0, 0.0, 0.0, 0.05, 0.05, 0.05, 0.0, 0.0, 0.0} + diff --git a/CarCrimeCity/Part2/assets/system/car_top.png b/Videos/CarCrimeCity/Part2/assets/system/car_top.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/car_top.png rename to Videos/CarCrimeCity/Part2/assets/system/car_top.png diff --git a/CarCrimeCity/Part2/assets/system/car_top3.png b/Videos/CarCrimeCity/Part2/assets/system/car_top3.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/car_top3.png rename to Videos/CarCrimeCity/Part2/assets/system/car_top3.png diff --git a/CarCrimeCity/Part2/assets/system/ccctitle1.png b/Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/ccctitle1.png rename to Videos/CarCrimeCity/Part2/assets/system/ccctitle1.png diff --git a/CarCrimeCity/Part2/assets/system/clouds1.png b/Videos/CarCrimeCity/Part2/assets/system/clouds1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/clouds1.png rename to Videos/CarCrimeCity/Part2/assets/system/clouds1.png diff --git a/CarCrimeCity/Part2/assets/system/clouds2.png b/Videos/CarCrimeCity/Part2/assets/system/clouds2.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/clouds2.png rename to Videos/CarCrimeCity/Part2/assets/system/clouds2.png diff --git a/CarCrimeCity/Part2/assets/system/grass1.png b/Videos/CarCrimeCity/Part2/assets/system/grass1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/grass1.png rename to Videos/CarCrimeCity/Part2/assets/system/grass1.png diff --git a/CarCrimeCity/Part2/assets/system/roads1.png b/Videos/CarCrimeCity/Part2/assets/system/roads1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/roads1.png rename to Videos/CarCrimeCity/Part2/assets/system/roads1.png diff --git a/CarCrimeCity/Part2/assets/system/roads2.png b/Videos/CarCrimeCity/Part2/assets/system/roads2.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/roads2.png rename to Videos/CarCrimeCity/Part2/assets/system/roads2.png diff --git a/CarCrimeCity/Part2/assets/system/roads3.png b/Videos/CarCrimeCity/Part2/assets/system/roads3.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/roads3.png rename to Videos/CarCrimeCity/Part2/assets/system/roads3.png diff --git a/CarCrimeCity/Part2/assets/system/roads4.png b/Videos/CarCrimeCity/Part2/assets/system/roads4.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/roads4.png rename to Videos/CarCrimeCity/Part2/assets/system/roads4.png diff --git a/CarCrimeCity/Part2/assets/system/skidsmoke1.png b/Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/skidsmoke1.png rename to Videos/CarCrimeCity/Part2/assets/system/skidsmoke1.png diff --git a/CarCrimeCity/Part2/assets/system/water1.png b/Videos/CarCrimeCity/Part2/assets/system/water1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/water1.png rename to Videos/CarCrimeCity/Part2/assets/system/water1.png diff --git a/CarCrimeCity/Part2/assets/system/waterside1.png b/Videos/CarCrimeCity/Part2/assets/system/waterside1.png similarity index 100% rename from CarCrimeCity/Part2/assets/system/waterside1.png rename to Videos/CarCrimeCity/Part2/assets/system/waterside1.png diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_SUV.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Sedan.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Cab.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Truck_Trailer.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Ute.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj b/Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarCrime_Wagon.obj diff --git a/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png b/Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png similarity index 100% rename from CarCrimeCity/Part2/assets/vehicles/CarTex_256.png rename to Videos/CarCrimeCity/Part2/assets/vehicles/CarTex_256.png diff --git a/CarCrimeCity/Part2/cAutomata.cpp b/Videos/CarCrimeCity/Part2/cAutomata.cpp similarity index 96% rename from CarCrimeCity/Part2/cAutomata.cpp rename to Videos/CarCrimeCity/Part2/cAutomata.cpp index f1bb2a9..b289bbc 100644 --- a/CarCrimeCity/Part2/cAutomata.cpp +++ b/Videos/CarCrimeCity/Part2/cAutomata.cpp @@ -1,206 +1,206 @@ -#include "cAutomata.h" - - -cAuto_Node::cAuto_Node() -{ - pos = { 0,0 }; -} - -cAuto_Node::cAuto_Node(const olc::vf2d &worldpos) -{ - pos = worldpos; -} - -olc::vf2d cAuto_Track::GetPostion(float t, cAuto_Node *pStart) -{ - // pStart indicates the node the automata first encounted this track - if (node[0] == pStart) - { - return node[0]->pos + (node[1]->pos - node[0]->pos) * (t / fTrackLength); - } - else - { - return node[1]->pos + (node[0]->pos - node[1]->pos) * (t / fTrackLength); - } -} - - - -cAuto_Body::cAuto_Body() -{ -} - - -cAuto_Body::~cAuto_Body() -{ -} - - -void cAuto_Body::UpdateAuto(float fElapsedTime) -{ - // Work out which node is the target destination - cAuto_Node *pExitNode = pCurrentTrack->node[0]; - if (pExitNode == pTrackOriginNode) - pExitNode = pCurrentTrack->node[1]; - - bool bAutomataCanMove = true; - - float fDistanceToAutoInFront = 1.0f; - - // First check if the vehicle overlaps with the one in front of it - - // Get an iterator for this automata - auto itThisAutomata = std::find(pCurrentTrack->listAutos.begin(), pCurrentTrack->listAutos.end(), this); - - // If this automata is at the front of this track segment - if (*itThisAutomata == pCurrentTrack->listAutos.front()) - { - // Then check all the following track segments. Take the position of - // each vehicle at the back of the track segments auto list - for (auto &track : pExitNode->listTracks) - { - if (track != pCurrentTrack && !track->listAutos.empty()) - { - // Get Auto at back - float fDistanceFromTrackStartToAutoRear = track->listAutos.back()->fAutoPos - track->listAutos.back()->fAutoLength; - - if ((*itThisAutomata)->fAutoPos < (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - fAutoLength)) - { - // Move Automata along track, as there is space - //bAutomataCanMove = true; - fDistanceToAutoInFront = (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - 0.1f) - (*itThisAutomata)->fAutoPos; - } - else - { - // No space, so do not move automata - bAutomataCanMove = false; - } - } - else - { - // Track in front was empty, node is clear to pass through so - //bAutomataCanMove = true; - } - } - - - - } - else - { - // Get the automata in front - auto itAutomataInFront = itThisAutomata; - itAutomataInFront--; - - // If the distance between the front of the automata in front and the fornt of this automata - // is greater than the length of the automata in front, then there is space for this automata - // to enter - if (fabs((*itAutomataInFront)->fAutoPos - (*itThisAutomata)->fAutoPos) > ((*itAutomataInFront)->fAutoLength + 0.1f)) - { - // Move Automata along track - //bAutomataCanMove = true; - fDistanceToAutoInFront = ((*itAutomataInFront)->fAutoPos - (*itAutomataInFront)->fAutoLength - 0.1f) - (*itThisAutomata)->fAutoPos; - } - else - { - // No space, so do not move automata - bAutomataCanMove = false; - } - } - - if (bAutomataCanMove) - { - if (fDistanceToAutoInFront > pCurrentTrack->fTrackLength) fDistanceToAutoInFront = pCurrentTrack->fTrackLength; - fAutoPos += fElapsedTime * std::max(fDistanceToAutoInFront, 1.0f) * (fAutoLength < 0.1f ? 0.3f : 0.5f); - } - - - if (fAutoPos >= pCurrentTrack->fTrackLength) - { - // Automata has reached end of current track - - // Check if it can transition beyond node - if (!pExitNode->bBlock) - { - // It can, so reset position along track back to start - fAutoPos -= pCurrentTrack->fTrackLength; - - // Choose a track from the node not equal to this one, that has an unblocked exit node - - // For now choose at random - cAuto_Track *pNewTrack = nullptr; - - if (pExitNode->listTracks.size() == 2) - { - // Automata is travelling along straight joined sections, one of the - // tracks is the track its just come in on, the other is the exit, so - // choose the exit. - auto it = pExitNode->listTracks.begin(); - pNewTrack = (*it); - if (pCurrentTrack == pNewTrack) - { - ++it; - pNewTrack = (*it); - } - } - else - { - // Automata has reached a junction with several exits - while (pNewTrack == nullptr) - { - int i = rand() % pExitNode->listTracks.size(); - int j = 0; - for (auto it = pExitNode->listTracks.begin(); it != pExitNode->listTracks.end(); ++it) - { - cAuto_Track* track = (*it); - - // Work out which node is the target destination - cAuto_Node *pNewExitNode = track->node[0]; - if (pNewExitNode == pExitNode) - pNewExitNode = track->node[1]; - - if (j == i && track != pCurrentTrack && !pNewExitNode->bBlock /*((*it)->cell != pCurrentTrack->cell)*/) - { - pNewTrack = track; - break; - } - - j++; - } - } - } - - - // Change to new track, the origin node of the next - // track is the same as the exit node to the current track - pTrackOriginNode = pExitNode; - - // Remove the automata from the front of the queue - // on the current track - pCurrentTrack->listAutos.pop_front(); - - // Switch the automatas track link to the new track - pCurrentTrack = pNewTrack; - - // Push the automata onto the back of the new track queue - pCurrentTrack->listAutos.push_back(this); - - } - else - { - // It cant pass the node, so clamp automata at this location - fAutoPos = pCurrentTrack->fTrackLength; - } - - } - else - { - // Automata is travelling - vAutoPos = pCurrentTrack->GetPostion(fAutoPos, pTrackOriginNode); - } -} - - - - - +#include "cAutomata.h" + + +cAuto_Node::cAuto_Node() +{ + pos = { 0,0 }; +} + +cAuto_Node::cAuto_Node(const olc::vf2d &worldpos) +{ + pos = worldpos; +} + +olc::vf2d cAuto_Track::GetPostion(float t, cAuto_Node *pStart) +{ + // pStart indicates the node the automata first encounted this track + if (node[0] == pStart) + { + return node[0]->pos + (node[1]->pos - node[0]->pos) * (t / fTrackLength); + } + else + { + return node[1]->pos + (node[0]->pos - node[1]->pos) * (t / fTrackLength); + } +} + + + +cAuto_Body::cAuto_Body() +{ +} + + +cAuto_Body::~cAuto_Body() +{ +} + + +void cAuto_Body::UpdateAuto(float fElapsedTime) +{ + // Work out which node is the target destination + cAuto_Node *pExitNode = pCurrentTrack->node[0]; + if (pExitNode == pTrackOriginNode) + pExitNode = pCurrentTrack->node[1]; + + bool bAutomataCanMove = true; + + float fDistanceToAutoInFront = 1.0f; + + // First check if the vehicle overlaps with the one in front of it + + // Get an iterator for this automata + auto itThisAutomata = std::find(pCurrentTrack->listAutos.begin(), pCurrentTrack->listAutos.end(), this); + + // If this automata is at the front of this track segment + if (*itThisAutomata == pCurrentTrack->listAutos.front()) + { + // Then check all the following track segments. Take the position of + // each vehicle at the back of the track segments auto list + for (auto &track : pExitNode->listTracks) + { + if (track != pCurrentTrack && !track->listAutos.empty()) + { + // Get Auto at back + float fDistanceFromTrackStartToAutoRear = track->listAutos.back()->fAutoPos - track->listAutos.back()->fAutoLength; + + if ((*itThisAutomata)->fAutoPos < (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - fAutoLength)) + { + // Move Automata along track, as there is space + //bAutomataCanMove = true; + fDistanceToAutoInFront = (pCurrentTrack->fTrackLength + fDistanceFromTrackStartToAutoRear - 0.1f) - (*itThisAutomata)->fAutoPos; + } + else + { + // No space, so do not move automata + bAutomataCanMove = false; + } + } + else + { + // Track in front was empty, node is clear to pass through so + //bAutomataCanMove = true; + } + } + + + + } + else + { + // Get the automata in front + auto itAutomataInFront = itThisAutomata; + itAutomataInFront--; + + // If the distance between the front of the automata in front and the fornt of this automata + // is greater than the length of the automata in front, then there is space for this automata + // to enter + if (fabs((*itAutomataInFront)->fAutoPos - (*itThisAutomata)->fAutoPos) > ((*itAutomataInFront)->fAutoLength + 0.1f)) + { + // Move Automata along track + //bAutomataCanMove = true; + fDistanceToAutoInFront = ((*itAutomataInFront)->fAutoPos - (*itAutomataInFront)->fAutoLength - 0.1f) - (*itThisAutomata)->fAutoPos; + } + else + { + // No space, so do not move automata + bAutomataCanMove = false; + } + } + + if (bAutomataCanMove) + { + if (fDistanceToAutoInFront > pCurrentTrack->fTrackLength) fDistanceToAutoInFront = pCurrentTrack->fTrackLength; + fAutoPos += fElapsedTime * std::max(fDistanceToAutoInFront, 1.0f) * (fAutoLength < 0.1f ? 0.3f : 0.5f); + } + + + if (fAutoPos >= pCurrentTrack->fTrackLength) + { + // Automata has reached end of current track + + // Check if it can transition beyond node + if (!pExitNode->bBlock) + { + // It can, so reset position along track back to start + fAutoPos -= pCurrentTrack->fTrackLength; + + // Choose a track from the node not equal to this one, that has an unblocked exit node + + // For now choose at random + cAuto_Track *pNewTrack = nullptr; + + if (pExitNode->listTracks.size() == 2) + { + // Automata is travelling along straight joined sections, one of the + // tracks is the track its just come in on, the other is the exit, so + // choose the exit. + auto it = pExitNode->listTracks.begin(); + pNewTrack = (*it); + if (pCurrentTrack == pNewTrack) + { + ++it; + pNewTrack = (*it); + } + } + else + { + // Automata has reached a junction with several exits + while (pNewTrack == nullptr) + { + int i = rand() % pExitNode->listTracks.size(); + int j = 0; + for (auto it = pExitNode->listTracks.begin(); it != pExitNode->listTracks.end(); ++it) + { + cAuto_Track* track = (*it); + + // Work out which node is the target destination + cAuto_Node *pNewExitNode = track->node[0]; + if (pNewExitNode == pExitNode) + pNewExitNode = track->node[1]; + + if (j == i && track != pCurrentTrack && !pNewExitNode->bBlock /*((*it)->cell != pCurrentTrack->cell)*/) + { + pNewTrack = track; + break; + } + + j++; + } + } + } + + + // Change to new track, the origin node of the next + // track is the same as the exit node to the current track + pTrackOriginNode = pExitNode; + + // Remove the automata from the front of the queue + // on the current track + pCurrentTrack->listAutos.pop_front(); + + // Switch the automatas track link to the new track + pCurrentTrack = pNewTrack; + + // Push the automata onto the back of the new track queue + pCurrentTrack->listAutos.push_back(this); + + } + else + { + // It cant pass the node, so clamp automata at this location + fAutoPos = pCurrentTrack->fTrackLength; + } + + } + else + { + // Automata is travelling + vAutoPos = pCurrentTrack->GetPostion(fAutoPos, pTrackOriginNode); + } +} + + + + + diff --git a/CarCrimeCity/Part2/cAutomata.h b/Videos/CarCrimeCity/Part2/cAutomata.h similarity index 96% rename from CarCrimeCity/Part2/cAutomata.h rename to Videos/CarCrimeCity/Part2/cAutomata.h index 84b9c14..f612481 100644 --- a/CarCrimeCity/Part2/cAutomata.h +++ b/Videos/CarCrimeCity/Part2/cAutomata.h @@ -1,107 +1,107 @@ -/* - Top Down City Based Car Crime Game - Part #2 - "Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Scroll with middle mouse wheel, TAB toggle edit mode, R to place road - P to place pavement, Q to place building, Arrow keys to drive car - - Relevant Video: https://youtu.be/fIV6P1W-wuo - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - - -#pragma once - -#include "olcPixelGameEngine.h" - -class cAuto_Track; -class cAuto_Node; -class cAuto_Body; -class cCell; - -class cAuto_Node -{ -public: - cAuto_Node(); - cAuto_Node(const olc::vf2d &worldpos); - olc::vf2d pos; - bool bBlock = false; - std::list 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; - -}; +/* + 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/CarCrimeCity/Part2/cCarCrimeCity.cpp b/Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp similarity index 96% rename from CarCrimeCity/Part2/cCarCrimeCity.cpp rename to Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp index 572b7a0..6067839 100644 --- a/CarCrimeCity/Part2/cCarCrimeCity.cpp +++ b/Videos/CarCrimeCity/Part2/cCarCrimeCity.cpp @@ -1,709 +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; -} +#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/CarCrimeCity/Part2/cCarCrimeCity.h b/Videos/CarCrimeCity/Part2/cCarCrimeCity.h similarity index 96% rename from CarCrimeCity/Part2/cCarCrimeCity.h rename to Videos/CarCrimeCity/Part2/cCarCrimeCity.h index 7ff0673..a2a481c 100644 --- a/CarCrimeCity/Part2/cCarCrimeCity.h +++ b/Videos/CarCrimeCity/Part2/cCarCrimeCity.h @@ -1,289 +1,289 @@ -/* - Top Down City Based Car Crime Game - Part #2 - "Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Scroll with middle mouse wheel, TAB toggle edit mode, R to place road - P to place pavement, Q to place building, Arrow keys to drive car - - Relevant Video: https://youtu.be/fIV6P1W-wuo - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - - -#pragma once - -#include "olcPixelGameEngine.h" -#include "olcPGEX_Graphics3D.h" - -#include "cGameSettings.h" -#include "cCityMap.h" - -#include -#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); -}; - +/* + 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/CarCrimeCity/Part2/cCell.cpp b/Videos/CarCrimeCity/Part2/cCell.cpp similarity index 94% rename from CarCrimeCity/Part2/cCell.cpp rename to Videos/CarCrimeCity/Part2/cCell.cpp index b5d173c..9f37d8d 100644 --- a/CarCrimeCity/Part2/cCell.cpp +++ b/Videos/CarCrimeCity/Part2/cCell.cpp @@ -1,121 +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() -{ - -} +#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/CarCrimeCity/Part2/cCell.h b/Videos/CarCrimeCity/Part2/cCell.h similarity index 95% rename from CarCrimeCity/Part2/cCell.h rename to Videos/CarCrimeCity/Part2/cCell.h index 6b70298..4f27a29 100644 --- a/CarCrimeCity/Part2/cCell.h +++ b/Videos/CarCrimeCity/Part2/cCell.h @@ -1,60 +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); -}; - +#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/CarCrimeCity/Part2/cCell_Building.cpp b/Videos/CarCrimeCity/Part2/cCell_Building.cpp similarity index 96% rename from CarCrimeCity/Part2/cCell_Building.cpp rename to Videos/CarCrimeCity/Part2/cCell_Building.cpp index 151f7d6..6635163 100644 --- a/CarCrimeCity/Part2/cCell_Building.cpp +++ b/Videos/CarCrimeCity/Part2/cCell_Building.cpp @@ -1,53 +1,53 @@ -#include "cCell_Building.h" - - - -cCell_Building::cCell_Building(const std::string &name, cCityMap* map, int x, int y) : cCell(map, x, y) -{ - sName = name; -} - - -cCell_Building::~cCell_Building() -{ -} - -void cCell_Building::CalculateAdjacency() -{ -} - -bool cCell_Building::LinkAssets(std::map& 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; -} +#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/CarCrimeCity/Part2/cCell_Building.h b/Videos/CarCrimeCity/Part2/cCell_Building.h similarity index 96% rename from CarCrimeCity/Part2/cCell_Building.h rename to Videos/CarCrimeCity/Part2/cCell_Building.h index 8ddd823..874ab42 100644 --- a/CarCrimeCity/Part2/cCell_Building.h +++ b/Videos/CarCrimeCity/Part2/cCell_Building.h @@ -1,25 +1,25 @@ -#pragma once -#include "cCell.h" -#include "olcPGEX_Graphics3D.h" - - -class cCell_Building : public cCell -{ -public: - cCell_Building(const std::string &name, cCityMap* map, int x, int y); - ~cCell_Building(); - -private: - std::string sName; - olc::Sprite* texture = nullptr; - olc::GFX3D::mesh* mesh = nullptr; - olc::GFX3D::mat4x4 transform; - -public: - virtual void CalculateAdjacency(); - virtual bool LinkAssets(std::map &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); -}; - +#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/CarCrimeCity/Part2/cCell_Plane.cpp b/Videos/CarCrimeCity/Part2/cCell_Plane.cpp similarity index 96% rename from CarCrimeCity/Part2/cCell_Plane.cpp rename to Videos/CarCrimeCity/Part2/cCell_Plane.cpp index 34fc1b8..a0a3da7 100644 --- a/CarCrimeCity/Part2/cCell_Plane.cpp +++ b/Videos/CarCrimeCity/Part2/cCell_Plane.cpp @@ -1,49 +1,49 @@ -#include "cCell_Plane.h" - - - -cCell_Plane::cCell_Plane(cCityMap* map, int x, int y, CELL_PLANE type) : cCell(map, x, y) -{ - bSolid = false; - nType = type; - if (nType == PLANE_GRASS) nCellType = CELL_GRASS; - if (nType == PLANE_ASPHALT) nCellType = CELL_PAVEMENT; -} - - -cCell_Plane::~cCell_Plane() -{ -} - -bool cCell_Plane::LinkAssets(std::map &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; -} +#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/CarCrimeCity/Part2/cCell_Plane.h b/Videos/CarCrimeCity/Part2/cCell_Plane.h similarity index 96% rename from CarCrimeCity/Part2/cCell_Plane.h rename to Videos/CarCrimeCity/Part2/cCell_Plane.h index 46abd0e..252296c 100644 --- a/CarCrimeCity/Part2/cCell_Plane.h +++ b/Videos/CarCrimeCity/Part2/cCell_Plane.h @@ -1,34 +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); -}; - +#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/CarCrimeCity/Part2/cCell_Road.cpp b/Videos/CarCrimeCity/Part2/cCell_Road.cpp similarity index 95% rename from CarCrimeCity/Part2/cCell_Road.cpp rename to Videos/CarCrimeCity/Part2/cCell_Road.cpp index 0b27721..5887e07 100644 --- a/CarCrimeCity/Part2/cCell_Road.cpp +++ b/Videos/CarCrimeCity/Part2/cCell_Road.cpp @@ -1,812 +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; -} +#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/CarCrimeCity/Part2/cCell_Road.h b/Videos/CarCrimeCity/Part2/cCell_Road.h similarity index 95% rename from CarCrimeCity/Part2/cCell_Road.h rename to Videos/CarCrimeCity/Part2/cCell_Road.h index 22abc7e..d0f183e 100644 --- a/CarCrimeCity/Part2/cCell_Road.h +++ b/Videos/CarCrimeCity/Part2/cCell_Road.h @@ -1,54 +1,54 @@ -#pragma once -#include "cCell.h" - -enum RoadType -{ - ROAD_H, - ROAD_V, - ROAD_C1, - ROAD_C2, - ROAD_C3, - ROAD_C4, - ROAD_T1, - ROAD_T2, - ROAD_T3, - ROAD_T4, - ROAD_X, -}; - - -class cCell_Road : public cCell -{ -public: - cCell_Road(cCityMap* map, int x, int y); - ~cCell_Road(); - -private: - struct StopPattern - { - bool bStop[49]; - }; - -private: - bool bNeighboursAreRoads[4]; - - olc::GFX3D::mesh *meshUnitQuad = nullptr; - olc::Sprite* sprRoadTex[11]; - - std::vector 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); -}; - +#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/CarCrimeCity/Part2/cCell_Water.cpp b/Videos/CarCrimeCity/Part2/cCell_Water.cpp similarity index 97% rename from CarCrimeCity/Part2/cCell_Water.cpp rename to Videos/CarCrimeCity/Part2/cCell_Water.cpp index da3b863..91f0fc3 100644 --- a/CarCrimeCity/Part2/cCell_Water.cpp +++ b/Videos/CarCrimeCity/Part2/cCell_Water.cpp @@ -1,91 +1,91 @@ -#include "cCell_Water.h" -#include "cCityMap.h" - - -cCell_Water::cCell_Water(cCityMap* map, int x, int y) : cCell(map, x, y) -{ - nCellType = CELL_WATER; - bNeighboursAreWater[0] = false; - bNeighboursAreWater[1] = false; - bNeighboursAreWater[2] = false; - bNeighboursAreWater[3] = false; -} - - -cCell_Water::~cCell_Water() -{ -} - -bool cCell_Water::LinkAssets(std::map& 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); +#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/CarCrimeCity/Part2/cCell_Water.h b/Videos/CarCrimeCity/Part2/cCell_Water.h similarity index 96% rename from CarCrimeCity/Part2/cCell_Water.h rename to Videos/CarCrimeCity/Part2/cCell_Water.h index 35a85f2..4ed0502 100644 --- a/CarCrimeCity/Part2/cCell_Water.h +++ b/Videos/CarCrimeCity/Part2/cCell_Water.h @@ -1,25 +1,25 @@ -#pragma once -#include "cCell.h" -class cCell_Water : public cCell -{ -public: - cCell_Water(cCityMap* map, int x, int y); - ~cCell_Water(); - -private: - olc::GFX3D::mesh* meshUnitQuad = nullptr; - olc::GFX3D::mesh* meshWalls = nullptr; - olc::Sprite* sprWater = nullptr; - olc::Sprite* sprSides = nullptr; - olc::Sprite* sprClouds = nullptr; - - bool bNeighboursAreWater[4]; - -public: - virtual void CalculateAdjacency(); - virtual bool LinkAssets(std::map &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); -}; - +#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/CarCrimeCity/Part2/cCityMap.cpp b/Videos/CarCrimeCity/Part2/cCityMap.cpp similarity index 95% rename from CarCrimeCity/Part2/cCityMap.cpp rename to Videos/CarCrimeCity/Part2/cCityMap.cpp index b4c1c54..712a225 100644 --- a/CarCrimeCity/Part2/cCityMap.cpp +++ b/Videos/CarCrimeCity/Part2/cCityMap.cpp @@ -1,202 +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; +#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/CarCrimeCity/Part2/cCityMap.h b/Videos/CarCrimeCity/Part2/cCityMap.h similarity index 96% rename from CarCrimeCity/Part2/cCityMap.h rename to Videos/CarCrimeCity/Part2/cCityMap.h index 828a23e..43a4456 100644 --- a/CarCrimeCity/Part2/cCityMap.h +++ b/Videos/CarCrimeCity/Part2/cCityMap.h @@ -1,63 +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(); -}; - +#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/CarCrimeCity/Part2/cGameSettings.cpp b/Videos/CarCrimeCity/Part2/cGameSettings.cpp similarity index 95% rename from CarCrimeCity/Part2/cGameSettings.cpp rename to Videos/CarCrimeCity/Part2/cGameSettings.cpp index 1ca52a5..17842d3 100644 --- a/CarCrimeCity/Part2/cGameSettings.cpp +++ b/Videos/CarCrimeCity/Part2/cGameSettings.cpp @@ -1,156 +1,156 @@ -#include "cGameSettings.h" - - - -cGameSettings::cGameSettings() -{ -} - -cGameSettings::~cGameSettings() -{ -} - -bool cGameSettings::LoadConfigFile(std::string sFile) -{ - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - - // Load game settings file - int r = luaL_loadfile(L, sFile.c_str()); - if (r != LUA_OK) - { - std::string errormsg = lua_tostring(L, -1); - std::cout << errormsg << std::endl; - return false; - } - - // Execute it - int i = lua_pcall(L, 0, LUA_MULTRET, 0); - if (i != LUA_OK) - { - std::string errormsg = lua_tostring(L, -1); - std::cout << errormsg << std::endl; - return false; - } - - lua_getglobal(L, "PixelWidth"); - if (lua_isinteger(L, -1)) cGameSettings::nPixelWidth = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "PixelHeight"); - if (lua_isinteger(L, -1)) cGameSettings::nPixelHeight = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "ScreenWidth"); - if (lua_isinteger(L, -1)) cGameSettings::nScreenWidth = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "ScreenHeight"); - if (lua_isinteger(L, -1)) cGameSettings::nScreenHeight = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "DefaultMapWidth"); - if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapWidth = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "DefaultMapHeight"); - if (lua_isinteger(L, -1)) cGameSettings::nDefaultMapHeight = (int)lua_tointeger(L, -1); - - lua_getglobal(L, "DefaultCityFile"); - if (lua_isstring(L, -1)) cGameSettings::sDefaultCityFile = lua_tostring(L, -1); - - lua_getglobal(L, "FullScreen"); - if (lua_isboolean(L, -1)) cGameSettings::bFullScreen = lua_toboolean(L, -1); - - - //// Load System Texture files - - // Load Texture Assets - lua_getglobal(L, "Textures"); // -1 Table "Teams" - if (lua_istable(L, -1)) - { - lua_pushnil(L); // -2 Key Nil : -1 Table "Teams" - - while (lua_next(L, -2) != 0) // -1 Table : -2 Key "TeamName" : -3 Table "Teams" - { - sAssetTexture texture; - int stage = 0; - if (lua_istable(L, -1)) - { - lua_gettable(L, -1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams" - lua_pushnil(L); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams" - while (lua_next(L, -2) != 0) // -1 Value "BotFile" : -2 Key Nil : -3 Table : -4 Table Value : -5 Key "TeamName" : -6 Table "Teams" - { - if (stage == 0) texture.sName = lua_tostring(L, -1); - if (stage == 1) texture.sFile = lua_tostring(L, -1); - lua_pop(L, 1); // -1 Key Nil : -2 Table : -3 Table Value : -4 Key "TeamName" : -5 Table "Teams" - stage++; - } - } - lua_pop(L, 1); // -1 Table : -2 Table Value : -3 Key "TeamName" : -4 Table "Teams" - vecAssetTextures.push_back(texture); - } - } - - auto GroupLoadAssets = [L](const std::string &group, std::vector &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; +#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/CarCrimeCity/Part2/cGameSettings.h b/Videos/CarCrimeCity/Part2/cGameSettings.h similarity index 94% rename from CarCrimeCity/Part2/cGameSettings.h rename to Videos/CarCrimeCity/Part2/cGameSettings.h index 59a9bcd..7855461 100644 --- a/CarCrimeCity/Part2/cGameSettings.h +++ b/Videos/CarCrimeCity/Part2/cGameSettings.h @@ -1,65 +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; -}; - +#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/CarCrimeCity/Part2/lua53.dll b/Videos/CarCrimeCity/Part2/lua53.dll similarity index 100% rename from CarCrimeCity/Part2/lua53.dll rename to Videos/CarCrimeCity/Part2/lua53.dll diff --git a/CarCrimeCity/Part2/main.cpp b/Videos/CarCrimeCity/Part2/main.cpp similarity index 96% rename from CarCrimeCity/Part2/main.cpp rename to Videos/CarCrimeCity/Part2/main.cpp index 3568ee2..7f5a732 100644 --- a/CarCrimeCity/Part2/main.cpp +++ b/Videos/CarCrimeCity/Part2/main.cpp @@ -1,367 +1,367 @@ -/* - Top Down City Based Car Crime Game - Part #2 - "Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Scroll with middle mouse wheel, TAB toggle edit mode, R to place road - P to place pavement, Q to place building, Arrow keys to drive car - - Relevant Video: https://youtu.be/fIV6P1W-wuo - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - - -#include "cGameSettings.h" -#include "cCarCrimeCity.h" - - -int main() -{ - // Load the settings singleton - cGameSettings config; - if (!config.LoadConfigFile("assets/config.lua")) - { - std::cout << "Failed to load '/assets/config.lua'" << std::endl; - std::cout << " -> Using default configuration" << std::endl; - } - - // Start the PixelGameEngine - cCarCrimeCity game; - if (game.Construct(config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight, config.bFullScreen)) - game.Start(); - - // Exit! - return 0; -} - -//#define OLC_PGE_APPLICATION -//#include "olcPixelGameEngine.h" -// -//#define OLC_PGEX_GRAPHICS3D -//#include "olcPGEX_Graphics3D.h" -// -// -// -//enum CELLTYPE -//{ -// CELL_BLANK = 0, -// CELL_GRASS = 1, -// CELL_CONCRETE = 2, -// CELL_WATER = 3, -// CELL_BUILDING = 4, -// CELL_ROAD_H = 5, -// CELL_ROAD_V = 6, -// CELL_ROAD_C1 = 7, -// CELL_ROAD_C2 = 8, -// CELL_ROAD_C3 = 9, -// CELL_ROAD_C4 = 10, -// CELL_ROAD_T1 = 11, -// CELL_ROAD_T2 = 12, -// CELL_ROAD_T3 = 13, -// CELL_ROAD_T4 = 14, -// CELL_ROAD_X = 15, -//}; -// -//struct cCityCell -//{ -// int nType = 5;// CELL_GRASS; -//}; -// -//class cCityMap -//{ -//public: -// // Construct a "blank" city w units wide by h units high -// cCityMap(int w, int h); -// -// // Cleans up city, like Batman -// ~cCityMap(); -// -// -//public: -// // Return width of city in cells -// int GetWidth(); -// // Return height of city in cells -// int GetHeight(); -// // Return a specific cell reference if inside city limits, or nullptr -// cCityCell* Cell(int x, int y); -// -//private: -// int m_nWidth = 0; -// int m_nHeight = 0; -// cCityCell *m_pCells = nullptr; -// -//private: -// // Creates a "default" city of specified size -// void CreateCity(int w, int h); -// // Destroy city -// void ReleaseCity(); -//}; -// -//cCityMap::cCityMap(int w, int h) -//{ -// CreateCity(w, h); -//} -// -//cCityMap::~cCityMap() -//{ -// //ReleaseCity(); -//} -// -//int cCityMap::GetWidth() -//{ -// return m_nWidth; -//} -// -//int cCityMap::GetHeight() -//{ -// return m_nHeight; -//} -// -//cCityCell* cCityMap::Cell(int x, int y) -//{ -// if (x >= 0 && x < m_nWidth && y >= 0 && y < m_nHeight) -// return &m_pCells[y*m_nWidth + x]; -// else -// return nullptr; -//} -// -//void cCityMap::CreateCity(int w, int h) -//{ -// //ReleaseCity(); -// m_nWidth = w; -// m_nHeight = h; -// m_pCells = new cCityCell[m_nHeight * m_nWidth]; -// -// for (int x = 0; x < m_nWidth; x++) -// { -// for (int y = 0; y < m_nHeight; y++) -// { -// //m_pCells[y*m_nWidth + x] = new cCityCell(); -// //Cell(x, y)->bRoad = false; -// //Cell(x, y)->nHeight = 0; -// //Cell(x, y)->nWorldX = x; -// //Cell(x, y)->nWorldY = y; -// Cell(x, y)->nType = CELL_GRASS; -// //Cell(x, y)->bBuilding = false; -// } -// } -//} -// -//void cCityMap::ReleaseCity() -//{ -// if (m_pCells != nullptr) delete m_pCells; -// m_nWidth = 0; -// m_nHeight = 0; -//} -// -// -//class cCarCrimeCity : public olc::PixelGameEngine -//{ -//public: -// cCarCrimeCity() -// { -// sAppName = "Car Crime City"; -// } -// -// ~cCarCrimeCity() -// { -// } -// -// bool OnUserCreate() -// { -// // Initialise PGEX 3D -// olc::GFX3D::ConfigureDisplay(); -// -// // Create Default city -// pCity = new cCityMap(64, 32);// cGameSettings::nDefaultMapWidth, cGameSettings::nDefaultMapHeight); -// -// -// // A simple flat unit quad -// meshQuad.tris = -// { -// { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::RED }, -// { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::RED}, -// }; -// -// -// sprOld = new olc::Sprite("assets/system/grass1.png"); -// -// -// -// SetDrawTarget(nullptr); -// return true; -// } -// -// -// bool OnUserUpdate(float fElapsedTime) -// { -// // User Input -// if (GetKey(olc::Key::W).bHeld) vCamera.y -= 2.0f * fElapsedTime; -// if (GetKey(olc::Key::S).bHeld) vCamera.y += 2.0f * fElapsedTime; -// if (GetKey(olc::Key::A).bHeld) vCamera.x -= 2.0f * fElapsedTime; -// if (GetKey(olc::Key::D).bHeld) vCamera.x += 2.0f * fElapsedTime; -// if (GetKey(olc::Key::Z).bHeld) vCamera.z += 10.0f * fElapsedTime; -// if (GetKey(olc::Key::X).bHeld) vCamera.z -= 10.0f * fElapsedTime; -// -// -// vEye = vCamera; -// -// // Perform Ray casting to calculate visible world extents and mouse position -// olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir); -// olc::GFX3D::mat4x4 matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f); -// olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp); -// -// -// -// // Render Scene -// Clear(olc::BLUE); -// olc::GFX3D::ClearDepth(); -// -// // Create rendering pipeline -// olc::GFX3D::PipeLine pipe; -// pipe.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f, 0.0f, 0.0f, (float)ScreenWidth(), (float)ScreenHeight()); -// pipe.SetCamera(vEye, vLookTarget, vUp); -// -// -// -// int nStartX = 0; -// int nEndX = pCity->GetWidth(); -// int nStartY = 0; -// int nEndY = pCity->GetHeight(); -// -// // Render Ground, Roads, Walls & Buildings -// for (int x = nStartX; x < nEndX; x++) -// { -// if (x == 15) -// int k = 7; -// -// for (int y = nStartY; y < nEndY; y++) -// { -// -// -// switch (pCity->Cell(x, y)->nType) -// { -// case CELL_GRASS: -// { -// olc::GFX3D::mat4x4 matWorld; -// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f); -// pipe.SetTransform(matWorld); -// pipe.SetTexture(sprOld); -// //pipe.SetTexture(vecSpriteSystem[0]); -// //pipe.Render(vecMeshSystem[0].tris); -// pipe.Render(meshQuad.tris); -// //pipe.Render(vecMeshSystem[0].tris, olc::GFX3D::RENDER_FLAT); -// break; -// } -// -// -// default: -// { -// olc::GFX3D::mat4x4 matWorld; -// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f); -// pipe.SetTransform(matWorld); -// pipe.Render(meshQuad.tris, olc::GFX3D::RENDER_WIRE); -// break; -// } -// } -// -// -// -// -// } -// } -// -// -// -// return true; -// } -// -// bool OnUserDestroy() -// { -// return true; -// } -// -// -//private: -// olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -10.0f }; -// olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f }; -// olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -10.0f }; -// olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f }; -// -// -// -// olc::Sprite *sprOld = nullptr; -// olc::GFX3D::mesh meshQuad; -// -// cCityMap *pCity = nullptr; -// -// -// -//}; -// -//int main() -//{ -// // Load the settings singleton -// /*cGameSettings config; -// if (!config.LoadConfigFile("assets/config.lua")) -// { -// std::cout << "Failed to load '/assets/config.lua'" << std::endl; -// std::cout << " -> Using default configuration" << std::endl; -// }*/ -// -// // Start the PixelGameEngine -// cCarCrimeCity game; -// if (game.Construct(256, 240, 4, 4))// config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight)) -// game.Start(); -// -// // Exit! -// return 0; +/* + Top Down City Based Car Crime Game - Part #2 + "Colin, I hope you're shooting 600+ wherever you are buddy. RIP." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Instructions: + ~~~~~~~~~~~~~ + Scroll with middle mouse wheel, TAB toggle edit mode, R to place road + P to place pavement, Q to place building, Arrow keys to drive car + + Relevant Video: https://youtu.be/fIV6P1W-wuo + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + + +#include "cGameSettings.h" +#include "cCarCrimeCity.h" + + +int main() +{ + // Load the settings singleton + cGameSettings config; + if (!config.LoadConfigFile("assets/config.lua")) + { + std::cout << "Failed to load '/assets/config.lua'" << std::endl; + std::cout << " -> Using default configuration" << std::endl; + } + + // Start the PixelGameEngine + cCarCrimeCity game; + if (game.Construct(config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight, config.bFullScreen)) + game.Start(); + + // Exit! + return 0; +} + +//#define OLC_PGE_APPLICATION +//#include "olcPixelGameEngine.h" +// +//#define OLC_PGEX_GRAPHICS3D +//#include "olcPGEX_Graphics3D.h" +// +// +// +//enum CELLTYPE +//{ +// CELL_BLANK = 0, +// CELL_GRASS = 1, +// CELL_CONCRETE = 2, +// CELL_WATER = 3, +// CELL_BUILDING = 4, +// CELL_ROAD_H = 5, +// CELL_ROAD_V = 6, +// CELL_ROAD_C1 = 7, +// CELL_ROAD_C2 = 8, +// CELL_ROAD_C3 = 9, +// CELL_ROAD_C4 = 10, +// CELL_ROAD_T1 = 11, +// CELL_ROAD_T2 = 12, +// CELL_ROAD_T3 = 13, +// CELL_ROAD_T4 = 14, +// CELL_ROAD_X = 15, +//}; +// +//struct cCityCell +//{ +// int nType = 5;// CELL_GRASS; +//}; +// +//class cCityMap +//{ +//public: +// // Construct a "blank" city w units wide by h units high +// cCityMap(int w, int h); +// +// // Cleans up city, like Batman +// ~cCityMap(); +// +// +//public: +// // Return width of city in cells +// int GetWidth(); +// // Return height of city in cells +// int GetHeight(); +// // Return a specific cell reference if inside city limits, or nullptr +// cCityCell* Cell(int x, int y); +// +//private: +// int m_nWidth = 0; +// int m_nHeight = 0; +// cCityCell *m_pCells = nullptr; +// +//private: +// // Creates a "default" city of specified size +// void CreateCity(int w, int h); +// // Destroy city +// void ReleaseCity(); +//}; +// +//cCityMap::cCityMap(int w, int h) +//{ +// CreateCity(w, h); +//} +// +//cCityMap::~cCityMap() +//{ +// //ReleaseCity(); +//} +// +//int cCityMap::GetWidth() +//{ +// return m_nWidth; +//} +// +//int cCityMap::GetHeight() +//{ +// return m_nHeight; +//} +// +//cCityCell* cCityMap::Cell(int x, int y) +//{ +// if (x >= 0 && x < m_nWidth && y >= 0 && y < m_nHeight) +// return &m_pCells[y*m_nWidth + x]; +// else +// return nullptr; +//} +// +//void cCityMap::CreateCity(int w, int h) +//{ +// //ReleaseCity(); +// m_nWidth = w; +// m_nHeight = h; +// m_pCells = new cCityCell[m_nHeight * m_nWidth]; +// +// for (int x = 0; x < m_nWidth; x++) +// { +// for (int y = 0; y < m_nHeight; y++) +// { +// //m_pCells[y*m_nWidth + x] = new cCityCell(); +// //Cell(x, y)->bRoad = false; +// //Cell(x, y)->nHeight = 0; +// //Cell(x, y)->nWorldX = x; +// //Cell(x, y)->nWorldY = y; +// Cell(x, y)->nType = CELL_GRASS; +// //Cell(x, y)->bBuilding = false; +// } +// } +//} +// +//void cCityMap::ReleaseCity() +//{ +// if (m_pCells != nullptr) delete m_pCells; +// m_nWidth = 0; +// m_nHeight = 0; +//} +// +// +//class cCarCrimeCity : public olc::PixelGameEngine +//{ +//public: +// cCarCrimeCity() +// { +// sAppName = "Car Crime City"; +// } +// +// ~cCarCrimeCity() +// { +// } +// +// bool OnUserCreate() +// { +// // Initialise PGEX 3D +// olc::GFX3D::ConfigureDisplay(); +// +// // Create Default city +// pCity = new cCityMap(64, 32);// cGameSettings::nDefaultMapWidth, cGameSettings::nDefaultMapHeight); +// +// +// // A simple flat unit quad +// meshQuad.tris = +// { +// { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, olc::RED }, +// { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, olc::RED}, +// }; +// +// +// sprOld = new olc::Sprite("assets/system/grass1.png"); +// +// +// +// SetDrawTarget(nullptr); +// return true; +// } +// +// +// bool OnUserUpdate(float fElapsedTime) +// { +// // User Input +// if (GetKey(olc::Key::W).bHeld) vCamera.y -= 2.0f * fElapsedTime; +// if (GetKey(olc::Key::S).bHeld) vCamera.y += 2.0f * fElapsedTime; +// if (GetKey(olc::Key::A).bHeld) vCamera.x -= 2.0f * fElapsedTime; +// if (GetKey(olc::Key::D).bHeld) vCamera.x += 2.0f * fElapsedTime; +// if (GetKey(olc::Key::Z).bHeld) vCamera.z += 10.0f * fElapsedTime; +// if (GetKey(olc::Key::X).bHeld) vCamera.z -= 10.0f * fElapsedTime; +// +// +// vEye = vCamera; +// +// // Perform Ray casting to calculate visible world extents and mouse position +// olc::GFX3D::vec3d vLookTarget = olc::GFX3D::Math::Vec_Add(vEye, vLookDir); +// olc::GFX3D::mat4x4 matProj = olc::GFX3D::Math::Mat_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f); +// olc::GFX3D::mat4x4 matView = olc::GFX3D::Math::Mat_PointAt(vEye, vLookTarget, vUp); +// +// +// +// // Render Scene +// Clear(olc::BLUE); +// olc::GFX3D::ClearDepth(); +// +// // Create rendering pipeline +// olc::GFX3D::PipeLine pipe; +// pipe.SetProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.5f, 1000.0f, 0.0f, 0.0f, (float)ScreenWidth(), (float)ScreenHeight()); +// pipe.SetCamera(vEye, vLookTarget, vUp); +// +// +// +// int nStartX = 0; +// int nEndX = pCity->GetWidth(); +// int nStartY = 0; +// int nEndY = pCity->GetHeight(); +// +// // Render Ground, Roads, Walls & Buildings +// for (int x = nStartX; x < nEndX; x++) +// { +// if (x == 15) +// int k = 7; +// +// for (int y = nStartY; y < nEndY; y++) +// { +// +// +// switch (pCity->Cell(x, y)->nType) +// { +// case CELL_GRASS: +// { +// olc::GFX3D::mat4x4 matWorld; +// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f); +// pipe.SetTransform(matWorld); +// pipe.SetTexture(sprOld); +// //pipe.SetTexture(vecSpriteSystem[0]); +// //pipe.Render(vecMeshSystem[0].tris); +// pipe.Render(meshQuad.tris); +// //pipe.Render(vecMeshSystem[0].tris, olc::GFX3D::RENDER_FLAT); +// break; +// } +// +// +// default: +// { +// olc::GFX3D::mat4x4 matWorld; +// matWorld = olc::GFX3D::Math::Mat_MakeTranslation((float)x, (float)y, 0.0f); +// pipe.SetTransform(matWorld); +// pipe.Render(meshQuad.tris, olc::GFX3D::RENDER_WIRE); +// break; +// } +// } +// +// +// +// +// } +// } +// +// +// +// return true; +// } +// +// bool OnUserDestroy() +// { +// return true; +// } +// +// +//private: +// olc::GFX3D::vec3d vCamera = { 0.0f, 0.0f, -10.0f }; +// olc::GFX3D::vec3d vUp = { 0.0f, 1.0f, 0.0f }; +// olc::GFX3D::vec3d vEye = { 0.0f, 0.0f, -10.0f }; +// olc::GFX3D::vec3d vLookDir = { 0.0f, 0.0f, 1.0f }; +// +// +// +// olc::Sprite *sprOld = nullptr; +// olc::GFX3D::mesh meshQuad; +// +// cCityMap *pCity = nullptr; +// +// +// +//}; +// +//int main() +//{ +// // Load the settings singleton +// /*cGameSettings config; +// if (!config.LoadConfigFile("assets/config.lua")) +// { +// std::cout << "Failed to load '/assets/config.lua'" << std::endl; +// std::cout << " -> Using default configuration" << std::endl; +// }*/ +// +// // Start the PixelGameEngine +// cCarCrimeCity game; +// if (game.Construct(256, 240, 4, 4))// config.nScreenWidth, config.nScreenHeight, config.nPixelWidth, config.nPixelHeight)) +// game.Start(); +// +// // Exit! +// return 0; //} \ No newline at end of file diff --git a/CarCrimeCity/Part2/olcPGEX_Graphics3D.h b/Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h similarity index 97% rename from CarCrimeCity/Part2/olcPGEX_Graphics3D.h rename to Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h index 072cc0d..970c021 100644 --- a/CarCrimeCity/Part2/olcPGEX_Graphics3D.h +++ b/Videos/CarCrimeCity/Part2/olcPGEX_Graphics3D.h @@ -1,1725 +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); - -} - +/* + 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/CarCrimeCity/Part2/olcPixelGameEngine.cpp b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp similarity index 96% rename from CarCrimeCity/Part2/olcPixelGameEngine.cpp rename to Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp index b534fc4..6ad6ab0 100644 --- a/CarCrimeCity/Part2/olcPixelGameEngine.cpp +++ b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.cpp @@ -1,5 +1,5 @@ -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -#define OLC_PGEX_GRAPHICS3D -#include "olcPGEX_Graphics3D.h" +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#define OLC_PGEX_GRAPHICS3D +#include "olcPGEX_Graphics3D.h" diff --git a/CarCrimeCity/Part2/olcPixelGameEngine.h b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.h similarity index 96% rename from CarCrimeCity/Part2/olcPixelGameEngine.h rename to Videos/CarCrimeCity/Part2/olcPixelGameEngine.h index c691d9d..4d13e6d 100644 --- a/CarCrimeCity/Part2/olcPixelGameEngine.h +++ b/Videos/CarCrimeCity/Part2/olcPixelGameEngine.h @@ -1,2317 +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 +/* + 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/DemoBinaries/OLC_8BitsImProc.zip b/Videos/DemoBinaries/OLC_8BitsImProc.zip similarity index 100% rename from DemoBinaries/OLC_8BitsImProc.zip rename to Videos/DemoBinaries/OLC_8BitsImProc.zip diff --git a/OneLoneCoder_PGE_8BitsImProc.cpp b/Videos/OneLoneCoder_PGE_8BitsImProc.cpp similarity index 96% rename from OneLoneCoder_PGE_8BitsImProc.cpp rename to Videos/OneLoneCoder_PGE_8BitsImProc.cpp index eae24cf..3bd6ea7 100644 --- a/OneLoneCoder_PGE_8BitsImProc.cpp +++ b/Videos/OneLoneCoder_PGE_8BitsImProc.cpp @@ -1,504 +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; -} - - +/* + 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/OneLoneCoder_PGE_Balls2.cpp b/Videos/OneLoneCoder_PGE_Balls2.cpp similarity index 96% rename from OneLoneCoder_PGE_Balls2.cpp rename to Videos/OneLoneCoder_PGE_Balls2.cpp index e66b75e..c1e3d6f 100644 --- a/OneLoneCoder_PGE_Balls2.cpp +++ b/Videos/OneLoneCoder_PGE_Balls2.cpp @@ -1,500 +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; -}; - +/* +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/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp b/Videos/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp similarity index 96% rename from OneLoneCoder_PGE_ExtensionTestGFX2D.cpp rename to Videos/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp index 7794c7d..f31ee79 100644 --- a/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp +++ b/Videos/OneLoneCoder_PGE_ExtensionTestGFX2D.cpp @@ -1,189 +1,189 @@ -/* - OneLoneCoder_PGE_ExtensionTestGFX2D.cpp - - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018 -*/ - -// Include the olcPixelGameEngine -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -// To use an extension, just include it -#define OLC_PGE_GRAPHICS2D -#include "olcPGEX_Graphics2D.h" - -class TestExtension : public olc::PixelGameEngine -{ -public: - TestExtension() - { - sAppName = "Testing Graphics2D"; - } - -public: - bool OnUserCreate() override - { - for (int i = 0; i < 16; i++) - listEvents.push_back(""); - - spr = new olc::Sprite("new_piskel.png"); - - return true; - } - - std::list listEvents; - float fTotalTime = 0.0f; - olc::Sprite *spr; - - bool OnUserUpdate(float fElapsedTime) override - { - // Clear Screen - SetPixelMode(olc::Pixel::NORMAL); - Clear(olc::BLUE); - - // Draw Primitives - DrawCircle(32, 32, 30); // Circle - DrawCircle(96, 32, 30); // Circle - - - float mx = (float)GetMouseX(); - float my = (float)GetMouseY(); - - float px1 = mx - 32, px2 = mx - 96; - float py1 = my - 32, py2 = my - 32; - float pr1 = 1.0f / sqrtf(px1*px1 + py1*py1); - float pr2 = 1.0f / sqrtf(px2*px2 + py2*py2); - px1 = 22.0f * (px1 * pr1) + 32.0f; - py1 = 22.0f * (py1 * pr1) + 32.0f; - px2 = 22.0f * (px2 * pr2) + 96.0f; - py2 = 22.0f * (py2 * pr2) + 32.0f; - FillCircle((int32_t)px1, (int32_t)py1, 8, olc::CYAN); - FillCircle((int32_t)px2, (int32_t)py2, 8, olc::CYAN); - - DrawLine(10, 70, 54, 70); // Lines - DrawLine(54, 70, 70, 54); - - DrawRect(10, 80, 54, 30); - FillRect(10, 80, 54, 30); - - // Multiline Text - std::string mpos = "Your Mouse Position is:\nX=" + std::to_string(mx) + "\nY=" + std::to_string(my); - DrawString(10, 130, mpos); - - auto AddEvent = [&](std::string s) - { - listEvents.push_back(s); - listEvents.pop_front(); - }; - - if (GetMouse(0).bPressed) AddEvent("Mouse Button 0 Down"); - if (GetMouse(0).bReleased) AddEvent("Mouse Button 0 Up"); - if (GetMouse(1).bPressed) AddEvent("Mouse Button 1 Down"); - if (GetMouse(1).bReleased) AddEvent("Mouse Button 1 Up"); - if (GetMouse(2).bPressed) AddEvent("Mouse Button 2 Down"); - if (GetMouse(2).bReleased) AddEvent("Mouse Button 2 Up"); - - - // Draw Event Log - int nLog = 0; - for (auto &s : listEvents) - { - DrawString(200, nLog * 8 + 20, s, olc::Pixel(nLog * 16, nLog * 16, nLog * 16)); - nLog++; - } - - std::string notes = "CDEFGAB"; - - - // Test Text scaling and colours - DrawString(0, 360, "Text Scale = 1", olc::WHITE, 1); - DrawString(0, 368, "Text Scale = 2", olc::BLUE, 2); - DrawString(0, 384, "Text Scale = 3", olc::RED, 3); - DrawString(0, 408, "Text Scale = 4", olc::YELLOW, 4); - DrawString(0, 440, "Text Scale = 5", olc::GREEN, 5); - - fTotalTime += fElapsedTime; - - float fAngle = fTotalTime; - - // Draw Sprite using extension, first create a transformation stack - olc::GFX2D::Transform2D t1; - - // Traslate sprite so center of image is at 0,0 - t1.Translate(-250, -35); - // Scale the sprite - t1.Scale(1 * sinf(fAngle) + 1, 1 * sinf(fAngle) + 1); - // Rotate it - t1.Rotate(fAngle*2.0f); - // Translate to 0,100 - t1.Translate(0, 100); - // Rotate different speed - t1.Rotate(fAngle / 3); - // Translate to centre of screen - t1.Translate(320, 240); - - SetPixelMode(olc::Pixel::ALPHA); - - // Use extension to draw sprite with transform applied - olc::GFX2D::DrawSprite(spr, t1); - - DrawSprite((int32_t)mx, (int32_t)my, spr, 4); - - return true; - } -}; - - -int main() -{ - TestExtension demo; - if (demo.Construct(640, 480, 2, 2)) - demo.Start(); - - return 0; +/* + OneLoneCoder_PGE_ExtensionTestGFX2D.cpp + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + +// Include the olcPixelGameEngine +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// To use an extension, just include it +#define OLC_PGE_GRAPHICS2D +#include "olcPGEX_Graphics2D.h" + +class TestExtension : public olc::PixelGameEngine +{ +public: + TestExtension() + { + sAppName = "Testing Graphics2D"; + } + +public: + bool OnUserCreate() override + { + for (int i = 0; i < 16; i++) + listEvents.push_back(""); + + spr = new olc::Sprite("new_piskel.png"); + + return true; + } + + std::list 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/OneLoneCoder_PGE_PathFinding_WaveProp.cpp b/Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp similarity index 97% rename from OneLoneCoder_PGE_PathFinding_WaveProp.cpp rename to Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp index 5b22c03..0648983 100644 --- a/OneLoneCoder_PGE_PathFinding_WaveProp.cpp +++ b/Videos/OneLoneCoder_PGE_PathFinding_WaveProp.cpp @@ -1,426 +1,426 @@ -/* - OneLoneCoder.com - Path Finding #2 - Wave Propagation & Potential Fields - "...never get lost again, so long as you know where you are" - @Javidx9 - - - Background - ~~~~~~~~~~ - A nice follow up alternative to the A* Algorithm. Wave propagation is less - applicable to multiple objects with multiple destinations, but fantatsic - for multiple objects all reaching the same destination. - - WARNING! This code is NOT OPTIMAL!! It is however very robust. There - are many ways to optimise this further. - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon/javidx9 - Homepage: https://www.onelonecoder.com - - Relevant Videos - ~~~~~~~~~~~~~~~ - Part #1 https://youtu.be/icZj67PTFhc - Part #2 https://youtu.be/0ihciMKlcP8 - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018 -*/ -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -#include -#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; +/* + 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/OneLoneCoder_PGE_PolygonCollisions1.cpp b/Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp similarity index 96% rename from OneLoneCoder_PGE_PolygonCollisions1.cpp rename to Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp index 9735fe5..e3b95c8 100644 --- a/OneLoneCoder_PGE_PolygonCollisions1.cpp +++ b/Videos/OneLoneCoder_PGE_PolygonCollisions1.cpp @@ -1,434 +1,434 @@ -/* - Convex Polygon Collision Detection - "Don't you dare try concave ones..." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Use arrow keys to control pentagon - Use WASD to control triangle - F1..F4 selects algorithm - - Relevant Video: https://youtu.be/7Ik2vowGcU0 - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -#include -#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; +/* + 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/OneLoneCoder_PGE_Polymorphism.cpp b/Videos/OneLoneCoder_PGE_Polymorphism.cpp similarity index 96% rename from OneLoneCoder_PGE_Polymorphism.cpp rename to Videos/OneLoneCoder_PGE_Polymorphism.cpp index ee19205..134cde3 100644 --- a/OneLoneCoder_PGE_Polymorphism.cpp +++ b/Videos/OneLoneCoder_PGE_Polymorphism.cpp @@ -1,536 +1,536 @@ -/* - 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); - } - } - } - - - - // 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.y), olc::YELLOW, 2); - return true; - } -}; - - -int main() -{ - Polymorphism demo; - if (demo.Construct(1600, 960, 1, 1)) - demo.Start(); - return 0; -} - - - - +/* + 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); + } + } + } + + + + // 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.y), olc::YELLOW, 2); + return true; + } +}; + + +int main() +{ + Polymorphism demo; + if (demo.Construct(1600, 960, 1, 1)) + demo.Start(); + return 0; +} + + + + diff --git a/OneLoneCoder_PGE_RobotArm1.cpp b/Videos/OneLoneCoder_PGE_RobotArm1.cpp similarity index 96% rename from OneLoneCoder_PGE_RobotArm1.cpp rename to Videos/OneLoneCoder_PGE_RobotArm1.cpp index f0b8847..8c1bcd4 100644 --- a/OneLoneCoder_PGE_RobotArm1.cpp +++ b/Videos/OneLoneCoder_PGE_RobotArm1.cpp @@ -1,272 +1,272 @@ -/* - Programming a robotic arm - "I told you, put down the screwdriver..." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Without a robot arm and an mbed there is not much you can do! - Also requires a 3rd Party PGEX UI by ZleapingBear: - https://youtu.be/bfiSjC__MCI - - - Relevant Video: https://youtu.be/ekdQ-aAB36Y - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" -#include "olcPGEX_UI.h" - -class RobotArm1 : public olc::PixelGameEngine -{ -public: - RobotArm1() - { - sAppName = "Robot Arm 1"; - } - - olc::UI_CONTAINER gui; - float fJointAngle[6]; - float fAccumulatedTime = 0.0f; - HANDLE hCom = nullptr; - - -public: - bool OnUserCreate() override - { - gui.addSlider(10, 20, 180); - gui.addSlider(10, 60, 180); - gui.addSlider(10, 100, 180); - gui.addSlider(10, 140, 180); - gui.addSlider(10, 180, 180); - gui.addSlider(10, 220, 180); - gui.setValue(0, 50); - gui.setValue(1, 50); - gui.setValue(2, 50); - gui.setValue(3, 50); - gui.setValue(4, 50); - gui.setValue(5, 50); - - // Open COM Port - hCom = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hCom == nullptr) return false; - - // Configure Protocol: 9600bps, 8N1 - DCB dcb = { 0 }; - GetCommState(hCom, &dcb); - dcb.BaudRate = CBR_9600; - dcb.ByteSize = 8; - dcb.StopBits = ONESTOPBIT; - dcb.Parity = NOPARITY; - SetCommState(hCom, &dcb); - return true; - } - - bool OnUserDestroy() override - { - if (hCom != nullptr) CloseHandle(hCom); - - return true; - } - - bool OnUserUpdate(float fElapsedTime) override - { - gui.Update(fElapsedTime); - Clear(olc::GREEN); - gui.drawUIObjects(); - - for (int i = 0; i < 6; i++) - fJointAngle[i] = (gui.getSliderFloat(i) / 100.0f) * 180.0f - 90.0f; - - unsigned char command[12]; - for (int i = 0; i < 6; i++) - { - command[i * 2 + 0] = i; - command[i * 2 + 1] = (int)(128 + fJointAngle[i]); - } - - fAccumulatedTime += fElapsedTime; - if (fAccumulatedTime > 0.05f) - { - fAccumulatedTime -= 0.05f; - DWORD bw = 0; - WriteFile(hCom, command, 12, &bw, 0); - } - - return true; - } -}; - -int main() -{ - RobotArm1 demo; - if (demo.Construct(400, 400, 2, 2)) - demo.Start(); - return 0; -} - -// Below here is the source code compiled on MBED LPC1768, using the BufferedSerial Library - -/* - -#include "mbed.h" -#include "BufferedSerial.h" - -PwmOut pin26(p26); -PwmOut pin25(p25); -PwmOut pin24(p24); -PwmOut pin23(p23); -PwmOut pin22(p22); -PwmOut pin21(p21); - -BufferedSerial uart(p9, p10); - - -class Joint -{ -private: - static const float fDutyMin = 0.03f; // -90 - static const float fDutyMax = 0.11f; // +90 - static const float fDutyRange = fDutyMax - fDutyMin; - - float fTarget; - float fPosition; - float fJointMax; - float fJointMin; - -public: - Joint(float fMin = -90.0f, float fMax = 90.0f, float fDefaultPos = 0.0f) - { - fJointMin = fMin; - fJointMax = fMax; - fPosition = 0.0f; - SetTarget(fDefaultPos); - } - - void SetTarget(float fAngle) - { - fTarget = fAngle; - if(fTarget < fJointMin) fTarget = fJointMin; - if(fTarget > fJointMax) fTarget = fJointMax; - } - - void UpdatePosition() - { - fPosition = fTarget; - } - - float GetTarget() - { - return fTarget; - } - - float GetDutyCycle() - { - float fDutyCycle = fPosition / (fJointMax - fJointMin); - fDutyCycle = (fDutyCycle * fDutyRange) + fDutyMin + (fDutyRange * 0.5f); - return fDutyCycle; - } -}; - - -int main() -{ - // Servos (MG996R) operate on 20ms period, so set - // PWM period for each pin - pin26.period(0.02f); - pin25.period(0.02f); - pin24.period(0.02f); - pin23.period(0.02f); - pin22.period(0.02f); - pin21.period(0.02f); - - Joint joint[6]; - - joint[0].SetTarget(0.0f); - joint[1].SetTarget(0.0f); - joint[2].SetTarget(0.0f); - joint[3].SetTarget(-25.0f); - joint[4].SetTarget(-20.0f); - joint[5].SetTarget(-15.0f); - - int nTargetJoint = 0; - - while(1) - { - // Read from UART - if(uart.readable()) - { - unsigned char c = (unsigned char)uart.getc(); - - if(c < 10) - nTargetJoint = c; - else - joint[nTargetJoint].SetTarget((float)c - 128); - - } - - - // Write Duty Cycles - // Update each joints position - for(int i=0; i<6; i++) - joint[i].UpdatePosition(); - - // Set PWM values for each joint - pin26.write(joint[0].GetDutyCycle()); - pin25.write(joint[1].GetDutyCycle()); - pin24.write(joint[2].GetDutyCycle()); - pin23.write(joint[3].GetDutyCycle()); - pin22.write(joint[4].GetDutyCycle()); - pin21.write(joint[5].GetDutyCycle()); - - } -} - +/* + Programming a robotic arm + "I told you, put down the screwdriver..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Instructions: + ~~~~~~~~~~~~~ + Without a robot arm and an mbed there is not much you can do! + Also requires a 3rd Party PGEX UI by ZleapingBear: + https://youtu.be/bfiSjC__MCI + + + Relevant Video: https://youtu.be/ekdQ-aAB36Y + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include "olcPGEX_UI.h" + +class RobotArm1 : public olc::PixelGameEngine +{ +public: + RobotArm1() + { + sAppName = "Robot Arm 1"; + } + + olc::UI_CONTAINER gui; + float fJointAngle[6]; + float fAccumulatedTime = 0.0f; + HANDLE hCom = nullptr; + + +public: + bool OnUserCreate() override + { + gui.addSlider(10, 20, 180); + gui.addSlider(10, 60, 180); + gui.addSlider(10, 100, 180); + gui.addSlider(10, 140, 180); + gui.addSlider(10, 180, 180); + gui.addSlider(10, 220, 180); + gui.setValue(0, 50); + gui.setValue(1, 50); + gui.setValue(2, 50); + gui.setValue(3, 50); + gui.setValue(4, 50); + gui.setValue(5, 50); + + // Open COM Port + hCom = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hCom == nullptr) return false; + + // Configure Protocol: 9600bps, 8N1 + DCB dcb = { 0 }; + GetCommState(hCom, &dcb); + dcb.BaudRate = CBR_9600; + dcb.ByteSize = 8; + dcb.StopBits = ONESTOPBIT; + dcb.Parity = NOPARITY; + SetCommState(hCom, &dcb); + return true; + } + + bool OnUserDestroy() override + { + if (hCom != nullptr) CloseHandle(hCom); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + gui.Update(fElapsedTime); + Clear(olc::GREEN); + gui.drawUIObjects(); + + for (int i = 0; i < 6; i++) + fJointAngle[i] = (gui.getSliderFloat(i) / 100.0f) * 180.0f - 90.0f; + + unsigned char command[12]; + for (int i = 0; i < 6; i++) + { + command[i * 2 + 0] = i; + command[i * 2 + 1] = (int)(128 + fJointAngle[i]); + } + + fAccumulatedTime += fElapsedTime; + if (fAccumulatedTime > 0.05f) + { + fAccumulatedTime -= 0.05f; + DWORD bw = 0; + WriteFile(hCom, command, 12, &bw, 0); + } + + return true; + } +}; + +int main() +{ + RobotArm1 demo; + if (demo.Construct(400, 400, 2, 2)) + demo.Start(); + return 0; +} + +// Below here is the source code compiled on MBED LPC1768, using the BufferedSerial Library + +/* + +#include "mbed.h" +#include "BufferedSerial.h" + +PwmOut pin26(p26); +PwmOut pin25(p25); +PwmOut pin24(p24); +PwmOut pin23(p23); +PwmOut pin22(p22); +PwmOut pin21(p21); + +BufferedSerial uart(p9, p10); + + +class Joint +{ +private: + static const float fDutyMin = 0.03f; // -90 + static const float fDutyMax = 0.11f; // +90 + static const float fDutyRange = fDutyMax - fDutyMin; + + float fTarget; + float fPosition; + float fJointMax; + float fJointMin; + +public: + Joint(float fMin = -90.0f, float fMax = 90.0f, float fDefaultPos = 0.0f) + { + fJointMin = fMin; + fJointMax = fMax; + fPosition = 0.0f; + SetTarget(fDefaultPos); + } + + void SetTarget(float fAngle) + { + fTarget = fAngle; + if(fTarget < fJointMin) fTarget = fJointMin; + if(fTarget > fJointMax) fTarget = fJointMax; + } + + void UpdatePosition() + { + fPosition = fTarget; + } + + float GetTarget() + { + return fTarget; + } + + float GetDutyCycle() + { + float fDutyCycle = fPosition / (fJointMax - fJointMin); + fDutyCycle = (fDutyCycle * fDutyRange) + fDutyMin + (fDutyRange * 0.5f); + return fDutyCycle; + } +}; + + +int main() +{ + // Servos (MG996R) operate on 20ms period, so set + // PWM period for each pin + pin26.period(0.02f); + pin25.period(0.02f); + pin24.period(0.02f); + pin23.period(0.02f); + pin22.period(0.02f); + pin21.period(0.02f); + + Joint joint[6]; + + joint[0].SetTarget(0.0f); + joint[1].SetTarget(0.0f); + joint[2].SetTarget(0.0f); + joint[3].SetTarget(-25.0f); + joint[4].SetTarget(-20.0f); + joint[5].SetTarget(-15.0f); + + int nTargetJoint = 0; + + while(1) + { + // Read from UART + if(uart.readable()) + { + unsigned char c = (unsigned char)uart.getc(); + + if(c < 10) + nTargetJoint = c; + else + joint[nTargetJoint].SetTarget((float)c - 128); + + } + + + // Write Duty Cycles + // Update each joints position + for(int i=0; i<6; i++) + joint[i].UpdatePosition(); + + // Set PWM values for each joint + pin26.write(joint[0].GetDutyCycle()); + pin25.write(joint[1].GetDutyCycle()); + pin24.write(joint[2].GetDutyCycle()); + pin23.write(joint[3].GetDutyCycle()); + pin22.write(joint[4].GetDutyCycle()); + pin21.write(joint[5].GetDutyCycle()); + + } +} + */ \ No newline at end of file diff --git a/OneLoneCoder_PGE_ShadowCasting2D.cpp b/Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp similarity index 100% rename from OneLoneCoder_PGE_ShadowCasting2D.cpp rename to Videos/OneLoneCoder_PGE_ShadowCasting2D.cpp diff --git a/OneLoneCoder_PGE_SoundTest.cpp b/Videos/OneLoneCoder_PGE_SoundTest.cpp similarity index 96% rename from OneLoneCoder_PGE_SoundTest.cpp rename to Videos/OneLoneCoder_PGE_SoundTest.cpp index 2b9da94..373d448 100644 --- a/OneLoneCoder_PGE_SoundTest.cpp +++ b/Videos/OneLoneCoder_PGE_SoundTest.cpp @@ -1,272 +1,272 @@ -/* - Simple example code for olcPGEX_Sound.h - Mind your speakers! - - You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo. - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Homepage: https://www.onelonecoder.com - Patreon: https://www.patreon.com/javidx9 - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018 -*/ - -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -#define OLC_PGEX_SOUND -#include "olcPGEX_Sound.h" - -#include - -class SoundTest : public olc::PixelGameEngine -{ -public: - SoundTest() - { - sAppName = "Sound Test"; - } - -private: - int sndSampleA; - int sndSampleB; - int sndSampleC; - bool bToggle = false; - static bool bSynthPlaying; - static float fSynthFrequency; - static float fFilterVolume; - - const olc::Key keys[12] = { olc::Key::Z, olc::Key::S, olc::Key::X, olc::Key::D, olc::Key::C, - olc::Key::V, olc::Key::G, olc::Key::B, olc::Key::H, olc::Key::N, olc::Key::J, olc::Key::M}; - - static float fPreviousSamples[128]; - static int nSamplePos; - - -private: - - // This is an optional function that allows the user to generate or synthesize sounds - // in a custom way, it is fed into the output mixer bu the extension - static float MyCustomSynthFunction(int nChannel, float fGlobalTime, float fTimeStep) - { - // Just generate a sine wave of the appropriate frequency - if (bSynthPlaying) - return sin(fSynthFrequency * 2.0f * 3.14159f * fGlobalTime); - else - return 0.0f; - } - - // This is an optional function that allows the user to filter the output from - // the internal mixer of the extension. Here you could add effects or just - // control volume. I also like to use it to extract information about - // the currently playing output waveform - static float MyCustomFilterFunction(int nChannel, float fGlobalTime, float fSample) - { - // Fundamentally just control volume - float fOutput = fSample * fFilterVolume; - - // But also add sample to list of previous samples for visualisation - fPreviousSamples[nSamplePos] = fOutput; - nSamplePos++; - nSamplePos %= 128; - - return fOutput; - } - - - bool OnUserCreate() - { - olc::SOUND::InitialiseAudio(44100, 1, 8, 512); - - sndSampleA = olc::SOUND::LoadAudioSample("SampleA.wav"); - sndSampleB = olc::SOUND::LoadAudioSample("SampleB.wav"); - sndSampleC = olc::SOUND::LoadAudioSample("SampleC.wav"); - - // Give the sound engine a hook to a custom generation function - olc::SOUND::SetUserSynthFunction(MyCustomSynthFunction); - - // Give the sound engine a hook to a custom filtering function - olc::SOUND::SetUserFilterFunction(MyCustomFilterFunction); - - return true; - } - - bool OnUserUpdate(float fElapsedTime) - { - //olc::SOUND::PlaySample(sndTest); - - auto PointInRect = [&](int x, int y, int rx, int ry, int rw, int rh) - { - return x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh); - }; - - int nMouseX = GetMouseX(); - int nMouseY = GetMouseY(); - - if(GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 16, 128, 24)) - olc::SOUND::PlaySample(sndSampleA); // Plays the sample once - - if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 48, 128, 24)) - olc::SOUND::PlaySample(sndSampleB); - - if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 80, 128, 24)) - { - bToggle = !bToggle; - if (bToggle) - { - olc::SOUND::PlaySample(sndSampleC, true); // Plays the sample in looping mode - } - else - { - olc::SOUND::StopSample(sndSampleC); - } - } - - if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 16, 90, 24)) - fFilterVolume += 2.0f * fElapsedTime; - - if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 48, 90, 24)) - fFilterVolume -= 2.0f * fElapsedTime; - - if (fFilterVolume < 0.0f) fFilterVolume = 0.0f; - if (fFilterVolume > 1.0f) fFilterVolume = 1.0f; - - // Detect keyboard - very simple synthesizer - if (IsFocused()) - { - bool bKeyIsPressed = false; - float fFrequency = 0.0f; - for (int i = 0; i < 12; i++) - { - if (GetKey(keys[i]).bHeld) - { - bKeyIsPressed = true; - float fOctaveBaseFrequency = 220.0f; - float f12thRootOf2 = pow(2.0f, 1.0f / 12.0f); - fFrequency = fOctaveBaseFrequency * powf(f12thRootOf2, (float)i); - } - } - - fSynthFrequency = fFrequency; - bSynthPlaying = bKeyIsPressed; - } - - - // Draw Buttons - Clear(olc::BLUE); - - DrawRect(16, 16, 128, 24); - DrawString(20, 20, "Play Sample A"); - - DrawRect(16, 48, 128, 24); - DrawString(20, 52, "Play Sample B"); - - DrawRect(16, 80, 128, 24); - DrawString(20, 84, (bToggle ? "Stop Sample C" : "Loop Sample C")); - - - DrawRect(160, 16, 90, 24); - DrawString(164, 20, "Volume +"); - - DrawRect(160, 48, 90, 24); - DrawString(164, 52, "Volume -"); - - - DrawString(164, 80, "Volume: " + std::to_string((int)(fFilterVolume * 10.0f))); - - // Draw Keyboard - - // White Keys - for (int i = 0; i < 7; i++) - { - FillRect(i * 16 + 8, 160, 16, 64); - DrawRect(i * 16 + 8, 160, 16, 64, olc::BLACK); - DrawString(i * 16 + 12, 212, std::string(1, "ZXCVBNM"[i]), olc::BLACK); - } - - // Black Keys - for (int i = 0; i < 6; i++) - { - if (i != 2) - { - FillRect(i * 16 + 18, 160, 12, 32, olc::BLACK); - DrawString(i * 16 + 20, 180, std::string(1, "SDFGHJ"[i]), olc::WHITE); - } - } - - // Draw visualisation - int nStartPos = (nSamplePos + 127) % 128; - - for (int i = 127; i >= 0; i--) - { - float fSample = fPreviousSamples[(nSamplePos + i) % 128]; - DrawLine(124 + i, 210, 124 + i, 210 + (int)(fSample * 20.0f), olc::RED); - } - - - return true; - } - - - // Note we must shut down the sound system too!! - bool OnUserDestroy() - { - olc::SOUND::DestroyAudio(); - return true; - } -}; - -bool SoundTest::bSynthPlaying = false; -float SoundTest::fSynthFrequency = 0.0f; -float SoundTest::fFilterVolume = 1.0f; -int SoundTest::nSamplePos = 0; -float SoundTest::fPreviousSamples[128]; - -int main() -{ - SoundTest demo; - if(demo.Construct(256, 240, 4, 4)) - demo.Start(); - - return 0; +/* + Simple example code for olcPGEX_Sound.h - Mind your speakers! + + You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#define OLC_PGEX_SOUND +#include "olcPGEX_Sound.h" + +#include + +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/OneLoneCoder_PGE_SpriteTransforms.cpp b/Videos/OneLoneCoder_PGE_SpriteTransforms.cpp similarity index 96% rename from OneLoneCoder_PGE_SpriteTransforms.cpp rename to Videos/OneLoneCoder_PGE_SpriteTransforms.cpp index 6587032..ed0d9b5 100644 --- a/OneLoneCoder_PGE_SpriteTransforms.cpp +++ b/Videos/OneLoneCoder_PGE_SpriteTransforms.cpp @@ -1,257 +1,257 @@ -/* - OneLoneCoder.com - 2D Sprite Affine Transformations - "No more 90 degree movements" - @Javidx9 - - - Background - ~~~~~~~~~~ - The sophistication of 2D engines is enhanced when the programmer is - able to rotate and scale sprites in a convenient manner. This program - shows the basics of how affine transformations accomplish this. - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Relevant Videos - ~~~~~~~~~~~~~~~ - https://youtu.be/zxwLN2blwbQ - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018 -*/ - - -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -#include -#undef min -#undef max - - -class SpriteTransforms : public olc::PixelGameEngine -{ -public: - SpriteTransforms() - { - sAppName = "Sprite Transforms"; - } - -private: - olc::Sprite *sprCar; - - struct matrix3x3 - { - float m[3][3]; - }; - - void Identity(matrix3x3 &mat) - { - mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f; - mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f; - mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f; - } - - void Translate(matrix3x3 &mat, float ox, float oy) - { - mat.m[0][0] = 1.0f; mat.m[1][0] = 0.0f; mat.m[2][0] = ox; - mat.m[0][1] = 0.0f; mat.m[1][1] = 1.0f; mat.m[2][1] = oy; - mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f; - } - - void Rotate(matrix3x3 &mat, float fTheta) - { - mat.m[0][0] = cosf(fTheta); mat.m[1][0] = sinf(fTheta); mat.m[2][0] = 0.0f; - mat.m[0][1] = -sinf(fTheta); mat.m[1][1] = cosf(fTheta); mat.m[2][1] = 0.0f; - mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f; - } - - void Scale(matrix3x3 &mat, float sx, float sy) - { - mat.m[0][0] = sx; mat.m[1][0] = 0.0f; mat.m[2][0] = 0.0f; - mat.m[0][1] = 0.0f; mat.m[1][1] = sy; mat.m[2][1] = 0.0f; - mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f; - } - - void Shear(matrix3x3 &mat, float sx, float sy) - { - mat.m[0][0] = 1.0f; mat.m[1][0] = sx; mat.m[2][0] = 0.0f; - mat.m[0][1] = sy; mat.m[1][1] = 1.0f; mat.m[2][1] = 0.0f; - mat.m[0][2] = 0.0f; mat.m[1][2] = 0.0f; mat.m[2][2] = 1.0f; - } - - void MatrixMultiply(matrix3x3 &matResult, matrix3x3 &matA, matrix3x3 &matB) - { - for (int c = 0; c < 3; c++) - { - for (int r = 0; r < 3; r++) - { - matResult.m[c][r] = matA.m[0][r] * matB.m[c][0] + - matA.m[1][r] * matB.m[c][1] + - matA.m[2][r] * matB.m[c][2]; - } - } - } - - void Forward(matrix3x3 &mat, float in_x, float in_y, float &out_x, float &out_y) - { - out_x = in_x * mat.m[0][0] + in_y * mat.m[1][0] + mat.m[2][0]; - out_y = in_x * mat.m[0][1] + in_y * mat.m[1][1] + mat.m[2][1]; - } - - void Invert(matrix3x3 &matIn, matrix3x3 &matOut) - { - float det = matIn.m[0][0] * (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) - - matIn.m[1][0] * (matIn.m[0][1] * matIn.m[2][2] - matIn.m[2][1] * matIn.m[0][2]) + - matIn.m[2][0] * (matIn.m[0][1] * matIn.m[1][2] - matIn.m[1][1] * matIn.m[0][2]); - - float idet = 1.0f / det; - matOut.m[0][0] = (matIn.m[1][1] * matIn.m[2][2] - matIn.m[1][2] * matIn.m[2][1]) * idet; - matOut.m[1][0] = (matIn.m[2][0] * matIn.m[1][2] - matIn.m[1][0] * matIn.m[2][2]) * idet; - matOut.m[2][0] = (matIn.m[1][0] * matIn.m[2][1] - matIn.m[2][0] * matIn.m[1][1]) * idet; - matOut.m[0][1] = (matIn.m[2][1] * matIn.m[0][2] - matIn.m[0][1] * matIn.m[2][2]) * idet; - matOut.m[1][1] = (matIn.m[0][0] * matIn.m[2][2] - matIn.m[2][0] * matIn.m[0][2]) * idet; - matOut.m[2][1] = (matIn.m[0][1] * matIn.m[2][0] - matIn.m[0][0] * matIn.m[2][1]) * idet; - matOut.m[0][2] = (matIn.m[0][1] * matIn.m[1][2] - matIn.m[0][2] * matIn.m[1][1]) * idet; - matOut.m[1][2] = (matIn.m[0][2] * matIn.m[1][0] - matIn.m[0][0] * matIn.m[1][2]) * idet; - matOut.m[2][2] = (matIn.m[0][0] * matIn.m[1][1] - matIn.m[0][1] * matIn.m[1][0]) * idet; - } - - - float fRotate = 0.0f; - -public: - bool OnUserCreate() override - { - sprCar = new olc::Sprite("car_top1.png"); - - return true; - } - - bool OnUserUpdate(float fElapsedTime) override - { - - if (GetKey(olc::Key::Z).bHeld) fRotate -= 2.0f * fElapsedTime; - if (GetKey(olc::Key::X).bHeld) fRotate += 2.0f * fElapsedTime; - - - Clear(olc::DARK_CYAN); - - SetPixelMode(olc::Pixel::ALPHA); - //DrawSprite(0, 0, sprCar, 3); - - - matrix3x3 matFinal, matA, matB, matC, matFinalInv; - Translate(matA, -100, -50); - Rotate(matB, fRotate); - MatrixMultiply(matC, matB, matA); - - Translate(matA, (float)ScreenWidth()/2, (float)ScreenHeight()/2); - MatrixMultiply(matFinal, matA, matC); - - Invert(matFinal, matFinalInv); - - // Draws the dumb way, but leaves gaps - /*for (int x = 0; x < sprCar->width; x++) - { - for (int y = 0; y < sprCar->height; y++) - { - olc::Pixel p = sprCar->GetPixel(x, y); - - float nx, ny; - Forward(matFinal, (float)x, (float)y, nx, ny); - Draw(nx, ny, p); - } - }*/ - - // Work out bounding box of sprite post-transformation - // by passing through sprite corner locations into - // transformation matrix - float ex, ey; - float sx, sy; - float px, py; - - Forward(matFinal, 0.0f, 0.0f, px, py); - sx = px; sy = py; - ex = px; ey = py; - - Forward(matFinal, (float)sprCar->width, (float)sprCar->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - Forward(matFinal, 0.0f, (float)sprCar->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - Forward(matFinal, (float)sprCar->width, 0.0f, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - // Use transformed corner locations in screen space to establish - // region of pixels to fill, using inverse transform to sample - // sprite at suitable locations. - for (int x = sx; x < ex; x++) - { - for (int y = sy; y < ey; y++) - { - float nx, ny; - Forward(matFinalInv, (float)x, (float)y, nx, ny); - olc::Pixel p = sprCar->GetPixel((int32_t)(nx + 0.5f), (int32_t)(ny + 0.5f)); - Draw(x, y, p); - } - } - - SetPixelMode(olc::Pixel::NORMAL); - - return true; - } -}; - -int main() -{ - SpriteTransforms demo; - if (demo.Construct(256, 240, 4, 4)) - demo.Start(); - return 0; +/* + OneLoneCoder.com - 2D Sprite Affine Transformations + "No more 90 degree movements" - @Javidx9 + + + Background + ~~~~~~~~~~ + The sophistication of 2D engines is enhanced when the programmer is + able to rotate and scale sprites in a convenient manner. This program + shows the basics of how affine transformations accomplish this. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/zxwLN2blwbQ + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include +#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/OneLoneCoder_PGE_olcEngine3D.cpp b/Videos/OneLoneCoder_PGE_olcEngine3D.cpp similarity index 100% rename from OneLoneCoder_PGE_olcEngine3D.cpp rename to Videos/OneLoneCoder_PGE_olcEngine3D.cpp diff --git a/SampleA.wav b/Videos/SampleA.wav similarity index 100% rename from SampleA.wav rename to Videos/SampleA.wav diff --git a/SampleB.wav b/Videos/SampleB.wav similarity index 100% rename from SampleB.wav rename to Videos/SampleB.wav diff --git a/SampleC.wav b/Videos/SampleC.wav similarity index 100% rename from SampleC.wav rename to Videos/SampleC.wav diff --git a/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp b/Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp similarity index 97% rename from SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp rename to Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp index cd38cb8..6bfe4d3 100644 --- a/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp +++ b/Videos/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp @@ -1,1509 +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; -} + +/* + 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/SavingSedit/Zix_PGE_Controller.h b/Videos/SavingSedit/Zix_PGE_Controller.h similarity index 96% rename from SavingSedit/Zix_PGE_Controller.h rename to Videos/SavingSedit/Zix_PGE_Controller.h index 351e486..c68dfed 100644 --- a/SavingSedit/Zix_PGE_Controller.h +++ b/Videos/SavingSedit/Zix_PGE_Controller.h @@ -1,224 +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 +#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/SavingSedit/licence.txt b/Videos/SavingSedit/licence.txt similarity index 97% rename from SavingSedit/licence.txt rename to Videos/SavingSedit/licence.txt index 3e4e818..be2bc61 100644 --- a/SavingSedit/licence.txt +++ b/Videos/SavingSedit/licence.txt @@ -1,42 +1,42 @@ -License (OLC-3) -~~~~~~~~~~~~~~~ - -Copyright 2018 - 2019 OneLoneCoder.com - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions or derivations of source code must retain the above -copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions or derivative works in binary form must reproduce -the above copyright notice. This list of conditions and the following -disclaimer must be reproduced in the documentation and/or other -materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its -contributors may be used to endorse or promote products derived -from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Links -~~~~~ -YouTube: https://www.youtube.com/javidx9 -Discord: https://discord.gg/WhwHUMV -Twitter: https://www.twitter.com/javidx9 -Twitch: https://www.twitch.tv/javidx9 -GitHub: https://www.github.com/onelonecoder -Homepage: https://www.onelonecoder.com -Patreon: https://www.patreon.com/javidx9 +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2018 - 2019 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above +copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce +the above copyright notice. This list of conditions and the following +disclaimer must be reproduced in the documentation and/or other +materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Links +~~~~~ +YouTube: https://www.youtube.com/javidx9 +Discord: https://discord.gg/WhwHUMV +Twitter: https://www.twitter.com/javidx9 +Twitch: https://www.twitch.tv/javidx9 +GitHub: https://www.github.com/onelonecoder +Homepage: https://www.onelonecoder.com +Patreon: https://www.patreon.com/javidx9 diff --git a/SavingSedit/olcPGEX_Graphics2D.h b/Videos/SavingSedit/olcPGEX_Graphics2D.h similarity index 97% rename from SavingSedit/olcPGEX_Graphics2D.h rename to Videos/SavingSedit/olcPGEX_Graphics2D.h index 146e7c3..c77be85 100644 --- a/SavingSedit/olcPGEX_Graphics2D.h +++ b/Videos/SavingSedit/olcPGEX_Graphics2D.h @@ -1,313 +1,313 @@ -/* - olcPGEX_Graphics2D.h - - +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine Extension | - | Advanced 2D Rendering - v0.4 | - +-------------------------------------------------------------+ - - What is this? - ~~~~~~~~~~~~~ - This is an extension to the olcPixelGameEngine, which provides - advanced olc::Sprite manipulation and drawing routines. To use - it, simply include this header file. - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018 - 2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - -/* - Matrices stored as [Column][Row] (i.e. x, y) - - |C0R0 C1R0 C2R0| | x | | x'| - |C0R1 C1R1 C2R1| * | y | = | y'| - |C0R2 C1R2 C2R2| |1.0| | - | -*/ - - - -#ifndef OLC_PGEX_GFX2D -#define OLC_PGEX_GFX2D - -#include -#undef min -#undef max - -namespace olc -{ - // Container class for Advanced 2D Drawing functions - class GFX2D : public olc::PGEX - { - // A representation of an affine transform, used to rotate, scale, offset & shear space - public: - class Transform2D - { - public: - Transform2D(); - - public: - // Set this transformation to unity - void Reset(); - // Append a rotation of fTheta radians to this transform - void Rotate(float fTheta); - // Append a translation (ox, oy) to this transform - void Translate(float ox, float oy); - // Append a scaling operation (sx, sy) to this transform - void Scale(float sx, float sy); - // Append a shear operation (sx, sy) to this transform - void Shear(float sx, float sy); - - void Perspective(float ox, float oy); - // Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) - void Forward(float in_x, float in_y, float &out_x, float &out_y); - // Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) - void Backward(float in_x, float in_y, float &out_x, float &out_y); - // Regenerate the Inverse Transformation - void Invert(); - - private: - void Multiply(); - float matrix[4][3][3]; - int nTargetMatrix; - int nSourceMatrix; - bool bDirty; - }; - - public: - // Draws a sprite with the transform applied - static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); - }; -} - - -#ifdef OLC_PGE_GRAPHICS2D -#undef OLC_PGE_GRAPHICS2D - -namespace olc -{ - void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform) - { - if (sprite == nullptr) - return; - - // Work out bounding rectangle of sprite - float ex, ey; - float sx, sy; - float px, py; - - transform.Forward(0.0f, 0.0f, sx, sy); - px = sx; py = sy; - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward((float)sprite->width, (float)sprite->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward(0.0f, (float)sprite->height, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - transform.Forward((float)sprite->width, 0.0f, px, py); - sx = std::min(sx, px); sy = std::min(sy, py); - ex = std::max(ex, px); ey = std::max(ey, py); - - // Perform inversion of transform if required - transform.Invert(); - - if (ex < sx) - std::swap(ex, sx); - if (ey < sy) - std::swap(ey, sy); - - // Iterate through render space, and sample Sprite from suitable texel location - for (float i = sx; i < ex; i++) - { - for (float j = sy; j < ey; j++) - { - float ox, oy; - transform.Backward(i, j, ox, oy); - pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f))); - } - } - } - - olc::GFX2D::Transform2D::Transform2D() - { - Reset(); - } - - void olc::GFX2D::Transform2D::Reset() - { - nTargetMatrix = 0; - nSourceMatrix = 1; - bDirty = true; - - // Columns Then Rows - - // Matrices 0 & 1 are used as swaps in Transform accumulation - matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f; - matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f; - matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f; - - matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f; - matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f; - matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f; - - // Matrix 2 is a cache matrix to hold the immediate transform operation - // Matrix 3 is a cache matrix to hold the inverted transform - } - - void olc::GFX2D::Transform2D::Multiply() - { - for (int c = 0; c < 3; c++) - { - for (int r = 0; r < 3; r++) - { - matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] + - matrix[2][1][r] * matrix[nSourceMatrix][c][1] + - matrix[2][2][r] * matrix[nSourceMatrix][c][2]; - } - } - - std::swap(nTargetMatrix, nSourceMatrix); - bDirty = true; // Any transform multiply dirties the inversion - } - - void olc::GFX2D::Transform2D::Rotate(float fTheta) - { - // Construct Rotation Matrix - matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f; - matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Scale(float sx, float sy) - { - // Construct Scale Matrix - matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Shear(float sx, float sy) - { - // Construct Shear Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Translate(float ox, float oy) - { - // Construct Translate Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy; - matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Perspective(float ox, float oy) - { - // Construct Translate Matrix - matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; - matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; - matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f; - Multiply(); - } - - void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y) - { - out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0]; - out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1]; - float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2]; - if (out_z != 0) - { - out_x /= out_z; - out_y /= out_z; - } - } - - void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y) - { - out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0]; - out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1]; - float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2]; - if (out_z != 0) - { - out_x /= out_z; - out_y /= out_z; - } - } - - void olc::GFX2D::Transform2D::Invert() - { - if (bDirty) // Obviously costly so only do if needed - { - float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) - - matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) + - matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]); - - float idet = 1.0f / det; - matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet; - matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet; - matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet; - matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet; - matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet; - matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet; - matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet; - matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet; - matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet; - bDirty = false; - } - } -} - -#endif +/* + olcPGEX_Graphics2D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Advanced 2D Rendering - v0.4 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + advanced olc::Sprite manipulation and drawing routines. To use + it, simply include this header file. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +/* + Matrices stored as [Column][Row] (i.e. x, y) + + |C0R0 C1R0 C2R0| | x | | x'| + |C0R1 C1R1 C2R1| * | y | = | y'| + |C0R2 C1R2 C2R2| |1.0| | - | +*/ + + + +#ifndef OLC_PGEX_GFX2D +#define OLC_PGEX_GFX2D + +#include +#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/SavingSedit/olcPGEX_Sound.h b/Videos/SavingSedit/olcPGEX_Sound.h similarity index 96% rename from SavingSedit/olcPGEX_Sound.h rename to Videos/SavingSedit/olcPGEX_Sound.h index 28a7759..55094aa 100644 --- a/SavingSedit/olcPGEX_Sound.h +++ b/Videos/SavingSedit/olcPGEX_Sound.h @@ -1,906 +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 +/* + 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/SavingSedit/olcPGEX_TileMaps_new.h b/Videos/SavingSedit/olcPGEX_TileMaps_new.h similarity index 96% rename from SavingSedit/olcPGEX_TileMaps_new.h rename to Videos/SavingSedit/olcPGEX_TileMaps_new.h index f09d3d1..b89a7c2 100644 --- a/SavingSedit/olcPGEX_TileMaps_new.h +++ b/Videos/SavingSedit/olcPGEX_TileMaps_new.h @@ -1,381 +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; - } - -} +#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/SavingSedit/olcPixelGameEngine.h b/Videos/SavingSedit/olcPixelGameEngine.h similarity index 96% rename from SavingSedit/olcPixelGameEngine.h rename to Videos/SavingSedit/olcPixelGameEngine.h index 67032fb..a6f36ed 100644 --- a/SavingSedit/olcPixelGameEngine.h +++ b/Videos/SavingSedit/olcPixelGameEngine.h @@ -1,2317 +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 +/* + 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/car_top1.png b/Videos/car_top1.png similarity index 100% rename from car_top1.png rename to Videos/car_top1.png diff --git a/light_cast.png b/Videos/light_cast.png similarity index 100% rename from light_cast.png rename to Videos/light_cast.png diff --git a/logo_long.png b/Videos/logo_long.png similarity index 100% rename from logo_long.png rename to Videos/logo_long.png diff --git a/mountains.obj b/Videos/mountains.obj similarity index 100% rename from mountains.obj rename to Videos/mountains.obj 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/olcPixelGameEngine.h b/Videos/olcPixelGameEngine.h new file mode 100644 index 0000000..0d24e8b --- /dev/null +++ b/Videos/olcPixelGameEngine.h @@ -0,0 +1,2353 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.19 | + | "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 96K YouTube followers, + 47 Patreons and 4.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, 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 }; + + 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 :( */ } + }; + + 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, 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]; + +#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; + } + + bool Pixel::operator==(const Pixel& p) const + { + return n == p.n; + } + + bool Pixel::operator!=(const Pixel& p) const + { + return n != p.n; + } + + //========================================================== + + 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, 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; + +#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) + { + delete pDefaultDrawTarget; + nScreenWidth = w; + nScreenHeight = h; + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + glClear(GL_COLOR_BUFFER_BIT); +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + 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; + + // 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 && !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); + } +#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 && !bEnableVSYNC) + 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/zombie.png b/Videos/zombie.png similarity index 100% rename from zombie.png rename to Videos/zombie.png diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index 5947dfb..f9f3a08 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -2,7 +2,7 @@ olcPixelGameEngine.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v1.19 | + | OneLoneCoder Pixel Game Engine v1.20 | | "Like the command prompt console one, but not..." - javidx9 | +-------------------------------------------------------------+ @@ -119,10 +119,12 @@ I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice Ralakus, Gorbit99, raoul, joshinils, benedani & MagetzUb for advice, ideas and - testing, and I'd like to extend my appreciation to the 96K YouTube followers, - 47 Patreons and 4.5K Discord server members who give me the motivation to keep + testing, and I'd like to extend my appreciation to the 101K YouTube followers, + 52 Patreons and 4.6K Discord server members who give me the motivation to keep going with all this :D + Significant Contributors: @MaGetzUb, @slavka, @Dragoneye & @Gorbit99 + Special thanks to those who bring gifts! GnarGnarHead.......Domina Gorbit99...........Bastion, Ori & The Blind Forest @@ -176,7 +178,7 @@ #ifndef OLC_PGE_DEF #define OLC_PGE_DEF -#ifdef _WIN32 +#if defined(_WIN32) // WINDOWS specific includes ============================================== // Link to libraries #ifndef __MINGW32__ #pragma comment(lib, "user32.lib") // Visual Studio Only @@ -202,7 +204,9 @@ #include typedef BOOL(WINAPI wglSwapInterval_t) (int interval); static wglSwapInterval_t *wglSwapInterval; -#else +#endif + +#ifdef __linux__ // LINUX specific includes ============================================== #include #include #include @@ -232,6 +236,7 @@ #undef min #undef max +#define UNUSED(x) (void)(x) namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace { @@ -301,6 +306,8 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace inline v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } inline v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } inline T& operator [] (std::size_t i) { return *((T*)this + i); /* <-- D'oh :( */ } + inline operator v2d_generic() 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); } @@ -332,7 +339,7 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace ResourcePack(); ~ResourcePack(); struct sEntry : public std::streambuf { - uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); } + uint32_t nID = 0, nFileOffset = 0, nFileSize = 0; uint8_t* data = nullptr; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); } }; public: @@ -545,10 +552,12 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace bool pMouseOldState[5]{ 0 }; HWButton pMouseState[5]; -#ifdef _WIN32 +#if defined(_WIN32) HDC glDeviceContext = nullptr; HGLRC glRenderContext = nullptr; -#else +#endif + +#if defined(__linux__) GLXContext glDeviceContext = nullptr; GLXContext glRenderContext = nullptr; #endif @@ -569,13 +578,15 @@ namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace void olc_ConstructFontSheet(); -#ifdef _WIN32 +#if defined(_WIN32) // Windows specific window handling HWND olc_hWnd = nullptr; HWND olc_WindowCreate(); std::wstring wsAppName; static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); -#else +#endif + +#if defined(__linux__) // Non-Windows specific window handling Display* olc_Display = nullptr; Window olc_WindowRoot; @@ -654,19 +665,23 @@ namespace olc //========================================================== +#if defined(_WIN32) std::wstring ConvertS2W(std::string s) - { -#ifdef _WIN32 + { +#ifdef __MINGW32__ + wchar_t *buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); wchar_t* buffer = new wchar_t[count]; - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif std::wstring w(buffer); delete[] buffer; return w; -#else - return L"SVN FTW!"; -#endif } +#endif Sprite::Sprite() { @@ -756,18 +771,10 @@ namespace olc olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) { -#ifdef _WIN32 + UNUSED(pack); +#if defined(_WIN32) // Use GDI+ - std::wstring wsImageFile; -#ifdef __MINGW32__ - wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; - mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); - buffer[sImageFile.length()] = L'\0'; - wsImageFile = buffer; - delete [] buffer; -#else - wsImageFile = ConvertS2W(sImageFile); -#endif + std::wstring wsImageFile = ConvertS2W(sImageFile); Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); if (bmp == nullptr) return olc::NO_FILE; @@ -785,7 +792,9 @@ namespace olc } delete bmp; return olc::OK; -#else +#endif + +#if defined(__linux__) //////////////////////////////////////////////////////////////////////////// // Use libpng, Thanks to Guillaume Cottenceau // https://gist.github.com/niw/5963798 @@ -1094,12 +1103,8 @@ namespace olc if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) return olc::FAIL; -#ifdef _WIN32 -#ifdef UNICODE -#ifndef __MINGW32__ +#if defined(_WIN32) && defined(UNICODE) && !defined(__MINGW32__) wsAppName = ConvertS2W(sAppName); -#endif -#endif #endif // Load the default font sheet olc_ConstructFontSheet(); @@ -1119,11 +1124,15 @@ namespace olc pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); SetDrawTarget(nullptr); glClear(GL_COLOR_BUFFER_BIT); -#ifdef _WIN32 + +#if defined(_WIN32) SwapBuffers(glDeviceContext); -#else +#endif + +#if defined(__linux__) glXSwapBuffers(olc_Display, olc_Window); #endif + glClear(GL_COLOR_BUFFER_BIT); olc_UpdateViewport(); } @@ -1134,21 +1143,11 @@ namespace olc if (!olc_WindowCreate()) return olc::FAIL; - // Load libraries required for PNG file interaction -//#ifdef _WIN32 -// // Windows use GDI+ -// Gdiplus::GdiplusStartupInput startupInput; -// ULONG_PTR token; -// Gdiplus::GdiplusStartup(&token, &startupInput, NULL); -//#else -// // Linux use libpng -// -//#endif // Start the thread bAtomActive = true; std::thread t = std::thread(&PixelGameEngine::EngineThread, this); -#ifdef _WIN32 +#if defined(_WIN32) // Handle Windows Message Loop MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) @@ -1521,8 +1520,10 @@ namespace olc else t2x += signx2; } next2: - if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; - if (maxxt1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxxt1x) minx = t1x; if (minx>t2x) minx = t2x; - if (maxxt1x) minx = t1x; + if (minx>t2x) minx = t2x; + if (maxx