├── .gitignore ├── LICENCE.md ├── README.md ├── contributions └── README.md ├── examples ├── TEST_Animate2D.cpp ├── TEST_Camera2D.cpp ├── TEST_Hardware3D.cpp ├── TEST_QuickGUI.cpp └── TEST_Shaders.cpp ├── extensions ├── olcPGEX_Graphics2D.h ├── olcPGEX_Graphics3D.h ├── olcPGEX_Network.h ├── olcPGEX_PopUpMenu.h ├── olcPGEX_QuickGUI.h ├── olcPGEX_RayCastWorld.h ├── olcPGEX_Shaders.h ├── olcPGEX_Sound.h ├── olcPGEX_SplashScreen.h ├── olcPGEX_TransformedView.h └── olcPGEX_Wireframe.h ├── olcExampleProgram.cpp ├── olcPixelGameEngine.h ├── tools └── wasm │ ├── README.md │ ├── basic_template.html │ └── pge2wasm.bat └── utilities ├── olcUTIL_Animate2D.h ├── olcUTIL_Camera2D.h ├── olcUTIL_Container.h ├── olcUTIL_DataFile.h ├── olcUTIL_Geometry2D.h ├── olcUTIL_Hardware3D.h ├── olcUTIL_Palette.h └── olcUTIL_QuadTree.h /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs 6 | /utilities/olcUTIL_AffineView.h 7 | /utilities/olcUTIL_Maths.h 8 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # License (OLC-3) 2 | 3 | Copyright 2018-2023 OneLoneCoder.com 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions or derivations of source code must retain the above 10 | copyright notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions or derivative works in binary form must reproduce 13 | the above copyright notice. This list of conditions and the following 14 | disclaimer must be reproduced in the documentation and/or other 15 | materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Hang on! I'm looking for Javidx9's video source code! It's moved here: https://github.com/OneLoneCoder/Javidx9 5 | 6 | # Shameless Promotion 7 | There is a feature rich and working fork of this repo aimed at Android & IOS development: https://github.com/Johnnyg63/OLCPGEMobileVisualStudio 8 | 9 | # olcPixelGameEngine 10 | The official distribution of olcPixelGameEngine, a tool used in javidx9's YouTube videos and projects. 11 | 12 | **You only need the one file - olcPixelGameEngine.h - included in your project!** 13 | 14 | Provides a fast, richly featured, cross platform pixel drawing and user interface framework for 15 | * The development of games 16 | * Visualisation of algorithms 17 | * Prototyping and experimentation 18 | * Education 19 | 20 | olcPixelGameEngine is easily extended! for example: 21 | * 2D Affine transforms 22 | * 3D Software renderer 23 | * Controller input 24 | * Sound 25 | * Hardware interfaces 26 | 27 | olcPixelGameEngine is easy to port! Runs on: 28 | * Windows (all) 29 | * Linux / Raspberry Pi / ChromeOS 30 | * MacOS (coming soon to official, but already available in "Contributors") 31 | * PSP & Switch (Not supported by OneLoneCoder) 32 | 33 | olcPixelGameEngine has been reimplemented in other languages! 34 | * C# 35 | * Rust 36 | * Lua 37 | * Java 38 | 39 | olcPixelGameEngine is actively maintained and developed! 40 | 41 | olcPixelGameEngine is used by 100s, if not 1000s of programmers at all levels of ability! 42 | 43 | 44 | # Documentation 45 | Please see https://github.com/OneLoneCoder/olcPixelGameEngine/wiki 46 | 47 | # License (OLC-3) 48 | Copyright 2018 - 2024 OneLoneCoder.com 49 | 50 | Redistribution and use in source and binary forms, with or without 51 | modification, are permitted provided that the following conditions 52 | are met: 53 | 54 | 1. Redistributions or derivations of source code must retain the above 55 | copyright notice, this list of conditions and the following disclaimer. 56 | 57 | 2. Redistributions or derivative works in binary form must reproduce 58 | the above copyright notice. This list of conditions and the following 59 | disclaimer must be reproduced in the documentation and/or other 60 | materials provided with the distribution. 61 | 62 | 3. Neither the name of the copyright holder nor the names of its 63 | contributors may be used to endorse or promote products derived 64 | from this software without specific prior written permission. 65 | 66 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 67 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 68 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 69 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 70 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 71 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 72 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 73 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 74 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 75 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 76 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 77 | -------------------------------------------------------------------------------- /contributions/README.md: -------------------------------------------------------------------------------- 1 | # 3rd Party Contributions 2 | 3 | These source code contributions enhance the functionality of the olcPixelGameEngine header file. They are not supported by OneLoneCoder.com or javidx9 so use them at your own risk! Though this is a nice community and to get listed here you have to be trusted... 4 | 5 | ## PixelGameEngine Extensions (PGEX) 6 | * Adds a "stencil buffer" like viewport to drawing functions 7 | * https://github.com/gorbit99/olcPGEX_ViewPort 8 | * Cross Platform Controller Support 9 | * https://github.com/gorbit99/olcPGEX_Gamepad 10 | * General Font Rendering 11 | * https://github.com/gorbit99/olcPGEX_TTF 12 | * Widens support for audio files into olcPGEX_Sound.h 13 | * https://github.com/gorbit99/olcPGEX_AudioConvert 14 | * Sprite Animation, Sprite State Machine and Sprite Sheet Manipulation 15 | * https://github.com/matt-hayward/olcPGEX_AnimatedSprite 16 | * Additional colour constants 17 | * https://github.com/matt-hayward/olcPGEX_AdditionalColours 18 | * Various Sprite Handling & Camera Control Utilities 19 | * https://github.com/justinrichardsmusic/PGEv2_Extensions 20 | * DearImGUI Integration 21 | * https://github.com/dandistine/olcPGEDearImGui 22 | * Pre-loaded Font Rendering 23 | * https://github.com/Oso-Grande/olcPGEX_Font 24 | * Arc Drawing 25 | * https://github.com/AlterEgoIV/olcPGEX_Arc 26 | 27 | ## MacOS Support 28 | * These will potentially be absorbed into main build 29 | * https://github.com/MumflrFumperdink/olcPGEMac 30 | 31 | ## Android & IOS Support 32 | * Fiddlier to setup, but pretty cool once going 33 | * https://github.com/Johnnyg63/OLCPGEMobileVisualStudio 34 | 35 | ## Build Systems 36 | * Meson (https://mesonbuild.com/index.html) 37 | * https://github.com/jpakkane/pixeldemo 38 | * CMake script 39 | * https://github.com/plane000/olcPixelGameEngine/blob/master/CMakeLists.txt 40 | * CMake script for all includes Emscripten/WASM/Webby stuff 41 | * https://github.com/L0huis/PGE-CMake 42 | 43 | ## Utilities 44 | * Additional fonts and font handling tools 45 | * https://github.com/gorbit99/OLC-Font 46 | * Convert olcConsoleGameEngine ".spr" files into olc::Sprite types 47 | * https://github.com/gorbit99/SprConverter 48 | 49 | ## Customisations 50 | * Version with SDL backend, and native controller support 51 | * https://github.com/Allersnj/olcPixelGameEngineSDL 52 | 53 | ## Cool Projects 54 | Have you made something using olcPixelGameEngine? Contact me to get a link to it here! 55 | -------------------------------------------------------------------------------- /examples/TEST_Animate2D.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Animate2D.cpp -------------------------------------------------------------------------------- /examples/TEST_Camera2D.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Camera2D.cpp -------------------------------------------------------------------------------- /examples/TEST_Hardware3D.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Hardware3D.cpp -------------------------------------------------------------------------------- /examples/TEST_QuickGUI.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_QuickGUI.cpp -------------------------------------------------------------------------------- /examples/TEST_Shaders.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Shaders.cpp -------------------------------------------------------------------------------- /extensions/olcPGEX_Graphics2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_Graphics2D.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Advanced 2D Rendering - v0.5 | 7 | +-------------------------------------------------------------+ 8 | 9 | What is this? 10 | ~~~~~~~~~~~~~ 11 | This is an extension to the olcPixelGameEngine, which provides 12 | advanced olc::Sprite manipulation and drawing routines. To use 13 | it, simply include this header file. 14 | 15 | License (OLC-3) 16 | ~~~~~~~~~~~~~~~ 17 | 18 | Copyright 2018 - 2019 OneLoneCoder.com 19 | 20 | Redistribution and use in source and binary forms, with or without 21 | modification, are permitted provided that the following conditions 22 | are met: 23 | 24 | 1. Redistributions or derivations of source code must retain the above 25 | copyright notice, this list of conditions and the following disclaimer. 26 | 27 | 2. Redistributions or derivative works in binary form must reproduce 28 | the above copyright notice. This list of conditions and the following 29 | disclaimer must be reproduced in the documentation and/or other 30 | materials provided with the distribution. 31 | 32 | 3. Neither the name of the copyright holder nor the names of its 33 | contributors may be used to endorse or promote products derived 34 | from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 40 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | 48 | Links 49 | ~~~~~ 50 | YouTube: https://www.youtube.com/javidx9 51 | Discord: https://discord.gg/WhwHUMV 52 | Twitter: https://www.twitter.com/javidx9 53 | Twitch: https://www.twitch.tv/javidx9 54 | GitHub: https://www.github.com/onelonecoder 55 | Homepage: https://www.onelonecoder.com 56 | 57 | Author 58 | ~~~~~~ 59 | David Barr, aka javidx9, ©OneLoneCoder 2019 60 | */ 61 | 62 | /* 63 | Matrices stored as [Column][Row] (i.e. x, y) 64 | 65 | |C0R0 C1R0 C2R0| | x | | x'| 66 | |C0R1 C1R1 C2R1| * | y | = | y'| 67 | |C0R2 C1R2 C2R2| |1.0| | - | 68 | */ 69 | 70 | 71 | 72 | #ifndef OLC_PGEX_GFX2D 73 | #define OLC_PGEX_GFX2D 74 | 75 | #include 76 | 77 | #include "olcPixelGameEngine.h" 78 | 79 | #undef min 80 | #undef max 81 | 82 | namespace olc 83 | { 84 | // Container class for Advanced 2D Drawing functions 85 | class GFX2D : public olc::PGEX 86 | { 87 | // A representation of an affine transform, used to rotate, scale, offset & shear space 88 | public: 89 | class Transform2D 90 | { 91 | public: 92 | Transform2D(); 93 | 94 | public: 95 | // Set this transformation to unity 96 | void Reset(); 97 | // Append a rotation of fTheta radians to this transform 98 | void Rotate(float fTheta); 99 | // Append a translation (ox, oy) to this transform 100 | void Translate(float ox, float oy); 101 | // Append a scaling operation (sx, sy) to this transform 102 | void Scale(float sx, float sy); 103 | // Append a shear operation (sx, sy) to this transform 104 | void Shear(float sx, float sy); 105 | 106 | void Perspective(float ox, float oy); 107 | // Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) 108 | void Forward(float in_x, float in_y, float &out_x, float &out_y); 109 | // Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) 110 | void Backward(float in_x, float in_y, float &out_x, float &out_y); 111 | // Regenerate the Inverse Transformation 112 | void Invert(); 113 | 114 | private: 115 | void Multiply(); 116 | float matrix[4][3][3]; 117 | int nTargetMatrix; 118 | int nSourceMatrix; 119 | bool bDirty; 120 | }; 121 | 122 | public: 123 | // Draws a sprite with the transform applied 124 | static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); 125 | }; 126 | } 127 | 128 | 129 | #ifdef OLC_PGEX_GRAPHICS2D 130 | #undef OLC_PGEX_GRAPHICS2D 131 | 132 | namespace olc 133 | { 134 | void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform) 135 | { 136 | if (sprite == nullptr) 137 | return; 138 | 139 | // Work out bounding rectangle of sprite 140 | float ex, ey; 141 | float sx, sy; 142 | float px, py; 143 | 144 | transform.Forward(0.0f, 0.0f, sx, sy); 145 | px = sx; py = sy; 146 | sx = std::min(sx, px); sy = std::min(sy, py); 147 | ex = std::max(ex, px); ey = std::max(ey, py); 148 | 149 | transform.Forward((float)sprite->width, (float)sprite->height, px, py); 150 | sx = std::min(sx, px); sy = std::min(sy, py); 151 | ex = std::max(ex, px); ey = std::max(ey, py); 152 | 153 | transform.Forward(0.0f, (float)sprite->height, px, py); 154 | sx = std::min(sx, px); sy = std::min(sy, py); 155 | ex = std::max(ex, px); ey = std::max(ey, py); 156 | 157 | transform.Forward((float)sprite->width, 0.0f, px, py); 158 | sx = std::min(sx, px); sy = std::min(sy, py); 159 | ex = std::max(ex, px); ey = std::max(ey, py); 160 | 161 | // Perform inversion of transform if required 162 | transform.Invert(); 163 | 164 | if (ex < sx) 165 | std::swap(ex, sx); 166 | if (ey < sy) 167 | std::swap(ey, sy); 168 | 169 | // Iterate through render space, and sample Sprite from suitable texel location 170 | for (float i = sx; i < ex; i++) 171 | { 172 | for (float j = sy; j < ey; j++) 173 | { 174 | float ox, oy; 175 | transform.Backward(i, j, ox, oy); 176 | pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f))); 177 | } 178 | } 179 | } 180 | 181 | olc::GFX2D::Transform2D::Transform2D() 182 | { 183 | Reset(); 184 | } 185 | 186 | void olc::GFX2D::Transform2D::Reset() 187 | { 188 | nTargetMatrix = 0; 189 | nSourceMatrix = 1; 190 | bDirty = true; 191 | 192 | // Columns Then Rows 193 | 194 | // Matrices 0 & 1 are used as swaps in Transform accumulation 195 | matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f; 196 | matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f; 197 | matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f; 198 | 199 | matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f; 200 | matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f; 201 | matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f; 202 | 203 | // Matrix 2 is a cache matrix to hold the immediate transform operation 204 | // Matrix 3 is a cache matrix to hold the inverted transform 205 | } 206 | 207 | void olc::GFX2D::Transform2D::Multiply() 208 | { 209 | for (int c = 0; c < 3; c++) 210 | { 211 | for (int r = 0; r < 3; r++) 212 | { 213 | matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] + 214 | matrix[2][1][r] * matrix[nSourceMatrix][c][1] + 215 | matrix[2][2][r] * matrix[nSourceMatrix][c][2]; 216 | } 217 | } 218 | 219 | std::swap(nTargetMatrix, nSourceMatrix); 220 | bDirty = true; // Any transform multiply dirties the inversion 221 | } 222 | 223 | void olc::GFX2D::Transform2D::Rotate(float fTheta) 224 | { 225 | // Construct Rotation Matrix 226 | matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f; 227 | matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f; 228 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; 229 | Multiply(); 230 | } 231 | 232 | void olc::GFX2D::Transform2D::Scale(float sx, float sy) 233 | { 234 | // Construct Scale Matrix 235 | matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; 236 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f; 237 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; 238 | Multiply(); 239 | } 240 | 241 | void olc::GFX2D::Transform2D::Shear(float sx, float sy) 242 | { 243 | // Construct Shear Matrix 244 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f; 245 | matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; 246 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; 247 | Multiply(); 248 | } 249 | 250 | void olc::GFX2D::Transform2D::Translate(float ox, float oy) 251 | { 252 | // Construct Translate Matrix 253 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox; 254 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy; 255 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; 256 | Multiply(); 257 | } 258 | 259 | void olc::GFX2D::Transform2D::Perspective(float ox, float oy) 260 | { 261 | // Construct Translate Matrix 262 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; 263 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; 264 | matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f; 265 | Multiply(); 266 | } 267 | 268 | void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y) 269 | { 270 | out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0]; 271 | out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1]; 272 | float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2]; 273 | if (out_z != 0) 274 | { 275 | out_x /= out_z; 276 | out_y /= out_z; 277 | } 278 | } 279 | 280 | void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y) 281 | { 282 | out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0]; 283 | out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1]; 284 | float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2]; 285 | if (out_z != 0) 286 | { 287 | out_x /= out_z; 288 | out_y /= out_z; 289 | } 290 | } 291 | 292 | void olc::GFX2D::Transform2D::Invert() 293 | { 294 | if (bDirty) // Obviously costly so only do if needed 295 | { 296 | float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) - 297 | matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) + 298 | matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]); 299 | 300 | float idet = 1.0f / det; 301 | matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet; 302 | matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet; 303 | matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet; 304 | matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet; 305 | matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet; 306 | matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet; 307 | matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet; 308 | matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet; 309 | matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet; 310 | bDirty = false; 311 | } 312 | } 313 | } 314 | 315 | #endif 316 | #endif -------------------------------------------------------------------------------- /extensions/olcPGEX_Network.h: -------------------------------------------------------------------------------- 1 | /* 2 | ASIO Based Networking olcPixelGameEngine Extension v1.0 3 | 4 | Videos: 5 | Part #1: https://youtu.be/2hNdkYInj4g 6 | Part #2: https://youtu.be/UbjxGvrDrbw 7 | Part #3: https://youtu.be/hHowZ3bWsio 8 | Part #4: https://youtu.be/f_1lt9pfaEo 9 | 10 | License (OLC-3) 11 | ~~~~~~~~~~~~~~~ 12 | 13 | Copyright 2018 - 2021 OneLoneCoder.com 14 | 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions 17 | are met: 18 | 19 | 1. Redistributions or derivations of source code must retain the above 20 | copyright notice, this list of conditions and the following disclaimer. 21 | 22 | 2. Redistributions or derivative works in binary form must reproduce 23 | the above copyright notice. This list of conditions and the following 24 | disclaimer must be reproduced in the documentation and/or other 25 | materials provided with the distribution. 26 | 27 | 3. Neither the name of the copyright holder nor the names of its 28 | contributors may be used to endorse or promote products derived 29 | from this software without specific prior written permission. 30 | 31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 34 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 35 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 36 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 37 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 38 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 39 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 40 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 41 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 | 43 | Links 44 | ~~~~~ 45 | YouTube: https://www.youtube.com/javidx9 46 | Discord: https://discord.gg/WhwHUMV 47 | Twitter: https://www.twitter.com/javidx9 48 | Twitch: https://www.twitch.tv/javidx9 49 | GitHub: https://www.github.com/onelonecoder 50 | Homepage: https://www.onelonecoder.com 51 | 52 | Author 53 | ~~~~~~ 54 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021 55 | 56 | */ 57 | 58 | #pragma once 59 | 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | 71 | #ifdef _WIN32 72 | #ifndef _WIN32_WINNT 73 | #define _WIN32_WINNT 0x0A00 74 | #endif 75 | #endif 76 | 77 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 78 | #define ASIO_STANDALONE 79 | #include 80 | #include 81 | #include 82 | 83 | namespace olc 84 | { 85 | namespace net 86 | { 87 | // Message 88 | 89 | // Message Header is sent at start of all messages. The template allows us 90 | // to use "enum class" to ensure that the messages are valid at compile time 91 | template 92 | struct message_header 93 | { 94 | T id{}; 95 | uint32_t size = 0; 96 | }; 97 | 98 | // Message Body contains a header and a std::vector, containing raw bytes 99 | // of infomation. This way the message can be variable length, but the size 100 | // in the header must be updated. 101 | template 102 | struct message 103 | { 104 | // Header & Body vector 105 | message_header header{}; 106 | std::vector body; 107 | 108 | // returns size of entire message packet in bytes 109 | size_t size() const 110 | { 111 | return body.size(); 112 | } 113 | 114 | // Override for std::cout compatibility - produces friendly description of message 115 | friend std::ostream& operator << (std::ostream& os, const message& msg) 116 | { 117 | os << "ID:" << int(msg.header.id) << " Size:" << msg.header.size; 118 | return os; 119 | } 120 | 121 | // Convenience Operator overloads - These allow us to add and remove stuff from 122 | // the body vector as if it were a stack, so First in, Last Out. These are a 123 | // template in itself, because we dont know what data type the user is pushing or 124 | // popping, so lets allow them all. NOTE: It assumes the data type is fundamentally 125 | // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector 126 | 127 | // Pushes any POD-like data into the message buffer 128 | template 129 | friend message& operator << (message& msg, const DataType& data) 130 | { 131 | // Check that the type of the data being pushed is trivially copyable 132 | static_assert(std::is_standard_layout::value, "Data is too complex to be pushed into vector"); 133 | 134 | // Cache current size of vector, as this will be the point we insert the data 135 | size_t i = msg.body.size(); 136 | 137 | // Resize the vector by the size of the data being pushed 138 | msg.body.resize(msg.body.size() + sizeof(DataType)); 139 | 140 | // Physically copy the data into the newly allocated vector space 141 | std::memcpy(msg.body.data() + i, &data, sizeof(DataType)); 142 | 143 | // Recalculate the message size 144 | msg.header.size = msg.size(); 145 | 146 | // Return the target message so it can be "chained" 147 | return msg; 148 | } 149 | 150 | // Pulls any POD-like data form the message buffer 151 | template 152 | friend message& operator >> (message& msg, DataType& data) 153 | { 154 | // Check that the type of the data being pushed is trivially copyable 155 | static_assert(std::is_standard_layout::value, "Data is too complex to be pulled from vector"); 156 | 157 | // Cache the location towards the end of the vector where the pulled data starts 158 | size_t i = msg.body.size() - sizeof(DataType); 159 | 160 | // Physically copy the data from the vector into the user variable 161 | std::memcpy(&data, msg.body.data() + i, sizeof(DataType)); 162 | 163 | // Shrink the vector to remove read bytes, and reset end position 164 | msg.body.resize(i); 165 | 166 | // Recalculate the message size 167 | msg.header.size = msg.size(); 168 | 169 | // Return the target message so it can be "chained" 170 | return msg; 171 | } 172 | }; 173 | 174 | 175 | // An "owned" message is identical to a regular message, but it is associated with 176 | // a connection. On a server, the owner would be the client that sent the message, 177 | // on a client the owner would be the server. 178 | 179 | // Forward declare the connection 180 | template 181 | class connection; 182 | 183 | template 184 | struct owned_message 185 | { 186 | std::shared_ptr> remote = nullptr; 187 | message msg; 188 | 189 | // Again, a friendly string maker 190 | friend std::ostream& operator<<(std::ostream& os, const owned_message& msg) 191 | { 192 | os << msg.msg; 193 | return os; 194 | } 195 | }; 196 | 197 | 198 | // Queue 199 | template 200 | class tsqueue 201 | { 202 | public: 203 | tsqueue() = default; 204 | tsqueue(const tsqueue&) = delete; 205 | virtual ~tsqueue() { clear(); } 206 | 207 | public: 208 | // Returns and maintains item at front of Queue 209 | const T& front() 210 | { 211 | std::scoped_lock lock(muxQueue); 212 | return deqQueue.front(); 213 | } 214 | 215 | // Returns and maintains item at back of Queue 216 | const T& back() 217 | { 218 | std::scoped_lock lock(muxQueue); 219 | return deqQueue.back(); 220 | } 221 | 222 | // Removes and returns item from front of Queue 223 | T pop_front() 224 | { 225 | std::scoped_lock lock(muxQueue); 226 | auto t = std::move(deqQueue.front()); 227 | deqQueue.pop_front(); 228 | return t; 229 | } 230 | 231 | // Removes and returns item from back of Queue 232 | T pop_back() 233 | { 234 | std::scoped_lock lock(muxQueue); 235 | auto t = std::move(deqQueue.back()); 236 | deqQueue.pop_back(); 237 | return t; 238 | } 239 | 240 | // Adds an item to back of Queue 241 | void push_back(const T& item) 242 | { 243 | std::scoped_lock lock(muxQueue); 244 | deqQueue.emplace_back(std::move(item)); 245 | 246 | std::unique_lock ul(muxBlocking); 247 | cvBlocking.notify_one(); 248 | } 249 | 250 | // Adds an item to front of Queue 251 | void push_front(const T& item) 252 | { 253 | std::scoped_lock lock(muxQueue); 254 | deqQueue.emplace_front(std::move(item)); 255 | 256 | std::unique_lock ul(muxBlocking); 257 | cvBlocking.notify_one(); 258 | } 259 | 260 | // Returns true if Queue has no items 261 | bool empty() 262 | { 263 | std::scoped_lock lock(muxQueue); 264 | return deqQueue.empty(); 265 | } 266 | 267 | // Returns number of items in Queue 268 | size_t count() 269 | { 270 | std::scoped_lock lock(muxQueue); 271 | return deqQueue.size(); 272 | } 273 | 274 | // Clears Queue 275 | void clear() 276 | { 277 | std::scoped_lock lock(muxQueue); 278 | deqQueue.clear(); 279 | } 280 | 281 | void wait() 282 | { 283 | while (empty()) 284 | { 285 | std::unique_lock ul(muxBlocking); 286 | cvBlocking.wait(ul); 287 | } 288 | } 289 | 290 | protected: 291 | std::mutex muxQueue; 292 | std::deque deqQueue; 293 | std::condition_variable cvBlocking; 294 | std::mutex muxBlocking; 295 | }; 296 | 297 | // Connection 298 | // Forward declare 299 | template 300 | class server_interface; 301 | 302 | template 303 | class connection : public std::enable_shared_from_this> 304 | { 305 | public: 306 | // A connection is "owned" by either a server or a client, and its 307 | // behaviour is slightly different bewteen the two. 308 | enum class owner 309 | { 310 | server, 311 | client 312 | }; 313 | 314 | public: 315 | // Constructor: Specify Owner, connect to context, transfer the socket 316 | // Provide reference to incoming message queue 317 | connection(owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, tsqueue>& qIn) 318 | : m_asioContext(asioContext), m_socket(std::move(socket)), m_qMessagesIn(qIn) 319 | { 320 | m_nOwnerType = parent; 321 | 322 | // Construct validation check data 323 | if (m_nOwnerType == owner::server) 324 | { 325 | // Connection is Server -> Client, construct random data for the client 326 | // to transform and send back for validation 327 | m_nHandshakeOut = uint64_t(std::chrono::system_clock::now().time_since_epoch().count()); 328 | 329 | // Pre-calculate the result for checking when the client responds 330 | m_nHandshakeCheck = scramble(m_nHandshakeOut); 331 | } 332 | else 333 | { 334 | // Connection is Client -> Server, so we have nothing to define, 335 | m_nHandshakeIn = 0; 336 | m_nHandshakeOut = 0; 337 | } 338 | } 339 | 340 | virtual ~connection() 341 | {} 342 | 343 | // This ID is used system wide - its how clients will understand other clients 344 | // exist across the whole system. 345 | uint32_t GetID() const 346 | { 347 | return id; 348 | } 349 | 350 | public: 351 | void ConnectToClient(olc::net::server_interface* server, uint32_t uid = 0) 352 | { 353 | if (m_nOwnerType == owner::server) 354 | { 355 | if (m_socket.is_open()) 356 | { 357 | id = uid; 358 | 359 | // Was: ReadHeader(); 360 | 361 | // A client has attempted to connect to the server, but we wish 362 | // the client to first validate itself, so first write out the 363 | // handshake data to be validated 364 | WriteValidation(); 365 | 366 | // Next, issue a task to sit and wait asynchronously for precisely 367 | // the validation data sent back from the client 368 | ReadValidation(server); 369 | } 370 | } 371 | } 372 | 373 | void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints) 374 | { 375 | // Only clients can connect to servers 376 | if (m_nOwnerType == owner::client) 377 | { 378 | // Request asio attempts to connect to an endpoint 379 | asio::async_connect(m_socket, endpoints, 380 | [this](std::error_code ec, asio::ip::tcp::endpoint endpoint) 381 | { 382 | if (!ec) 383 | { 384 | // Was: ReadHeader(); 385 | 386 | // First thing server will do is send packet to be validated 387 | // so wait for that and respond 388 | ReadValidation(); 389 | } 390 | }); 391 | } 392 | } 393 | 394 | 395 | void Disconnect() 396 | { 397 | if (IsConnected()) 398 | asio::post(m_asioContext, [this]() { m_socket.close(); }); 399 | } 400 | 401 | bool IsConnected() const 402 | { 403 | return m_socket.is_open(); 404 | } 405 | 406 | // Prime the connection to wait for incoming messages 407 | void StartListening() 408 | { 409 | 410 | } 411 | 412 | public: 413 | // ASYNC - Send a message, connections are one-to-one so no need to specifiy 414 | // the target, for a client, the target is the server and vice versa 415 | void Send(const message& msg) 416 | { 417 | asio::post(m_asioContext, 418 | [this, msg]() 419 | { 420 | // If the queue has a message in it, then we must 421 | // assume that it is in the process of asynchronously being written. 422 | // Either way add the message to the queue to be output. If no messages 423 | // were available to be written, then start the process of writing the 424 | // message at the front of the queue. 425 | bool bWritingMessage = !m_qMessagesOut.empty(); 426 | m_qMessagesOut.push_back(msg); 427 | if (!bWritingMessage) 428 | { 429 | WriteHeader(); 430 | } 431 | }); 432 | } 433 | 434 | 435 | 436 | private: 437 | // ASYNC - Prime context to write a message header 438 | void WriteHeader() 439 | { 440 | // If this function is called, we know the outgoing message queue must have 441 | // at least one message to send. So allocate a transmission buffer to hold 442 | // the message, and issue the work - asio, send these bytes 443 | asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(message_header)), 444 | [this](std::error_code ec, std::size_t length) 445 | { 446 | // asio has now sent the bytes - if there was a problem 447 | // an error would be available... 448 | if (!ec) 449 | { 450 | // ... no error, so check if the message header just sent also 451 | // has a message body... 452 | if (m_qMessagesOut.front().body.size() > 0) 453 | { 454 | // ...it does, so issue the task to write the body bytes 455 | WriteBody(); 456 | } 457 | else 458 | { 459 | // ...it didnt, so we are done with this message. Remove it from 460 | // the outgoing message queue 461 | m_qMessagesOut.pop_front(); 462 | 463 | // If the queue is not empty, there are more messages to send, so 464 | // make this happen by issuing the task to send the next header. 465 | if (!m_qMessagesOut.empty()) 466 | { 467 | WriteHeader(); 468 | } 469 | } 470 | } 471 | else 472 | { 473 | // ...asio failed to write the message, we could analyse why but 474 | // for now simply assume the connection has died by closing the 475 | // socket. When a future attempt to write to this client fails due 476 | // to the closed socket, it will be tidied up. 477 | std::cout << "[" << id << "] Write Header Fail.\n"; 478 | m_socket.close(); 479 | } 480 | }); 481 | } 482 | 483 | // ASYNC - Prime context to write a message body 484 | void WriteBody() 485 | { 486 | // If this function is called, a header has just been sent, and that header 487 | // indicated a body existed for this message. Fill a transmission buffer 488 | // with the body data, and send it! 489 | asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()), 490 | [this](std::error_code ec, std::size_t length) 491 | { 492 | if (!ec) 493 | { 494 | // Sending was successful, so we are done with the message 495 | // and remove it from the queue 496 | m_qMessagesOut.pop_front(); 497 | 498 | // If the queue still has messages in it, then issue the task to 499 | // send the next messages' header. 500 | if (!m_qMessagesOut.empty()) 501 | { 502 | WriteHeader(); 503 | } 504 | } 505 | else 506 | { 507 | // Sending failed, see WriteHeader() equivalent for description :P 508 | std::cout << "[" << id << "] Write Body Fail.\n"; 509 | m_socket.close(); 510 | } 511 | }); 512 | } 513 | 514 | // ASYNC - Prime context ready to read a message header 515 | void ReadHeader() 516 | { 517 | // If this function is called, we are expecting asio to wait until it receives 518 | // enough bytes to form a header of a message. We know the headers are a fixed 519 | // size, so allocate a transmission buffer large enough to store it. In fact, 520 | // we will construct the message in a "temporary" message object as it's 521 | // convenient to work with. 522 | asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(message_header)), 523 | [this](std::error_code ec, std::size_t length) 524 | { 525 | if (!ec) 526 | { 527 | // A complete message header has been read, check if this message 528 | // has a body to follow... 529 | if (m_msgTemporaryIn.header.size > 0) 530 | { 531 | // ...it does, so allocate enough space in the messages' body 532 | // vector, and issue asio with the task to read the body. 533 | m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size); 534 | ReadBody(); 535 | } 536 | else 537 | { 538 | // it doesn't, so add this bodyless message to the connections 539 | // incoming message queue 540 | AddToIncomingMessageQueue(); 541 | } 542 | } 543 | else 544 | { 545 | // Reading form the client went wrong, most likely a disconnect 546 | // has occurred. Close the socket and let the system tidy it up later. 547 | std::cout << "[" << id << "] Read Header Fail.\n"; 548 | m_socket.close(); 549 | } 550 | }); 551 | } 552 | 553 | // ASYNC - Prime context ready to read a message body 554 | void ReadBody() 555 | { 556 | // If this function is called, a header has already been read, and that header 557 | // request we read a body, The space for that body has already been allocated 558 | // in the temporary message object, so just wait for the bytes to arrive... 559 | asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()), 560 | [this](std::error_code ec, std::size_t length) 561 | { 562 | if (!ec) 563 | { 564 | // ...and they have! The message is now complete, so add 565 | // the whole message to incoming queue 566 | AddToIncomingMessageQueue(); 567 | } 568 | else 569 | { 570 | // As above! 571 | std::cout << "[" << id << "] Read Body Fail.\n"; 572 | m_socket.close(); 573 | } 574 | }); 575 | } 576 | 577 | // "Encrypt" data 578 | uint64_t scramble(uint64_t nInput) 579 | { 580 | uint64_t out = nInput ^ 0xDEADBEEFC0DECAFE; 581 | out = (out & 0xF0F0F0F0F0F0F0) >> 4 | (out & 0x0F0F0F0F0F0F0F) << 4; 582 | return out ^ 0xC0DEFACE12345678; 583 | } 584 | 585 | // ASYNC - Used by both client and server to write validation packet 586 | void WriteValidation() 587 | { 588 | asio::async_write(m_socket, asio::buffer(&m_nHandshakeOut, sizeof(uint64_t)), 589 | [this](std::error_code ec, std::size_t length) 590 | { 591 | if (!ec) 592 | { 593 | // Validation data sent, clients should sit and wait 594 | // for a response (or a closure) 595 | if (m_nOwnerType == owner::client) 596 | ReadHeader(); 597 | } 598 | else 599 | { 600 | m_socket.close(); 601 | } 602 | }); 603 | } 604 | 605 | void ReadValidation(olc::net::server_interface* server = nullptr) 606 | { 607 | asio::async_read(m_socket, asio::buffer(&m_nHandshakeIn, sizeof(uint64_t)), 608 | [this, server](std::error_code ec, std::size_t length) 609 | { 610 | if (!ec) 611 | { 612 | if (m_nOwnerType == owner::server) 613 | { 614 | // Connection is a server, so check response from client 615 | 616 | // Compare sent data to actual solution 617 | if (m_nHandshakeIn == m_nHandshakeCheck) 618 | { 619 | // Client has provided valid solution, so allow it to connect properly 620 | std::cout << "Client Validated" << std::endl; 621 | server->OnClientValidated(this->shared_from_this()); 622 | 623 | // Sit waiting to receive data now 624 | ReadHeader(); 625 | } 626 | else 627 | { 628 | // Client gave incorrect data, so disconnect 629 | std::cout << "Client Disconnected (Fail Validation)" << std::endl; 630 | m_socket.close(); 631 | } 632 | } 633 | else 634 | { 635 | // Connection is a client, so solve puzzle 636 | m_nHandshakeOut = scramble(m_nHandshakeIn); 637 | 638 | // Write the result 639 | WriteValidation(); 640 | } 641 | } 642 | else 643 | { 644 | // Some biggerfailure occured 645 | std::cout << "Client Disconnected (ReadValidation)" << std::endl; 646 | m_socket.close(); 647 | } 648 | }); 649 | } 650 | 651 | // Once a full message is received, add it to the incoming queue 652 | void AddToIncomingMessageQueue() 653 | { 654 | // Shove it in queue, converting it to an "owned message", by initialising 655 | // with the a shared pointer from this connection object 656 | if(m_nOwnerType == owner::server) 657 | m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn }); 658 | else 659 | m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn }); 660 | 661 | // We must now prime the asio context to receive the next message. It 662 | // wil just sit and wait for bytes to arrive, and the message construction 663 | // process repeats itself. Clever huh? 664 | ReadHeader(); 665 | } 666 | 667 | protected: 668 | // Each connection has a unique socket to a remote 669 | asio::ip::tcp::socket m_socket; 670 | 671 | // This context is shared with the whole asio instance 672 | asio::io_context& m_asioContext; 673 | 674 | // This queue holds all messages to be sent to the remote side 675 | // of this connection 676 | tsqueue> m_qMessagesOut; 677 | 678 | // This references the incoming queue of the parent object 679 | tsqueue>& m_qMessagesIn; 680 | 681 | // Incoming messages are constructed asynchronously, so we will 682 | // store the part assembled message here, until it is ready 683 | message m_msgTemporaryIn; 684 | 685 | // The "owner" decides how some of the connection behaves 686 | owner m_nOwnerType = owner::server; 687 | 688 | // Handshake Validation 689 | uint64_t m_nHandshakeOut = 0; 690 | uint64_t m_nHandshakeIn = 0; 691 | uint64_t m_nHandshakeCheck = 0; 692 | 693 | 694 | bool m_bValidHandshake = false; 695 | bool m_bConnectionEstablished = false; 696 | 697 | uint32_t id = 0; 698 | 699 | }; 700 | 701 | // Client 702 | template 703 | class client_interface 704 | { 705 | public: 706 | client_interface() 707 | {} 708 | 709 | virtual ~client_interface() 710 | { 711 | // If the client is destroyed, always try and disconnect from server 712 | Disconnect(); 713 | } 714 | 715 | public: 716 | // Connect to server with hostname/ip-address and port 717 | bool Connect(const std::string& host, const uint16_t port) 718 | { 719 | try 720 | { 721 | // Resolve hostname/ip-address into tangiable physical address 722 | asio::ip::tcp::resolver resolver(m_context); 723 | asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port)); 724 | 725 | // Create connection 726 | m_connection = std::make_unique>(connection::owner::client, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn); 727 | 728 | // Tell the connection object to connect to server 729 | m_connection->ConnectToServer(endpoints); 730 | 731 | // Start Context Thread 732 | thrContext = std::thread([this]() { m_context.run(); }); 733 | } 734 | catch (std::exception& e) 735 | { 736 | std::cerr << "Client Exception: " << e.what() << "\n"; 737 | return false; 738 | } 739 | return true; 740 | } 741 | 742 | // Disconnect from server 743 | void Disconnect() 744 | { 745 | // If connection exists, and it's connected then... 746 | if(IsConnected()) 747 | { 748 | // ...disconnect from server gracefully 749 | m_connection->Disconnect(); 750 | } 751 | 752 | // Either way, we're also done with the asio context... 753 | m_context.stop(); 754 | // ...and its thread 755 | if (thrContext.joinable()) 756 | thrContext.join(); 757 | 758 | // Destroy the connection object 759 | m_connection.release(); 760 | } 761 | 762 | // Check if client is actually connected to a server 763 | bool IsConnected() 764 | { 765 | if (m_connection) 766 | return m_connection->IsConnected(); 767 | else 768 | return false; 769 | } 770 | 771 | public: 772 | // Send message to server 773 | void Send(const message& msg) 774 | { 775 | if (IsConnected()) 776 | m_connection->Send(msg); 777 | } 778 | 779 | // Retrieve queue of messages from server 780 | tsqueue>& Incoming() 781 | { 782 | return m_qMessagesIn; 783 | } 784 | 785 | protected: 786 | // asio context handles the data transfer... 787 | asio::io_context m_context; 788 | // ...but needs a thread of its own to execute its work commands 789 | std::thread thrContext; 790 | // The client has a single instance of a "connection" object, which handles data transfer 791 | std::unique_ptr> m_connection; 792 | 793 | private: 794 | // This is the thread safe queue of incoming messages from server 795 | tsqueue> m_qMessagesIn; 796 | }; 797 | 798 | // Server 799 | template 800 | class server_interface 801 | { 802 | public: 803 | // Create a server, ready to listen on specified port 804 | server_interface(uint16_t port) 805 | : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) 806 | { 807 | 808 | } 809 | 810 | virtual ~server_interface() 811 | { 812 | // May as well try and tidy up 813 | Stop(); 814 | } 815 | 816 | // Starts the server! 817 | bool Start() 818 | { 819 | try 820 | { 821 | // Issue a task to the asio context - This is important 822 | // as it will prime the context with "work", and stop it 823 | // from exiting immediately. Since this is a server, we 824 | // want it primed ready to handle clients trying to 825 | // connect. 826 | WaitForClientConnection(); 827 | 828 | // Launch the asio context in its own thread 829 | m_threadContext = std::thread([this]() { m_asioContext.run(); }); 830 | } 831 | catch (std::exception& e) 832 | { 833 | // Something prohibited the server from listening 834 | std::cerr << "[SERVER] Exception: " << e.what() << "\n"; 835 | return false; 836 | } 837 | 838 | std::cout << "[SERVER] Started!\n"; 839 | return true; 840 | } 841 | 842 | // Stops the server! 843 | void Stop() 844 | { 845 | // Request the context to close 846 | m_asioContext.stop(); 847 | 848 | // Tidy up the context thread 849 | if (m_threadContext.joinable()) m_threadContext.join(); 850 | 851 | // Inform someone, anybody, if they care... 852 | std::cout << "[SERVER] Stopped!\n"; 853 | } 854 | 855 | // ASYNC - Instruct asio to wait for connection 856 | void WaitForClientConnection() 857 | { 858 | // Prime context with an instruction to wait until a socket connects. This 859 | // is the purpose of an "acceptor" object. It will provide a unique socket 860 | // for each incoming connection attempt 861 | m_asioAcceptor.async_accept( 862 | [this](std::error_code ec, asio::ip::tcp::socket socket) 863 | { 864 | // Triggered by incoming connection request 865 | if (!ec) 866 | { 867 | // Display some useful(?) information 868 | std::cout << "[SERVER] New Connection: " << socket.remote_endpoint() << "\n"; 869 | 870 | // Create a new connection to handle this client 871 | std::shared_ptr> newconn = 872 | std::make_shared>(connection::owner::server, 873 | m_asioContext, std::move(socket), m_qMessagesIn); 874 | 875 | 876 | 877 | // Give the user server a chance to deny connection 878 | if (OnClientConnect(newconn)) 879 | { 880 | // Connection allowed, so add to container of new connections 881 | m_deqConnections.push_back(std::move(newconn)); 882 | 883 | // And very important! Issue a task to the connection's 884 | // asio context to sit and wait for bytes to arrive! 885 | m_deqConnections.back()->ConnectToClient(this, nIDCounter++); 886 | 887 | std::cout << "[" << m_deqConnections.back()->GetID() << "] Connection Approved\n"; 888 | } 889 | else 890 | { 891 | std::cout << "[-----] Connection Denied\n"; 892 | 893 | // Connection will go out of scope with no pending tasks, so will 894 | // get destroyed automagically due to the wonder of smart pointers 895 | } 896 | } 897 | else 898 | { 899 | // Error has occurred during acceptance 900 | std::cout << "[SERVER] New Connection Error: " << ec.message() << "\n"; 901 | } 902 | 903 | // Prime the asio context with more work - again simply wait for 904 | // another connection... 905 | WaitForClientConnection(); 906 | }); 907 | } 908 | 909 | // Send a message to a specific client 910 | void MessageClient(std::shared_ptr> client, const message& msg) 911 | { 912 | // Check client is legitimate... 913 | if (client && client->IsConnected()) 914 | { 915 | // ...and post the message via the connection 916 | client->Send(msg); 917 | } 918 | else 919 | { 920 | // If we cant communicate with client then we may as 921 | // well remove the client - let the server know, it may 922 | // be tracking it somehow 923 | OnClientDisconnect(client); 924 | 925 | // Off you go now, bye bye! 926 | client.reset(); 927 | 928 | // Then physically remove it from the container 929 | m_deqConnections.erase( 930 | std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end()); 931 | } 932 | } 933 | 934 | // Send message to all clients 935 | void MessageAllClients(const message& msg, std::shared_ptr> pIgnoreClient = nullptr) 936 | { 937 | bool bInvalidClientExists = false; 938 | 939 | // Iterate through all clients in container 940 | for (auto& client : m_deqConnections) 941 | { 942 | // Check client is connected... 943 | if (client && client->IsConnected()) 944 | { 945 | // ..it is! 946 | if(client != pIgnoreClient) 947 | client->Send(msg); 948 | } 949 | else 950 | { 951 | // The client couldnt be contacted, so assume it has 952 | // disconnected. 953 | OnClientDisconnect(client); 954 | client.reset(); 955 | 956 | // Set this flag to then remove dead clients from container 957 | bInvalidClientExists = true; 958 | } 959 | } 960 | 961 | // Remove dead clients, all in one go - this way, we dont invalidate the 962 | // container as we iterated through it. 963 | if (bInvalidClientExists) 964 | m_deqConnections.erase( 965 | std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end()); 966 | } 967 | 968 | // Force server to respond to incoming messages 969 | void Update(size_t nMaxMessages = -1, bool bWait = false) 970 | { 971 | if (bWait) m_qMessagesIn.wait(); 972 | 973 | // Process as many messages as you can up to the value 974 | // specified 975 | size_t nMessageCount = 0; 976 | while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty()) 977 | { 978 | // Grab the front message 979 | auto msg = m_qMessagesIn.pop_front(); 980 | 981 | // Pass to message handler 982 | OnMessage(msg.remote, msg.msg); 983 | 984 | nMessageCount++; 985 | } 986 | } 987 | 988 | protected: 989 | // This server class should override thse functions to implement 990 | // customised functionality 991 | 992 | // Called when a client connects, you can veto the connection by returning false 993 | virtual bool OnClientConnect(std::shared_ptr> client) 994 | { 995 | return false; 996 | } 997 | 998 | // Called when a client appears to have disconnected 999 | virtual void OnClientDisconnect(std::shared_ptr> client) 1000 | { 1001 | 1002 | } 1003 | 1004 | // Called when a message arrives 1005 | virtual void OnMessage(std::shared_ptr> client, message& msg) 1006 | { 1007 | 1008 | } 1009 | 1010 | public: 1011 | // Called when a client is validated 1012 | virtual void OnClientValidated(std::shared_ptr> client) 1013 | { 1014 | 1015 | } 1016 | 1017 | 1018 | protected: 1019 | // Thread Safe Queue for incoming message packets 1020 | tsqueue> m_qMessagesIn; 1021 | 1022 | // Container of active validated connections 1023 | std::deque>> m_deqConnections; 1024 | 1025 | // Order of declaration is important - it is also the order of initialisation 1026 | asio::io_context m_asioContext; 1027 | std::thread m_threadContext; 1028 | 1029 | // These things need an asio context 1030 | asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts... 1031 | 1032 | // Clients will be identified in the "wider system" via an ID 1033 | uint32_t nIDCounter = 10000; 1034 | }; 1035 | } 1036 | } 1037 | 1038 | 1039 | -------------------------------------------------------------------------------- /extensions/olcPGEX_PopUpMenu.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_PopUp.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Retro PopUp Menu 1.0 | 7 | +-------------------------------------------------------------+ 8 | 9 | What is this? 10 | ~~~~~~~~~~~~~ 11 | This is an extension to the olcPixelGameEngine, which provides 12 | a quick and easy to use, flexible, skinnable context pop-up 13 | menu system. 14 | 15 | License (OLC-3) 16 | ~~~~~~~~~~~~~~~ 17 | 18 | Copyright 2018 - 2020 OneLoneCoder.com 19 | 20 | Redistribution and use in source and binary forms, with or without 21 | modification, are permitted provided that the following conditions 22 | are met: 23 | 24 | 1. Redistributions or derivations of source code must retain the above 25 | copyright notice, this list of conditions and the following disclaimer. 26 | 27 | 2. Redistributions or derivative works in binary form must reproduce 28 | the above copyright notice. This list of conditions and the following 29 | disclaimer must be reproduced in the documentation and/or other 30 | materials provided with the distribution. 31 | 32 | 3. Neither the name of the copyright holder nor the names of its 33 | contributors may be used to endorse or promote products derived 34 | from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 40 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | 48 | Links 49 | ~~~~~ 50 | YouTube: https://www.youtube.com/javidx9 51 | Discord: https://discord.gg/WhwHUMV 52 | Twitter: https://www.twitter.com/javidx9 53 | Twitch: https://www.twitch.tv/javidx9 54 | GitHub: https://www.github.com/onelonecoder 55 | Homepage: https://www.onelonecoder.com 56 | 57 | Author 58 | ~~~~~~ 59 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 60 | */ 61 | 62 | 63 | /* 64 | Example 65 | ~~~~~~~ 66 | 67 | #define OLC_PGEX_POPUPMENU 68 | #include "olcPGEX_PopUpMenu.h" 69 | 70 | NOTE: Requires a 9-patch sprite, by default each patch is 71 | 8x8 pixels, patches are as follows: 72 | 73 | | PANEL TL | PANEL T | PANEL TR | SCROLL UP | CURSOR TL | CURSOR TR | 74 | | PANEL L | PANEL M | PANEL R | SUBMENU | CURSOR BL | CURSOR BR | 75 | | PANEL BL | PANEL B | PANEL BR | SCROLL DOWN | UNUSED | UNUSED | 76 | 77 | You can find an example sprite here: 78 | https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/RetroMenu.png 79 | 80 | Constructing A Menu 81 | ~~~~~~~~~~~~~~~~~~~ 82 | 83 | // Declaration (presumably inside class) 84 | olc::popup::Menu m; 85 | 86 | // Construction (root menu is a 1x5 table) 87 | m.SetTable(1, 5); 88 | 89 | // Add first item to root menu (A 1x5 submenu) 90 | m["Menu1"].SetTable(1, 5); 91 | 92 | // Add items to first item 93 | m["Menu1"]["Item1"]; 94 | m["Menu1"]["Item2"]; 95 | 96 | // Add a 4x3 submenu 97 | m["Menu1"]["Item3"].SetTable(4, 3); 98 | m["Menu1"]["Item3"]["Option1"]; 99 | m["Menu1"]["Item3"]["Option2"]; 100 | 101 | // Set properties of specific item 102 | m["Menu1"]["Item3"]["Option3"].Enable(false); 103 | m["Menu1"]["Item3"]["Option4"]; 104 | m["Menu1"]["Item3"]["Option5"]; 105 | m["Menu1"]["Item4"]; 106 | 107 | // Add second item to root menu 108 | m["Menu2"].SetTable(3, 3); 109 | m["Menu2"]["Item1"]; 110 | m["Menu2"]["Item2"].SetID(1001).Enable(true); 111 | m["Menu2"]["Item3"]; 112 | 113 | // Construct the menu structure 114 | m.Build(); 115 | 116 | 117 | Displaying a Menu 118 | ~~~~~~~~~~~~~~~~~ 119 | 120 | // Declaration of menu manager (presumably inside class) 121 | olc::popup::Manager man; 122 | 123 | // Set the Menu object to the MenuManager (call once per pop) 124 | man.Open(&m); 125 | 126 | // Draw Menu at position (30, 30), using "patch sprite" 127 | man.Draw(sprGFX, { 30,30 }); 128 | 129 | 130 | Interacting with menu 131 | ~~~~~~~~~~~~~~~~~~~~~ 132 | 133 | // Send key events to menu 134 | if (GetKey(olc::Key::UP).bPressed) man.OnUp(); 135 | if (GetKey(olc::Key::DOWN).bPressed) man.OnDown(); 136 | if (GetKey(olc::Key::LEFT).bPressed) man.OnLeft(); 137 | if (GetKey(olc::Key::RIGHT).bPressed) man.OnRight(); 138 | if (GetKey(olc::Key::Z).bPressed) man.OnBack(); 139 | 140 | // "Confirm/Action" Key does something, if it returns non-null 141 | // then a menu item has been selected. The specific item will 142 | // be returned 143 | olc::popup::Menu* command = nullptr; 144 | if (GetKey(olc::Key::SPACE).bPressed) command = man.OnConfirm(); 145 | if (command != nullptr) 146 | { 147 | std::string sLastAction = 148 | "Selected: " + command->GetName() + 149 | " ID: " + std::to_string(command->GetID()); 150 | 151 | // Optionally close menu? 152 | man.Close(); 153 | } 154 | 155 | */ 156 | 157 | #ifndef OLC_PGEX_POPUPMENU_H 158 | #define OLC_PGEX_POPUPMENU_H 159 | 160 | #include 161 | 162 | #include "olcPixelGameEngine.h" 163 | 164 | namespace olc 165 | { 166 | namespace popup 167 | { 168 | constexpr int32_t nPatch = 8; 169 | 170 | class Menu 171 | { 172 | public: 173 | Menu(); 174 | Menu(const std::string n); 175 | 176 | Menu& SetTable(int32_t nColumns, int32_t nRows); 177 | Menu& SetID(int32_t id); 178 | Menu& Enable(bool b); 179 | 180 | int32_t GetID(); 181 | std::string& GetName(); 182 | bool Enabled(); 183 | bool HasChildren(); 184 | olc::vi2d GetSize(); 185 | olc::vi2d& GetCursorPosition(); 186 | Menu& operator[](const std::string& name); 187 | void Build(); 188 | void DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset); 189 | void ClampCursor(); 190 | void OnUp(); 191 | void OnDown(); 192 | void OnLeft(); 193 | void OnRight(); 194 | Menu* OnConfirm(); 195 | Menu* GetSelectedItem(); 196 | 197 | protected: 198 | int32_t nID = -1; 199 | olc::vi2d vCellTable = { 1, 0 }; 200 | std::unordered_map itemPointer; 201 | std::vector items; 202 | olc::vi2d vSizeInPatches = { 0, 0 }; 203 | olc::vi2d vCellSize = { 0, 0 }; 204 | olc::vi2d vCellPadding = { 2, 0 }; 205 | olc::vi2d vCellCursor = { 0, 0 }; 206 | int32_t nCursorItem = 0; 207 | int32_t nTopVisibleRow = 0; 208 | int32_t nTotalRows = 0; 209 | const olc::vi2d vPatchSize = { nPatch, nPatch }; 210 | std::string sName; 211 | olc::vi2d vCursorPos = { 0, 0 }; 212 | bool bEnabled = true; 213 | }; 214 | 215 | class Manager : public olc::PGEX 216 | { 217 | public: 218 | Manager(); 219 | void Open(Menu* mo); 220 | void Close(); 221 | void OnUp(); 222 | void OnDown(); 223 | void OnLeft(); 224 | void OnRight(); 225 | void OnBack(); 226 | Menu* OnConfirm(); 227 | void Draw(olc::Sprite* sprGFX, olc::vi2d vScreenOffset); 228 | 229 | private: 230 | std::list panels; 231 | }; 232 | 233 | } 234 | }; 235 | 236 | 237 | 238 | 239 | #ifdef OLC_PGEX_POPUPMENU 240 | #undef OLC_PGEX_POPUPMENU 241 | 242 | namespace olc 243 | { 244 | namespace popup 245 | { 246 | Menu::Menu() 247 | { 248 | } 249 | 250 | Menu::Menu(const std::string n) 251 | { 252 | sName = n; 253 | } 254 | 255 | 256 | Menu& Menu::SetTable(int32_t nColumns, int32_t nRows) 257 | { 258 | vCellTable = { nColumns, nRows }; 259 | return *this; 260 | } 261 | 262 | Menu& Menu::SetID(int32_t id) 263 | { 264 | nID = id; 265 | return *this; 266 | } 267 | 268 | Menu& Menu::Enable(bool b) 269 | { 270 | bEnabled = b; 271 | return *this; 272 | } 273 | 274 | int32_t Menu::GetID() 275 | { 276 | return nID; 277 | } 278 | 279 | std::string& Menu::GetName() 280 | { 281 | return sName; 282 | } 283 | 284 | bool Menu::Enabled() 285 | { 286 | return bEnabled; 287 | } 288 | 289 | bool Menu::HasChildren() 290 | { 291 | return !items.empty(); 292 | } 293 | 294 | olc::vi2d Menu::GetSize() 295 | { 296 | return { int32_t(sName.size()), 1 }; 297 | } 298 | 299 | olc::vi2d& Menu::GetCursorPosition() 300 | { 301 | return vCursorPos; 302 | } 303 | 304 | Menu& Menu::operator[](const std::string& name) 305 | { 306 | if (itemPointer.count(name) == 0) 307 | { 308 | itemPointer[name] = items.size(); 309 | items.push_back(Menu(name)); 310 | } 311 | 312 | return items[itemPointer[name]]; 313 | } 314 | 315 | void Menu::Build() 316 | { 317 | // Recursively build all children, so they can determine their size, use 318 | // that size to indicate cell sizes if this object contains more than 319 | // one item 320 | for (auto& m : items) 321 | { 322 | if (m.HasChildren()) 323 | { 324 | m.Build(); 325 | } 326 | 327 | // Longest child name determines cell width 328 | vCellSize.x = std::max(m.GetSize().x, vCellSize.x); 329 | vCellSize.y = std::max(m.GetSize().y, vCellSize.y); 330 | } 331 | 332 | // Adjust size of this object (in patches) if it were rendered as a panel 333 | vSizeInPatches.x = vCellTable.x * vCellSize.x + (vCellTable.x - 1) * vCellPadding.x + 2; 334 | vSizeInPatches.y = vCellTable.y * vCellSize.y + (vCellTable.y - 1) * vCellPadding.y + 2; 335 | 336 | // Calculate how many rows this item has to hold 337 | nTotalRows = (items.size() / vCellTable.x) + (((items.size() % vCellTable.x) > 0) ? 1 : 0); 338 | } 339 | 340 | void Menu::DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset) 341 | { 342 | // === Draw Panel 343 | 344 | // Record current pixel mode user is using 345 | olc::Pixel::Mode currentPixelMode = pge.GetPixelMode(); 346 | pge.SetPixelMode(olc::Pixel::MASK); 347 | 348 | // Draw Panel & Border 349 | olc::vi2d vPatchPos = { 0,0 }; 350 | for (vPatchPos.x = 0; vPatchPos.x < vSizeInPatches.x; vPatchPos.x++) 351 | { 352 | for (vPatchPos.y = 0; vPatchPos.y < vSizeInPatches.y; vPatchPos.y++) 353 | { 354 | // Determine position in screen space 355 | olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; 356 | 357 | // Calculate which patch is needed 358 | olc::vi2d vSourcePatch = { 0, 0 }; 359 | if (vPatchPos.x > 0) vSourcePatch.x = 1; 360 | if (vPatchPos.x == vSizeInPatches.x - 1) vSourcePatch.x = 2; 361 | if (vPatchPos.y > 0) vSourcePatch.y = 1; 362 | if (vPatchPos.y == vSizeInPatches.y - 1) vSourcePatch.y = 2; 363 | 364 | // Draw Actual Patch 365 | pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); 366 | } 367 | } 368 | 369 | // === Draw Panel Contents 370 | olc::vi2d vCell = { 0,0 }; 371 | vPatchPos = { 1,1 }; 372 | 373 | // Work out visible items 374 | int32_t nTopLeftItem = nTopVisibleRow * vCellTable.x; 375 | int32_t nBottomRightItem = vCellTable.y * vCellTable.x + nTopLeftItem; 376 | 377 | // Clamp to size of child item vector 378 | nBottomRightItem = std::min(int32_t(items.size()), nBottomRightItem); 379 | int32_t nVisibleItems = nBottomRightItem - nTopLeftItem; 380 | 381 | // Draw Scroll Markers (if required) 382 | if (nTopVisibleRow > 0) 383 | { 384 | vPatchPos = { vSizeInPatches.x - 2, 0 }; 385 | olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; 386 | olc::vi2d vSourcePatch = { 3, 0 }; 387 | pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); 388 | } 389 | 390 | if ((nTotalRows - nTopVisibleRow) > vCellTable.y) 391 | { 392 | vPatchPos = { vSizeInPatches.x - 2, vSizeInPatches.y - 1 }; 393 | olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; 394 | olc::vi2d vSourcePatch = { 3, 2 }; 395 | pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); 396 | } 397 | 398 | // Draw Visible Items 399 | for (int32_t i = 0; i < nVisibleItems; i++) 400 | { 401 | // Cell location 402 | vCell.x = i % vCellTable.x; 403 | vCell.y = i / vCellTable.x; 404 | 405 | // Patch location (including border offset and padding) 406 | vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1; 407 | vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; 408 | 409 | // Actual screen location in pixels 410 | olc::vi2d vScreenLocation = vPatchPos * nPatch + vScreenOffset; 411 | 412 | // Display Item Header 413 | pge.DrawString(vScreenLocation, items[nTopLeftItem + i].sName, items[nTopLeftItem + i].bEnabled ? olc::WHITE : olc::DARK_GREY); 414 | 415 | if (items[nTopLeftItem + i].HasChildren()) 416 | { 417 | // Display Indicator that panel has a sub panel 418 | vPatchPos.x = vCell.x * (vCellSize.x + vCellPadding.x) + 1 + vCellSize.x; 419 | vPatchPos.y = vCell.y * (vCellSize.y + vCellPadding.y) + 1; 420 | olc::vi2d vSourcePatch = { 3, 1 }; 421 | vScreenLocation = vPatchPos * nPatch + vScreenOffset; 422 | pge.DrawPartialSprite(vScreenLocation, sprGFX, vSourcePatch * nPatch, vPatchSize); 423 | } 424 | } 425 | 426 | // Calculate cursor position in screen space in case system draws it 427 | vCursorPos.x = (vCellCursor.x * (vCellSize.x + vCellPadding.x)) * nPatch + vScreenOffset.x - nPatch; 428 | vCursorPos.y = ((vCellCursor.y - nTopVisibleRow) * (vCellSize.y + vCellPadding.y)) * nPatch + vScreenOffset.y + nPatch; 429 | } 430 | 431 | void Menu::ClampCursor() 432 | { 433 | // Find item in children 434 | nCursorItem = vCellCursor.y * vCellTable.x + vCellCursor.x; 435 | 436 | // Clamp Cursor 437 | if (nCursorItem >= int32_t(items.size())) 438 | { 439 | vCellCursor.y = (items.size() / vCellTable.x); 440 | vCellCursor.x = (items.size() % vCellTable.x) - 1; 441 | nCursorItem = items.size() - 1; 442 | } 443 | } 444 | 445 | void Menu::OnUp() 446 | { 447 | vCellCursor.y--; 448 | if (vCellCursor.y < 0) vCellCursor.y = 0; 449 | 450 | if (vCellCursor.y < nTopVisibleRow) 451 | { 452 | nTopVisibleRow--; 453 | if (nTopVisibleRow < 0) nTopVisibleRow = 0; 454 | } 455 | 456 | ClampCursor(); 457 | } 458 | 459 | void Menu::OnDown() 460 | { 461 | vCellCursor.y++; 462 | if (vCellCursor.y == nTotalRows) vCellCursor.y = nTotalRows - 1; 463 | 464 | if (vCellCursor.y > (nTopVisibleRow + vCellTable.y - 1)) 465 | { 466 | nTopVisibleRow++; 467 | if (nTopVisibleRow > (nTotalRows - vCellTable.y)) 468 | nTopVisibleRow = nTotalRows - vCellTable.y; 469 | } 470 | 471 | ClampCursor(); 472 | } 473 | 474 | void Menu::OnLeft() 475 | { 476 | vCellCursor.x--; 477 | if (vCellCursor.x < 0) vCellCursor.x = 0; 478 | ClampCursor(); 479 | } 480 | 481 | void Menu::OnRight() 482 | { 483 | vCellCursor.x++; 484 | if (vCellCursor.x == vCellTable.x) vCellCursor.x = vCellTable.x - 1; 485 | ClampCursor(); 486 | } 487 | 488 | Menu* Menu::OnConfirm() 489 | { 490 | // Check if selected item has children 491 | if (items[nCursorItem].HasChildren()) 492 | { 493 | return &items[nCursorItem]; 494 | } 495 | else 496 | return this; 497 | } 498 | 499 | Menu* Menu::GetSelectedItem() 500 | { 501 | return &items[nCursorItem]; 502 | } 503 | 504 | // ===================================================================== 505 | 506 | Manager::Manager() 507 | { 508 | } 509 | 510 | void Manager::Open(Menu* mo) 511 | { 512 | Close(); 513 | panels.push_back(mo); 514 | } 515 | 516 | void Manager::Close() 517 | { 518 | panels.clear(); 519 | } 520 | 521 | void Manager::OnUp() 522 | { 523 | if (!panels.empty()) panels.back()->OnUp(); 524 | } 525 | 526 | void Manager::OnDown() 527 | { 528 | if (!panels.empty()) panels.back()->OnDown(); 529 | } 530 | 531 | void Manager::OnLeft() 532 | { 533 | if (!panels.empty()) panels.back()->OnLeft(); 534 | } 535 | 536 | void Manager::OnRight() 537 | { 538 | if (!panels.empty()) panels.back()->OnRight(); 539 | } 540 | 541 | void Manager::OnBack() 542 | { 543 | if (!panels.empty()) panels.pop_back(); 544 | } 545 | 546 | Menu* Manager::OnConfirm() 547 | { 548 | if (panels.empty()) return nullptr; 549 | 550 | Menu* next = panels.back()->OnConfirm(); 551 | if (next == panels.back()) 552 | { 553 | if (panels.back()->GetSelectedItem()->Enabled()) 554 | return panels.back()->GetSelectedItem(); 555 | } 556 | else 557 | { 558 | if (next->Enabled()) 559 | panels.push_back(next); 560 | } 561 | 562 | return nullptr; 563 | } 564 | 565 | void Manager::Draw(olc::Sprite* sprGFX, olc::vi2d vScreenOffset) 566 | { 567 | if (panels.empty()) return; 568 | 569 | // Draw Visible Menu System 570 | for (auto& p : panels) 571 | { 572 | p->DrawSelf(*pge, sprGFX, vScreenOffset); 573 | vScreenOffset += {10, 10}; 574 | } 575 | 576 | // Draw Cursor 577 | olc::Pixel::Mode currentPixelMode = pge->GetPixelMode(); 578 | pge->SetPixelMode(olc::Pixel::ALPHA); 579 | pge->DrawPartialSprite(panels.back()->GetCursorPosition(), sprGFX, olc::vi2d(4, 0) * nPatch, { nPatch * 2, nPatch * 2 }); 580 | pge->SetPixelMode(currentPixelMode); 581 | } 582 | } 583 | }; 584 | 585 | 586 | #endif 587 | #endif 588 | -------------------------------------------------------------------------------- /extensions/olcPGEX_RayCastWorld.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_RayCastWorld.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Ray Cast World v1.02 | 7 | +-------------------------------------------------------------+ 8 | 9 | NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES 10 | 11 | What is this? 12 | ~~~~~~~~~~~~~ 13 | This is an extension to the olcPixelGameEngine, which provides 14 | a quick and easy to use, flexible, skinnable, ray-cast 3D world 15 | engine, which handles all graphics and collisions within a 16 | pseudo 3D world. 17 | 18 | It is designed to be implementation independent. Please see example 19 | files for usage instructions. 20 | 21 | Video: https://youtu.be/Vij_obgv9h4 22 | 23 | License (OLC-3) 24 | ~~~~~~~~~~~~~~~ 25 | 26 | Copyright 2018 - 2020 OneLoneCoder.com 27 | 28 | Redistribution and use in source and binary forms, with or without 29 | modification, are permitted provided that the following conditions 30 | are met: 31 | 32 | 1. Redistributions or derivations of source code must retain the above 33 | copyright notice, this list of conditions and the following disclaimer. 34 | 35 | 2. Redistributions or derivative works in binary form must reproduce 36 | the above copyright notice. This list of conditions and the following 37 | disclaimer must be reproduced in the documentation and/or other 38 | materials provided with the distribution. 39 | 40 | 3. Neither the name of the copyright holder nor the names of its 41 | contributors may be used to endorse or promote products derived 42 | from this software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 46 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 47 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 48 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 49 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 50 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 52 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 53 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 54 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | Links 57 | ~~~~~ 58 | YouTube: https://www.youtube.com/javidx9 59 | Discord: https://discord.gg/WhwHUMV 60 | Twitter: https://www.twitter.com/javidx9 61 | Twitch: https://www.twitch.tv/javidx9 62 | GitHub: https://www.github.com/onelonecoder 63 | Homepage: https://www.onelonecoder.com 64 | 65 | Author 66 | ~~~~~~ 67 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 68 | 69 | Revisions: 70 | 1.00: Initial Release 71 | 1.01: Fix NaN check on overlap distance (Thanks Dandistine) 72 | 1.02: Added dynamic step size for collisions 73 | */ 74 | 75 | #ifndef OLC_PGEX_RAYCASTWORLD_H 76 | #define OLC_PGEX_RAYCASTWORLD_H 77 | 78 | #include 79 | #include 80 | 81 | #include "olcPixelGameEngine.h" 82 | 83 | namespace olc 84 | { 85 | namespace rcw 86 | { 87 | // Base class for objects that exist in world 88 | class Object 89 | { 90 | public: 91 | // Linkage to user object description 92 | uint32_t nGenericID = 0; 93 | // Position in tile/world space 94 | olc::vf2d pos; 95 | // Velocity in tile/world space 96 | olc::vf2d vel; 97 | // Speed of object 98 | float fSpeed = 0.0f; 99 | // Angular direction of object 100 | float fHeading = 0.0f; 101 | // Collision radius of object 102 | float fRadius = 0.5f; 103 | // Is drawn? 104 | bool bVisible = true; 105 | // Flag to be removed form world 106 | bool bRemove = false; 107 | // Can collide with scenery? 108 | bool bCollideWithScenery = true; 109 | // Notify scenery collision? 110 | bool bNotifySceneryCollision = false; 111 | // Can collide with other objects? 112 | bool bCollideWithObjects = false; 113 | // Notify object collisions? 114 | bool bNotifyObjectCollision = false; 115 | // Can this object be moved by another object? 116 | bool bCanBeMoved = true; 117 | // Has physics/collisions applied 118 | bool bIsActive = true; 119 | 120 | void Walk(const float fWalkSpeed); 121 | void Strafe(const float fStrafeSpeed); 122 | void Turn(const float fTurnSpeed); 123 | void Stop(); 124 | }; 125 | 126 | // The RayCastWorld Engine - Inherit from this, implement abstract 127 | // methods, call Update() and Render() when required 128 | class Engine : public olc::PGEX 129 | { 130 | public: 131 | // Identifies side of cell 132 | enum class CellSide 133 | { 134 | North, 135 | East, 136 | South, 137 | West, 138 | Top, 139 | Bottom, 140 | }; 141 | 142 | public: 143 | // Construct world rednering parameters 144 | Engine(const int screen_w, const int screen_h, const float fov); 145 | 146 | protected: 147 | // ABSTRACT - User must return a suitable olc::Pixel depending on world location information provided 148 | virtual olc::Pixel SelectSceneryPixel(const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float sample_x, const float sample_y, const float distance) = 0; 149 | 150 | // ABSTRACT - User must return a boolean indicating if the tile is solid or not 151 | virtual bool IsLocationSolid(const float tile_x, const float tile_y) = 0; 152 | 153 | // ABSTRACT - User must return sizes of requested objects in Unit Cell Size 154 | virtual float GetObjectWidth(const uint32_t id) = 0; 155 | virtual float GetObjectHeight(const uint32_t id) = 0; 156 | 157 | // ABSTRACT - User must return suitable olc::Pixel for object sprite sample location 158 | virtual olc::Pixel SelectObjectPixel(const uint32_t id, const float sample_x, const float sample_y, const float distance, const float angle) = 0; 159 | 160 | // OPTIONAL - User can handle collsiion response with scenery should they choose to 161 | virtual void HandleObjectVsScenery(std::shared_ptr object, const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float offset_x, const float offset_y); 162 | 163 | // OPTIONAL - User can handle collsiion response with objects should they choose to 164 | virtual void HandleObjectVsObject(std::shared_ptr object1, std::shared_ptr object2); 165 | 166 | 167 | 168 | public: 169 | // Sets In-Game Camera position 170 | void SetCamera(const olc::vf2d& pos, const float heading); 171 | 172 | // Called to update world state 173 | virtual void Update(float fElapsedTime); 174 | 175 | // Called to draw the world and its contents 176 | void Render(); 177 | 178 | public: 179 | std::unordered_map> mapObjects; 180 | 181 | private: 182 | // A convenient utility struct to store all the info required to understand how a ray 183 | // has hit a specific tile 184 | struct sTileHit 185 | { 186 | olc::vi2d vTilePos = { 0,0 }; 187 | olc::vf2d vHitPos = { 0,0 }; 188 | float fLength = 0.0f; 189 | float fSampleX = 0.0f; 190 | Engine::CellSide eSide = Engine::CellSide::North; 191 | }; 192 | 193 | // Cast ray into tile world, and return info about what it hits (if anything) 194 | bool CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit); 195 | 196 | // Convenient constants in algorithms 197 | const olc::vi2d vScreenSize; 198 | const olc::vi2d vHalfScreenSize; 199 | const olc::vf2d vFloatScreenSize; 200 | float fFieldOfView = 0.0f; 201 | 202 | // A depth buffer used to sort pixels in Z-Axis 203 | std::unique_ptr pDepthBuffer; 204 | 205 | // Local store of camera position and direction 206 | olc::vf2d vCameraPos = { 5.0f, 5.0f }; 207 | float fCameraHeading = 0.0f; 208 | }; 209 | } 210 | } 211 | 212 | 213 | #ifdef OLC_PGEX_RAYCASTWORLD 214 | #undef OLC_PGEX_RAYCASTWORLD 215 | 216 | #undef min 217 | #undef max 218 | 219 | void olc::rcw::Object::Walk(const float fWalkSpeed) 220 | { 221 | fSpeed = fWalkSpeed; 222 | vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)) * fSpeed; 223 | } 224 | 225 | void olc::rcw::Object::Strafe(const float fStrafeSpeed) 226 | { 227 | fSpeed = fStrafeSpeed; 228 | vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)).perp() * fSpeed; 229 | } 230 | 231 | void olc::rcw::Object::Turn(const float fTurnSpeed) 232 | { 233 | fHeading += fTurnSpeed; 234 | 235 | // Wrap heading to sensible angle 236 | if (fHeading < -3.14159f) fHeading += 2.0f * 3.14159f; 237 | if (fHeading > 3.14159f) fHeading -= 2.0f * 3.14159f; 238 | } 239 | 240 | 241 | void olc::rcw::Object::Stop() 242 | { 243 | fSpeed = 0; 244 | vel = { 0,0 }; 245 | } 246 | 247 | olc::rcw::Engine::Engine(const int screen_w, const int screen_h, const float fov) : 248 | vScreenSize(screen_w, screen_h), 249 | vHalfScreenSize(screen_w / 2, screen_h / 2), 250 | vFloatScreenSize(float(screen_w), float(screen_h)) 251 | { 252 | fFieldOfView = fov; 253 | pDepthBuffer.reset(new float[vScreenSize.x * vScreenSize.y]); 254 | } 255 | 256 | 257 | void olc::rcw::Engine::SetCamera(const olc::vf2d& pos, const float heading) 258 | { 259 | vCameraPos = pos; 260 | fCameraHeading = heading; 261 | } 262 | 263 | 264 | void olc::rcw::Engine::Update(float fElapsedTime) 265 | { 266 | // Update the position and statically resolve for collisions against the map 267 | for (auto& ob : mapObjects) 268 | { 269 | std::shared_ptr object = ob.second; 270 | if (!object->bIsActive) continue; 271 | 272 | int nSteps = 1; 273 | float fDelta = fElapsedTime; 274 | float fTotalTravel = (object->vel * fElapsedTime).mag2(); 275 | float fTotalRadius = (object->fRadius * object->fRadius); 276 | 277 | if(fTotalTravel >= fTotalRadius) 278 | { 279 | float fSteps = std::ceil(fTotalTravel / fTotalRadius); 280 | nSteps = int(fSteps); 281 | fDelta = fElapsedTime / fSteps; 282 | } 283 | 284 | for (int nStep = 0; nStep < nSteps; nStep++) 285 | { 286 | // Determine where object is trying to be 287 | olc::vf2d vPotentialPosition = object->pos + object->vel * fDelta; 288 | 289 | // If the object can collide with other objects 290 | if (object->bCollideWithObjects) 291 | { 292 | // Iterate through all other objects (this can be costly) 293 | for (auto& ob2 : mapObjects) 294 | { 295 | std::shared_ptr target = ob2.second; 296 | 297 | // Ignore if target object cant interact 298 | if (!target->bCollideWithObjects) continue; 299 | 300 | // Don't test against self 301 | if (target == object) continue; 302 | 303 | // Quick check to see if objects overlap... 304 | if ((target->pos - object->pos).mag2() <= (target->fRadius + object->fRadius) * (target->fRadius + object->fRadius)) 305 | { 306 | // ..they do. Calculate displacement required 307 | float fDistance = (target->pos - object->pos).mag(); 308 | float fOverlap = 1.0f * (fDistance - object->fRadius - target->fRadius); 309 | 310 | // Object will always give way to target 311 | vPotentialPosition -= (object->pos - target->pos) / fDistance * fOverlap; 312 | 313 | if (target->bCanBeMoved) 314 | target->pos += (object->pos - target->pos) / fDistance * fOverlap; 315 | 316 | if (object->bNotifyObjectCollision) 317 | HandleObjectVsObject(object, target); 318 | } 319 | 320 | 321 | } 322 | } 323 | 324 | // If the object can collide with scenery... 325 | if (object->bCollideWithScenery) 326 | { 327 | // ...Determine an area of cells to check for collision. We use a region 328 | // to account for diagonal collisions, and corner collisions. 329 | olc::vi2d vCurrentCell = object->pos; 330 | olc::vi2d vTargetCell = vPotentialPosition; 331 | olc::vi2d vAreaTL = { std::min(vCurrentCell.x, vTargetCell.x) - 1, std::min(vCurrentCell.y, vTargetCell.y) - 1 }; 332 | olc::vi2d vAreaBR = { std::max(vCurrentCell.x, vTargetCell.x) + 1, std::max(vCurrentCell.y, vTargetCell.y) + 1 }; 333 | 334 | 335 | 336 | 337 | // Iterate through each cell in test area 338 | olc::vi2d vCell; 339 | for (vCell.y = vAreaTL.y; vCell.y <= vAreaBR.y; vCell.y++) 340 | { 341 | for (vCell.x = vAreaTL.x; vCell.x <= vAreaBR.x; vCell.x++) 342 | { 343 | // Check if the cell is actually solid... 344 | olc::vf2d vCellMiddle = olc::vf2d(float(vCell.x) + 0.5f, float(vCell.y) + 0.5f); 345 | if (IsLocationSolid(vCellMiddle.x, vCellMiddle.y)) 346 | { 347 | // ...it is! So work out nearest point to future player position, around perimeter 348 | // of cell rectangle. We can test the distance to this point to see if we have 349 | // collided. 350 | 351 | olc::vf2d vNearestPoint; 352 | // Inspired by this (very clever btw) 353 | // https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response 354 | vNearestPoint.x = std::max(float(vCell.x), std::min(vPotentialPosition.x, float(vCell.x + 1))); 355 | vNearestPoint.y = std::max(float(vCell.y), std::min(vPotentialPosition.y, float(vCell.y + 1))); 356 | 357 | // But modified to work :P 358 | olc::vf2d vRayToNearest = vNearestPoint - vPotentialPosition; 359 | float fOverlap = object->fRadius - vRayToNearest.mag(); 360 | if (std::isnan(fOverlap)) fOverlap = 0;// Thanks Dandistine! 361 | 362 | // If overlap is positive, then a collision has occurred, so we displace backwards by the 363 | // overlap amount. The potential position is then tested against other tiles in the area 364 | // therefore "statically" resolving the collision 365 | if (fOverlap > 0) 366 | { 367 | // Statically resolve the collision 368 | vPotentialPosition = vPotentialPosition - vRayToNearest.norm() * fOverlap; 369 | 370 | // Notify system that a collision has occurred 371 | if (object->bNotifySceneryCollision) 372 | { 373 | olc::rcw::Engine::CellSide side = olc::rcw::Engine::CellSide::Bottom; 374 | if (vNearestPoint.x == float(vCell.x)) side = olc::rcw::Engine::CellSide::West; 375 | if (vNearestPoint.x == float(vCell.x + 1)) side = olc::rcw::Engine::CellSide::East; 376 | if (vNearestPoint.y == float(vCell.y)) side = olc::rcw::Engine::CellSide::North; 377 | if (vNearestPoint.y == float(vCell.y + 1)) side = olc::rcw::Engine::CellSide::South; 378 | 379 | HandleObjectVsScenery(object, vCell.x, vCell.y, side, vNearestPoint.x - float(vCell.x), vNearestPoint.y - float(vCell.y)); 380 | } 381 | } 382 | } 383 | } 384 | } 385 | } 386 | 387 | // Set the objects new position to the allowed potential position 388 | object->pos = vPotentialPosition; 389 | } 390 | } 391 | } 392 | 393 | void olc::rcw::Engine::Render() 394 | { 395 | // Utility lambda to draw to screen and depth buffer 396 | auto DepthDraw = [&](int x, int y, float z, olc::Pixel p) 397 | { 398 | if (z <= pDepthBuffer[y * vScreenSize.x + x]) 399 | { 400 | pge->Draw(x, y, p); 401 | pDepthBuffer[y * vScreenSize.x + x] = z; 402 | } 403 | }; 404 | 405 | 406 | // Clear screen and depth buffer ======================================== 407 | // pge->Clear(olc::BLACK); <- Left to user to decide 408 | for (int i = 0; i < vScreenSize.x * vScreenSize.y; i++) 409 | pDepthBuffer[i] = INFINITY; 410 | 411 | // Draw World =========================================================== 412 | 413 | // For each column on screen... 414 | for (int x = 0; x < vScreenSize.x; x++) 415 | { 416 | // ...create a ray eminating from player position into world... 417 | float fRayAngle = (fCameraHeading - (fFieldOfView / 2.0f)) + (float(x) / vFloatScreenSize.x) * fFieldOfView; 418 | 419 | // ...create unit vector for that ray... 420 | olc::vf2d vRayDirection = { std::cos(fRayAngle), std::sin(fRayAngle) }; 421 | 422 | // ... and cast ray into world, see what it hits (if anything) 423 | sTileHit hit; 424 | 425 | // Assuming it hits nothing, then we draw to the middle of the screen (far far away) 426 | float fRayLength = INFINITY; 427 | 428 | // Otherwise... 429 | if (CastRayDDA(vCameraPos, vRayDirection, hit)) 430 | { 431 | // It has hit something, so extract information to draw column 432 | olc::vf2d vRay = hit.vHitPos - vCameraPos; 433 | 434 | // Length of ray is vital for pseudo-depth, but we'll also cosine correct to remove fisheye 435 | fRayLength = vRay.mag() * std::cos(fRayAngle - fCameraHeading); 436 | } 437 | 438 | // Calculate locations in column that divides ceiling, wall and floor 439 | float fCeiling = (vFloatScreenSize.y / 2.0f) - (vFloatScreenSize.y / fRayLength); 440 | float fFloor = vFloatScreenSize.y - fCeiling; 441 | float fWallHeight = fFloor - fCeiling; 442 | float fFloorHeight = vFloatScreenSize.y - fFloor; 443 | 444 | // Now draw the column from top to bottom 445 | for (int y = 0; y < vScreenSize.y; y++) 446 | { 447 | if (y <= int(fCeiling)) 448 | { 449 | // For floors and ceilings, we don't use the ray, instead we just pseudo-project 450 | // a plane, a la Mode 7. First calculate depth into screen... 451 | float fPlaneZ = (vFloatScreenSize.y / 2.0f) / ((vFloatScreenSize.y / 2.0f) - float(y)); 452 | 453 | // ... then project polar coordinate (r, theta) from camera into screen (x, y), again 454 | // compensating with cosine to remove fisheye 455 | olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading); 456 | 457 | // Work out which planar tile we are in 458 | int nPlaneTileX = int(vPlanePoint.x); 459 | int nPlaneTileY = int(vPlanePoint.y); 460 | 461 | // Work out normalised offset into planar tile 462 | float fPlaneSampleX = vPlanePoint.x - nPlaneTileX; 463 | float fPlaneSampleY = vPlanePoint.y - nPlaneTileY; 464 | 465 | // Location is marked as ceiling 466 | olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Top, fPlaneSampleX, fPlaneSampleY, fPlaneZ); 467 | 468 | // Draw ceiling pixel - no depth buffer required 469 | pge->Draw(x, y, pixel); 470 | } 471 | else if (y > int(fCeiling) && y <= int(fFloor)) 472 | { 473 | // Location is marked as wall. Here we will sample the appropriate 474 | // texture at the hit location. The U sample coordinate is provided 475 | // by the "hit" structure, though we will need to scan the V sample 476 | // coordinate based upon the height of the wall in screen space 477 | 478 | // Create normalised "V" based on height of wall --> (0.0 to 1.0) 479 | float fSampleY = (float(y) - fCeiling) / fWallHeight; 480 | 481 | // Select appropriate texture of that wall 482 | olc::Pixel pixel = SelectSceneryPixel(hit.vTilePos.x, hit.vTilePos.y, hit.eSide, hit.fSampleX, fSampleY, fRayLength); 483 | 484 | // Finally draw the screen pixel doing a depth test too 485 | DepthDraw(x, y, fRayLength, pixel); 486 | } 487 | else 488 | { 489 | // For floors and ceilings, we don't use the ray, instead we just pseudo-project 490 | // a plane, a la Mode 7. First calculate depth into screen... 491 | float fPlaneZ = (vFloatScreenSize.y / 2.0f) / (float(y) - (vFloatScreenSize.y / 2.0f)); 492 | 493 | // ... then project polar coordinate (r, theta) from camera into screen (x, y), again 494 | // compensating with cosine to remove fisheye 495 | olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading); 496 | 497 | // Work out which planar tile we are in 498 | int nPlaneTileX = int(vPlanePoint.x); 499 | int nPlaneTileY = int(vPlanePoint.y); 500 | 501 | // Work out normalised offset into planar tile 502 | float fPlaneSampleX = vPlanePoint.x - nPlaneTileX; 503 | float fPlaneSampleY = vPlanePoint.y - nPlaneTileY; 504 | 505 | // Location is marked as floor 506 | olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Bottom, fPlaneSampleX, fPlaneSampleY, fPlaneZ); 507 | 508 | // Draw floor pixel - no depth buffer required 509 | pge->Draw(x, y, pixel); 510 | } 511 | } 512 | } 513 | 514 | // Scenery is now drawn, and depth buffer is filled. We can now draw 515 | // the ingame objects. Assuming binary transparency, we've no need to 516 | // sort objects 517 | 518 | // Iterate through all in game objects 519 | for (const auto& ob : mapObjects) 520 | { 521 | const std::shared_ptr object = ob.second; 522 | 523 | // If object is invisible, nothing to do - this is useful 524 | // for both effects, and making sure we dont render the 525 | // "player" at the camera location perhaps 526 | if (!object->bVisible) continue; 527 | 528 | // Create vector from camera to object 529 | olc::vf2d vObject = object->pos - vCameraPos; 530 | 531 | // Calculate distance object is away from camera 532 | float fDistanceToObject = vObject.mag(); 533 | 534 | // Check if object center is within camera FOV... 535 | float fObjectAngle = atan2f(vObject.y, vObject.x) - fCameraHeading; 536 | if (fObjectAngle < -3.14159f) fObjectAngle += 2.0f * 3.14159f; 537 | if (fObjectAngle > 3.14159f) fObjectAngle -= 2.0f * 3.14159f; 538 | 539 | // ...with a bias based upon distance - allows us to have object centers offscreen 540 | bool bInPlayerFOV = fabs(fObjectAngle) < (fFieldOfView + (1.0f / fDistanceToObject)) / 2.0f; 541 | 542 | // If object is within view, and not too close to camera, draw it! 543 | if (bInPlayerFOV && vObject.mag() >= 0.5f) 544 | { 545 | // Work out its position on the floor... 546 | olc::vf2d vFloorPoint; 547 | 548 | // Horizontal screen location is determined based on object angle relative to camera heading 549 | vFloorPoint.x = (0.5f * ((fObjectAngle / (fFieldOfView * 0.5f))) + 0.5f) * vFloatScreenSize.x; 550 | 551 | // Vertical screen location is projected distance 552 | vFloorPoint.y = (vFloatScreenSize.y / 2.0f) + (vFloatScreenSize.y / fDistanceToObject) / std::cos(fObjectAngle / 2.0f); 553 | 554 | // First we need the objects size... 555 | olc::vf2d vObjectSize = { float(GetObjectWidth(object->nGenericID)), float(GetObjectHeight(object->nGenericID)) }; 556 | 557 | // ...which we can scale into world space (maintaining aspect ratio)... 558 | vObjectSize *= 2.0f * vFloatScreenSize.y; 559 | 560 | // ...then project into screen space 561 | vObjectSize /= fDistanceToObject; 562 | 563 | // Second we need the objects top left position in screen space... 564 | olc::vf2d vObjectTopLeft; 565 | 566 | // ...which is relative to the objects size and assumes the middle of the object is 567 | // the location in world space 568 | vObjectTopLeft = { vFloorPoint.x - vObjectSize.x / 2.0f, vFloorPoint.y - vObjectSize.y }; 569 | 570 | // Now iterate through the objects screen pixels 571 | for (float y = 0; y < vObjectSize.y; y++) 572 | { 573 | for (float x = 0; x < vObjectSize.x; x++) 574 | { 575 | // Create a normalised sample coordinate 576 | float fSampleX = x / vObjectSize.x; 577 | float fSampleY = y / vObjectSize.y; 578 | 579 | // Get pixel from a suitable texture 580 | float fNiceAngle = fCameraHeading - object->fHeading + 3.14159f / 4.0f; 581 | if (fNiceAngle < 0) fNiceAngle += 2.0f * 3.14159f; 582 | if (fNiceAngle > 2.0f * 3.14159f) fNiceAngle -= 2.0f * 3.14159f; 583 | olc::Pixel p = SelectObjectPixel(object->nGenericID, fSampleX, fSampleY, fDistanceToObject, fNiceAngle); 584 | 585 | // Calculate screen pixel location 586 | olc::vi2d a = { int(vObjectTopLeft.x + x), int(vObjectTopLeft.y + y) }; 587 | 588 | // Check if location is actually on screen (to not go OOB on depth buffer) 589 | // and if the pixel is indeed visible (has no transparency component) 590 | if (a.x >= 0 && a.x < vScreenSize.x && a.y >= 0 && a.y < vScreenSize.y && p.a == 255) 591 | { 592 | // Draw the pixel taking into account the depth buffer 593 | DepthDraw(a.x, a.y, fDistanceToObject, p); 594 | } 595 | } 596 | } 597 | } 598 | } 599 | } 600 | 601 | void olc::rcw::Engine::HandleObjectVsScenery(std::shared_ptr object, const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float offset_x, const float offset_y) 602 | {} 603 | 604 | void olc::rcw::Engine::HandleObjectVsObject(std::shared_ptr object1, std::shared_ptr object2) 605 | {} 606 | 607 | // Will be explained in upcoming video... 608 | bool olc::rcw::Engine::CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit) 609 | { 610 | olc::vf2d vRayDelta = { sqrt(1 + (vDirection.y / vDirection.x) * (vDirection.y / vDirection.x)), sqrt(1 + (vDirection.x / vDirection.y) * (vDirection.x / vDirection.y)) }; 611 | 612 | olc::vi2d vMapCheck = vOrigin; 613 | olc::vf2d vSideDistance; 614 | olc::vi2d vStepDistance; 615 | 616 | if (vDirection.x < 0) 617 | { 618 | vStepDistance.x = -1; 619 | vSideDistance.x = (vOrigin.x - (float)vMapCheck.x) * vRayDelta.x; 620 | } 621 | else 622 | { 623 | vStepDistance.x = 1; 624 | vSideDistance.x = ((float)vMapCheck.x + 1.0f - vOrigin.x) * vRayDelta.x; 625 | } 626 | 627 | if (vDirection.y < 0) 628 | { 629 | vStepDistance.y = -1; 630 | vSideDistance.y = (vOrigin.y - (float)vMapCheck.y) * vRayDelta.y; 631 | } 632 | else 633 | { 634 | vStepDistance.y = 1; 635 | vSideDistance.y = ((float)vMapCheck.y + 1.0f - vOrigin.y) * vRayDelta.y; 636 | } 637 | 638 | olc::vf2d vIntersection; 639 | olc::vi2d vHitTile; 640 | float fMaxDistance = 100.0f; 641 | float fDistance = 0.0f; 642 | bool bTileFound = false; 643 | while (!bTileFound && fDistance < fMaxDistance) 644 | { 645 | if (vSideDistance.x < vSideDistance.y) 646 | { 647 | vSideDistance.x += vRayDelta.x; 648 | vMapCheck.x += vStepDistance.x; 649 | } 650 | else 651 | { 652 | vSideDistance.y += vRayDelta.y; 653 | vMapCheck.y += vStepDistance.y; 654 | } 655 | 656 | olc::vf2d rayDist = { (float)vMapCheck.x - vOrigin.x, (float)vMapCheck.y - vOrigin.y }; 657 | fDistance = rayDist.mag(); 658 | 659 | 660 | if (IsLocationSolid(float(vMapCheck.x), float(vMapCheck.y))) 661 | { 662 | vHitTile = vMapCheck; 663 | bTileFound = true; 664 | 665 | hit.vTilePos = vMapCheck; 666 | 667 | 668 | // Find accurate Hit Location 669 | 670 | float m = vDirection.y / vDirection.x; 671 | 672 | 673 | // From Top Left 674 | 675 | if (vOrigin.y <= vMapCheck.y) 676 | { 677 | if (vOrigin.x <= vMapCheck.x) 678 | { 679 | hit.eSide = olc::rcw::Engine::CellSide::West; 680 | vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y; 681 | vIntersection.x = float(vMapCheck.x); 682 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 683 | } 684 | else if (vOrigin.x >= (vMapCheck.x + 1)) 685 | { 686 | hit.eSide = olc::rcw::Engine::CellSide::East; 687 | vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y; 688 | vIntersection.x = float(vMapCheck.x + 1); 689 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 690 | } 691 | else 692 | { 693 | hit.eSide = olc::rcw::Engine::CellSide::North; 694 | vIntersection.y = float(vMapCheck.y); 695 | vIntersection.x = (vMapCheck.y - vOrigin.y) / m + vOrigin.x; 696 | hit.fSampleX = vIntersection.x - std::floor(vIntersection.x); 697 | } 698 | 699 | 700 | if (vIntersection.y < vMapCheck.y) 701 | { 702 | hit.eSide = olc::rcw::Engine::CellSide::North; 703 | vIntersection.y = float(vMapCheck.y); 704 | vIntersection.x = (vMapCheck.y - vOrigin.y) / m + vOrigin.x; 705 | hit.fSampleX = vIntersection.x - std::floor(vIntersection.x); 706 | } 707 | } 708 | else if (vOrigin.y >= vMapCheck.y + 1) 709 | { 710 | if (vOrigin.x <= vMapCheck.x) 711 | { 712 | hit.eSide = olc::rcw::Engine::CellSide::West; 713 | vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y; 714 | vIntersection.x = float(vMapCheck.x); 715 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 716 | } 717 | else if (vOrigin.x >= (vMapCheck.x + 1)) 718 | { 719 | hit.eSide = olc::rcw::Engine::CellSide::East; 720 | vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y; 721 | vIntersection.x = float(vMapCheck.x + 1); 722 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 723 | } 724 | else 725 | { 726 | hit.eSide = olc::rcw::Engine::CellSide::South; 727 | vIntersection.y = float(vMapCheck.y + 1); 728 | vIntersection.x = ((vMapCheck.y + 1) - vOrigin.y) / m + vOrigin.x; 729 | hit.fSampleX = vIntersection.x - std::floor(vIntersection.x); 730 | } 731 | 732 | if (vIntersection.y > (vMapCheck.y + 1)) 733 | { 734 | hit.eSide = olc::rcw::Engine::CellSide::South; 735 | vIntersection.y = float(vMapCheck.y + 1); 736 | vIntersection.x = ((vMapCheck.y + 1) - vOrigin.y) / m + vOrigin.x; 737 | hit.fSampleX = vIntersection.x - std::floor(vIntersection.x); 738 | } 739 | } 740 | else 741 | { 742 | if (vOrigin.x <= vMapCheck.x) 743 | { 744 | hit.eSide = olc::rcw::Engine::CellSide::West; 745 | vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y; 746 | vIntersection.x = float(vMapCheck.x); 747 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 748 | } 749 | else if (vOrigin.x >= (vMapCheck.x + 1)) 750 | { 751 | hit.eSide = olc::rcw::Engine::CellSide::East; 752 | vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y; 753 | vIntersection.x = float(vMapCheck.x + 1); 754 | hit.fSampleX = vIntersection.y - std::floor(vIntersection.y); 755 | } 756 | } 757 | 758 | hit.vHitPos = vIntersection; 759 | } 760 | } 761 | 762 | return bTileFound; 763 | } 764 | 765 | 766 | #endif // OLC_PGEX_RAYCASTWORLD 767 | #endif // OLC_PGEX_RAYCASTWORLD_H 768 | -------------------------------------------------------------------------------- /extensions/olcPGEX_Shaders.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/extensions/olcPGEX_Shaders.h -------------------------------------------------------------------------------- /extensions/olcPGEX_Sound.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_Sound.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Sound - v0.4 | 7 | +-------------------------------------------------------------+ 8 | 9 | What is this? 10 | ~~~~~~~~~~~~~ 11 | This is an extension to the olcPixelGameEngine, which provides 12 | sound generation and wave playing routines. 13 | 14 | Special Thanks: 15 | ~~~~~~~~~~~~~~~ 16 | Slavka - For entire non-windows system back end! 17 | Gorbit99 - Testing, Bug Fixes 18 | Cyberdroid - Testing, Bug Fixes 19 | Dragoneye - Testing 20 | Puol - Testing 21 | 22 | License (OLC-3) 23 | ~~~~~~~~~~~~~~~ 24 | 25 | Copyright 2018 - 2019 OneLoneCoder.com 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions 29 | are met: 30 | 31 | 1. Redistributions or derivations of source code must retain the above 32 | copyright notice, this list of conditions and the following disclaimer. 33 | 34 | 2. Redistributions or derivative works in binary form must reproduce 35 | the above copyright notice. This list of conditions and the following 36 | disclaimer must be reproduced in the documentation and/or other 37 | materials provided with the distribution. 38 | 39 | 3. Neither the name of the copyright holder nor the names of its 40 | contributors may be used to endorse or promote products derived 41 | from this software without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | 55 | Links 56 | ~~~~~ 57 | YouTube: https://www.youtube.com/javidx9 58 | Discord: https://discord.gg/WhwHUMV 59 | Twitter: https://www.twitter.com/javidx9 60 | Twitch: https://www.twitch.tv/javidx9 61 | GitHub: https://www.github.com/onelonecoder 62 | Homepage: https://www.onelonecoder.com 63 | Patreon: https://www.patreon.com/javidx9 64 | 65 | Author 66 | ~~~~~~ 67 | David Barr, aka javidx9, ©OneLoneCoder 2019 68 | */ 69 | 70 | 71 | #ifndef OLC_PGEX_SOUND_H 72 | #define OLC_PGEX_SOUND_H 73 | 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | 80 | #include "olcPixelGameEngine.h" 81 | 82 | #undef min 83 | #undef max 84 | 85 | // Choose a default sound backend 86 | #if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS) 87 | #ifdef __linux__ 88 | #define USE_ALSA 89 | #endif 90 | 91 | #ifdef __EMSCRIPTEN__ 92 | #define USE_OPENAL 93 | #endif 94 | 95 | #ifdef _WIN32 96 | #define USE_WINDOWS 97 | #endif 98 | 99 | #endif 100 | 101 | #ifdef USE_ALSA 102 | #define ALSA_PCM_NEW_HW_PARAMS_API 103 | #include 104 | #endif 105 | 106 | #ifdef USE_OPENAL 107 | #include 108 | #include 109 | #include 110 | #endif 111 | 112 | #pragma pack(push, 1) 113 | typedef struct { 114 | uint16_t wFormatTag; 115 | uint16_t nChannels; 116 | uint32_t nSamplesPerSec; 117 | uint32_t nAvgBytesPerSec; 118 | uint16_t nBlockAlign; 119 | uint16_t wBitsPerSample; 120 | uint16_t cbSize; 121 | } OLC_WAVEFORMATEX; 122 | #pragma pack(pop) 123 | 124 | namespace olc 125 | { 126 | // Container class for Advanced 2D Drawing functions 127 | class SOUND : public olc::PGEX 128 | { 129 | // A representation of an affine transform, used to rotate, scale, offset & shear space 130 | public: 131 | class AudioSample 132 | { 133 | public: 134 | AudioSample(); 135 | AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); 136 | olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr); 137 | 138 | public: 139 | OLC_WAVEFORMATEX wavHeader; 140 | float *fSample = nullptr; 141 | long nSamples = 0; 142 | int nChannels = 0; 143 | bool bSampleValid = false; 144 | }; 145 | 146 | struct sCurrentlyPlayingSample 147 | { 148 | int nAudioSampleID = 0; 149 | long nSamplePosition = 0; 150 | bool bFinished = false; 151 | bool bLoop = false; 152 | bool bFlagForStop = false; 153 | }; 154 | 155 | static std::list listActiveSamples; 156 | 157 | public: 158 | static bool InitialiseAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); 159 | static bool DestroyAudio(); 160 | static void SetUserSynthFunction(std::function func); 161 | static void SetUserFilterFunction(std::function func); 162 | 163 | public: 164 | static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); 165 | static void PlaySample(int id, bool bLoop = false); 166 | static void StopSample(int id); 167 | static void StopAll(); 168 | static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); 169 | 170 | 171 | private: 172 | #ifdef USE_WINDOWS // Windows specific sound management 173 | static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); 174 | static unsigned int m_nSampleRate; 175 | static unsigned int m_nChannels; 176 | static unsigned int m_nBlockCount; 177 | static unsigned int m_nBlockSamples; 178 | static unsigned int m_nBlockCurrent; 179 | static short* m_pBlockMemory; 180 | static WAVEHDR *m_pWaveHeaders; 181 | static HWAVEOUT m_hwDevice; 182 | static std::atomic m_nBlockFree; 183 | static std::condition_variable m_cvBlockNotZero; 184 | static std::mutex m_muxBlockNotZero; 185 | #endif 186 | 187 | #ifdef USE_ALSA 188 | static snd_pcm_t *m_pPCM; 189 | static unsigned int m_nSampleRate; 190 | static unsigned int m_nChannels; 191 | static unsigned int m_nBlockSamples; 192 | static short* m_pBlockMemory; 193 | #endif 194 | 195 | #ifdef USE_OPENAL 196 | static std::queue m_qAvailableBuffers; 197 | static ALuint *m_pBuffers; 198 | static ALuint m_nSource; 199 | static ALCdevice *m_pDevice; 200 | static ALCcontext *m_pContext; 201 | static unsigned int m_nSampleRate; 202 | static unsigned int m_nChannels; 203 | static unsigned int m_nBlockCount; 204 | static unsigned int m_nBlockSamples; 205 | static short* m_pBlockMemory; 206 | #endif 207 | 208 | static void AudioThread(); 209 | static std::thread m_AudioThread; 210 | static std::atomic m_bAudioThreadActive; 211 | static std::atomic m_fGlobalTime; 212 | static std::function funcUserSynth; 213 | static std::function funcUserFilter; 214 | }; 215 | } 216 | 217 | 218 | // Implementation, platform-independent 219 | 220 | #ifdef OLC_PGEX_SOUND 221 | #undef OLC_PGEX_SOUND 222 | 223 | namespace olc 224 | { 225 | SOUND::AudioSample::AudioSample() 226 | { } 227 | 228 | SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack) 229 | { 230 | LoadFromFile(sWavFile, pack); 231 | } 232 | 233 | olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, olc::ResourcePack *pack) 234 | { 235 | auto ReadWave = [&](std::istream &is) 236 | { 237 | char dump[4]; 238 | is.read(dump, sizeof(char) * 4); // Read "RIFF" 239 | if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL; 240 | is.read(dump, sizeof(char) * 4); // Not Interested 241 | is.read(dump, sizeof(char) * 4); // Read "WAVE" 242 | if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL; 243 | 244 | // Read Wave description chunk 245 | is.read(dump, sizeof(char) * 4); // Read "fmt " 246 | unsigned int nHeaderSize = 0; 247 | is.read((char*)&nHeaderSize, sizeof(unsigned int)); // Not Interested 248 | is.read((char*)&wavHeader, nHeaderSize);// sizeof(WAVEFORMATEX)); // Read Wave Format Structure chunk 249 | // Note the -2, because the structure has 2 bytes to indicate its own size 250 | // which are not in the wav file 251 | 252 | // Just check if wave format is compatible with olcPGE 253 | if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) 254 | return olc::FAIL; 255 | 256 | // Search for audio data chunk 257 | uint32_t nChunksize = 0; 258 | is.read(dump, sizeof(char) * 4); // Read chunk header 259 | is.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size 260 | while (strncmp(dump, "data", 4) != 0) 261 | { 262 | // Not audio data, so just skip it 263 | //std::fseek(f, nChunksize, SEEK_CUR); 264 | is.seekg(nChunksize, std::istream::cur); 265 | is.read(dump, sizeof(char) * 4); 266 | is.read((char*)&nChunksize, sizeof(uint32_t)); 267 | } 268 | 269 | // Finally got to data, so read it all in and convert to float samples 270 | nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); 271 | nChannels = wavHeader.nChannels; 272 | 273 | // Create floating point buffer to hold audio sample 274 | fSample = new float[nSamples * nChannels]; 275 | float *pSample = fSample; 276 | 277 | // Read in audio data and normalise 278 | for (long i = 0; i < nSamples; i++) 279 | { 280 | for (int c = 0; c < nChannels; c++) 281 | { 282 | short s = 0; 283 | if (!is.eof()) 284 | { 285 | is.read((char*)&s, sizeof(short)); 286 | 287 | *pSample = (float)s / (float)(SHRT_MAX); 288 | pSample++; 289 | } 290 | } 291 | } 292 | 293 | // All done, flag sound as valid 294 | bSampleValid = true; 295 | return olc::OK; 296 | }; 297 | 298 | if (pack != nullptr) 299 | { 300 | olc::ResourceBuffer rb = pack->GetFileBuffer(sWavFile); 301 | std::istream is(&rb); 302 | return ReadWave(is); 303 | } 304 | else 305 | { 306 | // Read from file 307 | std::ifstream ifs(sWavFile, std::ifstream::binary); 308 | if (ifs.is_open()) 309 | { 310 | return ReadWave(ifs); 311 | } 312 | else 313 | return olc::FAIL; 314 | } 315 | } 316 | 317 | // This vector holds all loaded sound samples in memory 318 | std::vector vecAudioSamples; 319 | 320 | // This structure represents a sound that is currently playing. It only 321 | // holds the sound ID and where this instance of it is up to for its 322 | // current playback 323 | 324 | void SOUND::SetUserSynthFunction(std::function func) 325 | { 326 | funcUserSynth = func; 327 | } 328 | 329 | void SOUND::SetUserFilterFunction(std::function func) 330 | { 331 | funcUserFilter = func; 332 | } 333 | 334 | // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID 335 | // number is returned if successful, otherwise -1 336 | int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) 337 | { 338 | 339 | olc::SOUND::AudioSample a(sWavFile, pack); 340 | if (a.bSampleValid) 341 | { 342 | vecAudioSamples.push_back(a); 343 | return (unsigned int)vecAudioSamples.size(); 344 | } 345 | else 346 | return -1; 347 | } 348 | 349 | // Add sample 'id' to the mixers sounds to play list 350 | void SOUND::PlaySample(int id, bool bLoop) 351 | { 352 | olc::SOUND::sCurrentlyPlayingSample a; 353 | a.nAudioSampleID = id; 354 | a.nSamplePosition = 0; 355 | a.bFinished = false; 356 | a.bFlagForStop = false; 357 | a.bLoop = bLoop; 358 | SOUND::listActiveSamples.push_back(a); 359 | } 360 | 361 | void SOUND::StopSample(int id) 362 | { 363 | // Find first occurence of sample id 364 | auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); 365 | if (s != listActiveSamples.end()) 366 | s->bFlagForStop = true; 367 | } 368 | 369 | void SOUND::StopAll() 370 | { 371 | for (auto &s : listActiveSamples) 372 | { 373 | s.bFlagForStop = true; 374 | } 375 | } 376 | 377 | float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) 378 | { 379 | // Accumulate sample for this channel 380 | float fMixerSample = 0.0f; 381 | 382 | for (auto &s : listActiveSamples) 383 | { 384 | if (m_bAudioThreadActive) 385 | { 386 | if (s.bFlagForStop) 387 | { 388 | s.bLoop = false; 389 | s.bFinished = true; 390 | } 391 | else 392 | { 393 | // Calculate sample position 394 | s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); 395 | 396 | // If sample position is valid add to the mix 397 | if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) 398 | fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; 399 | else 400 | { 401 | if (s.bLoop) 402 | { 403 | s.nSamplePosition = 0; 404 | } 405 | else 406 | s.bFinished = true; // Else sound has completed 407 | } 408 | } 409 | } 410 | else 411 | return 0.0f; 412 | } 413 | 414 | // If sounds have completed then remove them 415 | listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); 416 | 417 | // The users application might be generating sound, so grab that if it exists 418 | if (funcUserSynth != nullptr) 419 | fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); 420 | 421 | // Return the sample via an optional user override to filter the sound 422 | if (funcUserFilter != nullptr) 423 | return funcUserFilter(nChannel, fGlobalTime, fMixerSample); 424 | else 425 | return fMixerSample; 426 | } 427 | 428 | std::thread SOUND::m_AudioThread; 429 | std::atomic SOUND::m_bAudioThreadActive{ false }; 430 | std::atomic SOUND::m_fGlobalTime{ 0.0f }; 431 | std::list SOUND::listActiveSamples; 432 | std::function SOUND::funcUserSynth = nullptr; 433 | std::function SOUND::funcUserFilter = nullptr; 434 | } 435 | 436 | // Implementation, Windows-specific 437 | #ifdef USE_WINDOWS 438 | #pragma comment(lib, "winmm.lib") 439 | 440 | namespace olc 441 | { 442 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) 443 | { 444 | // Initialise Sound Engine 445 | m_bAudioThreadActive = false; 446 | m_nSampleRate = nSampleRate; 447 | m_nChannels = nChannels; 448 | m_nBlockCount = nBlocks; 449 | m_nBlockSamples = nBlockSamples; 450 | m_nBlockFree = m_nBlockCount; 451 | m_nBlockCurrent = 0; 452 | m_pBlockMemory = nullptr; 453 | m_pWaveHeaders = nullptr; 454 | 455 | // Device is available 456 | WAVEFORMATEX waveFormat; 457 | waveFormat.wFormatTag = WAVE_FORMAT_PCM; 458 | waveFormat.nSamplesPerSec = m_nSampleRate; 459 | waveFormat.wBitsPerSample = sizeof(short) * 8; 460 | waveFormat.nChannels = m_nChannels; 461 | waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; 462 | waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; 463 | waveFormat.cbSize = 0; 464 | 465 | listActiveSamples.clear(); 466 | 467 | // Open Device if valid 468 | if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, CALLBACK_FUNCTION) != S_OK) 469 | return DestroyAudio(); 470 | 471 | // Allocate Wave|Block Memory 472 | m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; 473 | if (m_pBlockMemory == nullptr) 474 | return DestroyAudio(); 475 | ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); 476 | 477 | m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; 478 | if (m_pWaveHeaders == nullptr) 479 | return DestroyAudio(); 480 | ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); 481 | 482 | // Link headers to block memory 483 | for (unsigned int n = 0; n < m_nBlockCount; n++) 484 | { 485 | m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); 486 | m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); 487 | } 488 | 489 | m_bAudioThreadActive = true; 490 | m_AudioThread = std::thread(&SOUND::AudioThread); 491 | 492 | // Start the ball rolling with the sound delivery thread 493 | std::unique_lock lm(m_muxBlockNotZero); 494 | m_cvBlockNotZero.notify_one(); 495 | return true; 496 | } 497 | 498 | // Stop and clean up audio system 499 | bool SOUND::DestroyAudio() 500 | { 501 | m_bAudioThreadActive = false; 502 | if(m_AudioThread.joinable()) 503 | m_AudioThread.join(); 504 | return false; 505 | } 506 | 507 | // Handler for soundcard request for more data 508 | void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) 509 | { 510 | if (uMsg != WOM_DONE) return; 511 | m_nBlockFree++; 512 | std::unique_lock lm(m_muxBlockNotZero); 513 | m_cvBlockNotZero.notify_one(); 514 | } 515 | 516 | // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' 517 | // with audio data. If no requests are available it goes dormant until the sound 518 | // card is ready for more data. The block is fille by the "user" in some manner 519 | // and then issued to the soundcard. 520 | void SOUND::AudioThread() 521 | { 522 | m_fGlobalTime = 0.0f; 523 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 524 | 525 | // Goofy hack to get maximum integer for a type at run-time 526 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 527 | float fMaxSample = (float)nMaxSample; 528 | short nPreviousSample = 0; 529 | 530 | auto tp1 = std::chrono::system_clock::now(); 531 | auto tp2 = std::chrono::system_clock::now(); 532 | 533 | while (m_bAudioThreadActive) 534 | { 535 | // Wait for block to become available 536 | if (m_nBlockFree == 0) 537 | { 538 | std::unique_lock lm(m_muxBlockNotZero); 539 | while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly 540 | m_cvBlockNotZero.wait(lm); 541 | } 542 | 543 | // Block is here, so use it 544 | m_nBlockFree--; 545 | 546 | // Prepare block for processing 547 | if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) 548 | waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); 549 | 550 | short nNewSample = 0; 551 | int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; 552 | 553 | auto clip = [](float fSample, float fMax) 554 | { 555 | if (fSample >= 0.0) 556 | return fmin(fSample, fMax); 557 | else 558 | return fmax(fSample, -fMax); 559 | }; 560 | 561 | tp2 = std::chrono::system_clock::now(); 562 | std::chrono::duration elapsedTime = tp2 - tp1; 563 | tp1 = tp2; 564 | 565 | // Our time per frame coefficient 566 | float fElapsedTime = elapsedTime.count(); 567 | 568 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) 569 | { 570 | // User Process 571 | for (unsigned int c = 0; c < m_nChannels; c++) 572 | { 573 | nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample); 574 | m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; 575 | nPreviousSample = nNewSample; 576 | } 577 | } 578 | 579 | m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; 580 | 581 | // Send block to sound device 582 | waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); 583 | waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); 584 | m_nBlockCurrent++; 585 | m_nBlockCurrent %= m_nBlockCount; 586 | } 587 | } 588 | 589 | unsigned int SOUND::m_nSampleRate = 0; 590 | unsigned int SOUND::m_nChannels = 0; 591 | unsigned int SOUND::m_nBlockCount = 0; 592 | unsigned int SOUND::m_nBlockSamples = 0; 593 | unsigned int SOUND::m_nBlockCurrent = 0; 594 | short* SOUND::m_pBlockMemory = nullptr; 595 | WAVEHDR *SOUND::m_pWaveHeaders = nullptr; 596 | HWAVEOUT SOUND::m_hwDevice; 597 | std::atomic SOUND::m_nBlockFree = 0; 598 | std::condition_variable SOUND::m_cvBlockNotZero; 599 | std::mutex SOUND::m_muxBlockNotZero; 600 | } 601 | 602 | #elif defined(USE_ALSA) 603 | 604 | namespace olc 605 | { 606 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) 607 | { 608 | // Initialise Sound Engine 609 | m_bAudioThreadActive = false; 610 | m_nSampleRate = nSampleRate; 611 | m_nChannels = nChannels; 612 | m_nBlockSamples = nBlockSamples; 613 | m_pBlockMemory = nullptr; 614 | 615 | // Open PCM stream 616 | int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0); 617 | if (rc < 0) 618 | return DestroyAudio(); 619 | 620 | 621 | // Prepare the parameter structure and set default parameters 622 | snd_pcm_hw_params_t *params; 623 | snd_pcm_hw_params_alloca(¶ms); 624 | snd_pcm_hw_params_any(m_pPCM, params); 625 | 626 | // Set other parameters 627 | snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED); 628 | snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE); 629 | snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); 630 | snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); 631 | snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0); 632 | snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0); 633 | 634 | // Save these parameters 635 | rc = snd_pcm_hw_params(m_pPCM, params); 636 | if (rc < 0) 637 | return DestroyAudio(); 638 | 639 | listActiveSamples.clear(); 640 | 641 | // Allocate Wave|Block Memory 642 | m_pBlockMemory = new short[m_nBlockSamples]; 643 | if (m_pBlockMemory == nullptr) 644 | return DestroyAudio(); 645 | std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); 646 | 647 | // Unsure if really needed, helped prevent underrun on my setup 648 | snd_pcm_start(m_pPCM); 649 | for (unsigned int i = 0; i < nBlocks; i++) 650 | rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512); 651 | 652 | snd_pcm_start(m_pPCM); 653 | m_bAudioThreadActive = true; 654 | m_AudioThread = std::thread(&SOUND::AudioThread); 655 | 656 | return true; 657 | } 658 | 659 | // Stop and clean up audio system 660 | bool SOUND::DestroyAudio() 661 | { 662 | m_bAudioThreadActive = false; 663 | if(m_AudioThread.joinable()) 664 | m_AudioThread.join(); 665 | snd_pcm_drain(m_pPCM); 666 | snd_pcm_close(m_pPCM); 667 | return false; 668 | } 669 | 670 | 671 | // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' 672 | // with audio data. If no requests are available it goes dormant until the sound 673 | // card is ready for more data. The block is fille by the "user" in some manner 674 | // and then issued to the soundcard. 675 | void SOUND::AudioThread() 676 | { 677 | m_fGlobalTime = 0.0f; 678 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 679 | 680 | // Goofy hack to get maximum integer for a type at run-time 681 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 682 | float fMaxSample = (float)nMaxSample; 683 | short nPreviousSample = 0; 684 | 685 | while (m_bAudioThreadActive) 686 | { 687 | short nNewSample = 0; 688 | 689 | auto clip = [](float fSample, float fMax) 690 | { 691 | if (fSample >= 0.0) 692 | return fmin(fSample, fMax); 693 | else 694 | return fmax(fSample, -fMax); 695 | }; 696 | 697 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) 698 | { 699 | // User Process 700 | for (unsigned int c = 0; c < m_nChannels; c++) 701 | { 702 | nNewSample = (short)(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample; 703 | m_pBlockMemory[n + c] = nNewSample; 704 | nPreviousSample = nNewSample; 705 | } 706 | } 707 | 708 | m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; 709 | 710 | // Send block to sound device 711 | snd_pcm_uframes_t nLeft = m_nBlockSamples; 712 | short *pBlockPos = m_pBlockMemory; 713 | while (nLeft > 0) 714 | { 715 | int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); 716 | if (rc > 0) 717 | { 718 | pBlockPos += rc * m_nChannels; 719 | nLeft -= rc; 720 | } 721 | if (rc == -EAGAIN) continue; 722 | if (rc == -EPIPE) // an underrun occured, prepare the device for more data 723 | snd_pcm_prepare(m_pPCM); 724 | } 725 | } 726 | } 727 | 728 | snd_pcm_t* SOUND::m_pPCM = nullptr; 729 | unsigned int SOUND::m_nSampleRate = 0; 730 | unsigned int SOUND::m_nChannels = 0; 731 | unsigned int SOUND::m_nBlockSamples = 0; 732 | short* SOUND::m_pBlockMemory = nullptr; 733 | } 734 | 735 | #elif defined(USE_OPENAL) 736 | 737 | namespace olc 738 | { 739 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) 740 | { 741 | // Initialise Sound Engine 742 | m_bAudioThreadActive = false; 743 | m_nSampleRate = nSampleRate; 744 | m_nChannels = nChannels; 745 | m_nBlockCount = nBlocks; 746 | m_nBlockSamples = nBlockSamples; 747 | m_pBlockMemory = nullptr; 748 | 749 | // Open the device and create the context 750 | m_pDevice = alcOpenDevice(NULL); 751 | if (m_pDevice) 752 | { 753 | m_pContext = alcCreateContext(m_pDevice, NULL); 754 | alcMakeContextCurrent(m_pContext); 755 | } 756 | else 757 | return DestroyAudio(); 758 | 759 | // Allocate memory for sound data 760 | alGetError(); 761 | m_pBuffers = new ALuint[m_nBlockCount]; 762 | alGenBuffers(m_nBlockCount, m_pBuffers); 763 | alGenSources(1, &m_nSource); 764 | 765 | for (unsigned int i = 0; i < m_nBlockCount; i++) 766 | m_qAvailableBuffers.push(m_pBuffers[i]); 767 | 768 | listActiveSamples.clear(); 769 | 770 | // Allocate Wave|Block Memory 771 | m_pBlockMemory = new short[m_nBlockSamples]; 772 | if (m_pBlockMemory == nullptr) 773 | return DestroyAudio(); 774 | std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); 775 | 776 | m_bAudioThreadActive = true; 777 | m_AudioThread = std::thread(&SOUND::AudioThread); 778 | return true; 779 | } 780 | 781 | // Stop and clean up audio system 782 | bool SOUND::DestroyAudio() 783 | { 784 | m_bAudioThreadActive = false; 785 | if(m_AudioThread.joinable()) 786 | m_AudioThread.join(); 787 | 788 | alDeleteBuffers(m_nBlockCount, m_pBuffers); 789 | delete[] m_pBuffers; 790 | alDeleteSources(1, &m_nSource); 791 | 792 | alcMakeContextCurrent(NULL); 793 | alcDestroyContext(m_pContext); 794 | alcCloseDevice(m_pDevice); 795 | return false; 796 | } 797 | 798 | 799 | // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' 800 | // with audio data. If no requests are available it goes dormant until the sound 801 | // card is ready for more data. The block is fille by the "user" in some manner 802 | // and then issued to the soundcard. 803 | void SOUND::AudioThread() 804 | { 805 | m_fGlobalTime = 0.0f; 806 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 807 | 808 | // Goofy hack to get maximum integer for a type at run-time 809 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 810 | float fMaxSample = (float)nMaxSample; 811 | short nPreviousSample = 0; 812 | 813 | std::vector vProcessed; 814 | 815 | while (m_bAudioThreadActive) 816 | { 817 | ALint nState, nProcessed; 818 | alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState); 819 | alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed); 820 | 821 | // Add processed buffers to our queue 822 | vProcessed.resize(nProcessed); 823 | alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data()); 824 | for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf); 825 | 826 | // Wait until there is a free buffer (ewww) 827 | if (m_qAvailableBuffers.empty()) continue; 828 | 829 | short nNewSample = 0; 830 | 831 | auto clip = [](float fSample, float fMax) 832 | { 833 | if (fSample >= 0.0) 834 | return fmin(fSample, fMax); 835 | else 836 | return fmax(fSample, -fMax); 837 | }; 838 | 839 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) 840 | { 841 | // User Process 842 | for (unsigned int c = 0; c < m_nChannels; c++) 843 | { 844 | nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); 845 | m_pBlockMemory[n + c] = nNewSample; 846 | nPreviousSample = nNewSample; 847 | } 848 | 849 | m_fGlobalTime = m_fGlobalTime + fTimeStep; 850 | } 851 | 852 | // Fill OpenAL data buffer 853 | alBufferData( 854 | m_qAvailableBuffers.front(), 855 | m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, 856 | m_pBlockMemory, 857 | 2 * m_nBlockSamples, 858 | m_nSampleRate 859 | ); 860 | // Add it to the OpenAL queue 861 | alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front()); 862 | // Remove it from ours 863 | m_qAvailableBuffers.pop(); 864 | 865 | // If it's not playing for some reason, change that 866 | if (nState != AL_PLAYING) 867 | alSourcePlay(m_nSource); 868 | } 869 | } 870 | 871 | std::queue SOUND::m_qAvailableBuffers; 872 | ALuint *SOUND::m_pBuffers = nullptr; 873 | ALuint SOUND::m_nSource = 0; 874 | ALCdevice *SOUND::m_pDevice = nullptr; 875 | ALCcontext *SOUND::m_pContext = nullptr; 876 | unsigned int SOUND::m_nSampleRate = 0; 877 | unsigned int SOUND::m_nChannels = 0; 878 | unsigned int SOUND::m_nBlockCount = 0; 879 | unsigned int SOUND::m_nBlockSamples = 0; 880 | short* SOUND::m_pBlockMemory = nullptr; 881 | } 882 | 883 | #else // Some other platform 884 | 885 | namespace olc 886 | { 887 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) 888 | { 889 | return true; 890 | } 891 | 892 | // Stop and clean up audio system 893 | bool SOUND::DestroyAudio() 894 | { 895 | return false; 896 | } 897 | 898 | 899 | // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' 900 | // with audio data. If no requests are available it goes dormant until the sound 901 | // card is ready for more data. The block is fille by the "user" in some manner 902 | // and then issued to the soundcard. 903 | void SOUND::AudioThread() 904 | { } 905 | } 906 | 907 | #endif 908 | #endif 909 | #endif // OLC_PGEX_SOUND -------------------------------------------------------------------------------- /extensions/olcPGEX_SplashScreen.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_SplashScreen.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Splash Screen v1.0 | 7 | +-------------------------------------------------------------+ 8 | 9 | NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES 10 | 11 | What is this? 12 | ~~~~~~~~~~~~~ 13 | This extension produces an animated splashscreen and copyright notice 14 | at the beginning of a PGE application, for the purposes of remaining 15 | OLC-3 compliant should it be ambiguous in your deployment. 16 | 17 | License (OLC-3) 18 | ~~~~~~~~~~~~~~~ 19 | 20 | Copyright 2018 - 2024 OneLoneCoder.com 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions 24 | are met: 25 | 26 | 1. Redistributions or derivations of source code must retain the above 27 | copyright notice, this list of conditions and the following disclaimer. 28 | 29 | 2. Redistributions or derivative works in binary form must reproduce 30 | the above copyright notice. This list of conditions and the following 31 | disclaimer must be reproduced in the documentation and/or other 32 | materials provided with the distribution. 33 | 34 | 3. Neither the name of the copyright holder nor the names of its 35 | contributors may be used to endorse or promote products derived 36 | from this software without specific prior written permission. 37 | 38 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 39 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 40 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 41 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 42 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 43 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 44 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 45 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 46 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 47 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 48 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 49 | 50 | Links 51 | ~~~~~ 52 | YouTube: https://www.youtube.com/javidx9 53 | Discord: https://discord.gg/WhwHUMV 54 | Twitter: https://www.twitter.com/javidx9 55 | Twitch: https://www.twitch.tv/javidx9 56 | GitHub: https://www.github.com/onelonecoder 57 | Homepage: https://www.onelonecoder.com 58 | 59 | Author 60 | ~~~~~~ 61 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024 62 | 63 | Revisions: 64 | 1.00: Initial Release 65 | */ 66 | 67 | #pragma once 68 | 69 | #include "olcPixelGameEngine.h" 70 | 71 | namespace olc 72 | { 73 | class SplashScreen : public olc::PGEX 74 | { 75 | public: 76 | SplashScreen(); 77 | 78 | protected: 79 | virtual void OnAfterUserCreate() override; 80 | virtual bool OnBeforeUserUpdate(float& fElapsedTime) override; 81 | 82 | private: 83 | olc::Renderable spr; 84 | std::vector> vBoom; 85 | olc::vf2d vScale; 86 | olc::vf2d vPosition; 87 | float fParticleTime = 0.0f; 88 | float fAspect = 0.0f; 89 | bool bComplete = false; 90 | }; 91 | 92 | 93 | } 94 | 95 | #ifdef OLC_PGEX_SPLASHSCREEN 96 | #undef OLC_PGEX_SPLASHSCREEN 97 | 98 | namespace olc 99 | { 100 | SplashScreen::SplashScreen() : olc::PGEX(true) 101 | { 102 | } 103 | 104 | void SplashScreen::OnAfterUserCreate() 105 | { 106 | const char logo[] = 107 | "000000000000000000000000000000000000000000000000000000000000000000005" 108 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED1EE" 109 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED5EEE" 110 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE@E@000" 111 | "0000000000000000000000000000000000000000000000000000000000001E1D:ZZZZ" 112 | "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1D5BZZZZZZ" 113 | "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ5@E:P0002Z0" 114 | "02ZZX000000000000ZP0000000000000000000000000000ZX000Z002XE1DX?o`o:Poo" 115 | "800SooaE5@E1ED5BX?ol5E@E0E1ED?oo5@E1ED5DE1D5E@ZQEEBPEE2QD5BSooclZ?olQ" 116 | "AB?oo5DEEDEEDE:SooaEEAE5DEEDoolEADEEDEAE5AEEBZ5EE:5EE:5@E:?oo?bXoob55" 117 | "8o3lEAEEAD5ADZ?oo5@E5EEAD5Cl01E5AD5AE5DE5@E:X01DXEEDXE1DXo3lo:Sl0800S" 118 | "ooaE1ED5EE5BXo00EEDEEE5EE?oo5EE5EE5DEEDEEDZQEEBQD5BQD5BSl?cl0?`0ZZZ?o" 119 | "o5D5E@EEDE03loaEEAEEDEEDoolEED5EDEAEEAEEBZ5EE:5@E:5@E:?oo?oloob008o00" 120 | "EAEEAD01EE?co5EE5EEAD03l01DE@05AE5AE5@0:XE000EEDXE1DXooloocoo8DDSlZQE" 121 | "5EE5EE5EDoolE1DE4E5EE?oo5AE5EE5DE5DEEDZQEEAAEEBQD5BPoo3oo3olQAB?bZ5DE" 122 | "1D5EDEE@ooaD5AD1D5EDoolE1DEE@EAD5@EEBZ5EE51ED:5@E:P000000020080:X0000" 123 | "00000000000000000000000000000000000:X0000002XE1DZZZZZZZZZZZZZZZZZZZZZ" 124 | "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZQD5@ZZZZZZZZZZZZZZZZZZZZZZ" 125 | "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZX5@E@00000000000000000000000" 126 | "00000000000000000000000000000000000000001E1EEEEEEEEEEEEEEEEEEEEEEEEEE" 127 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED5EEEEEEEEEEEEEEEEEEEEEEEEEEE" 128 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE@5EEEEEEEEEEEEEEEEEEEEEEEEEEEE" 129 | "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED0000000000000000000000000000000" 130 | "0000000000000000000000000000000000000"; 131 | 132 | spr.Create(203, 24); 133 | int px = 0, py = 0; 134 | for (size_t b = 0; b < 1624; b += 4) 135 | { 136 | uint32_t sym1 = (uint32_t)logo[b + 0] - 48; 137 | uint32_t sym2 = (uint32_t)logo[b + 1] - 48; 138 | uint32_t sym3 = (uint32_t)logo[b + 2] - 48; 139 | uint32_t sym4 = (uint32_t)logo[b + 3] - 48; 140 | uint32_t r = sym1 << 18 | sym2 << 12 | sym3 << 6 | sym4; 141 | 142 | for (int i = 0; i < 12; i++) 143 | { 144 | olc::Pixel p = olc::RED; 145 | switch ((r & 0xC00000) >> 22) 146 | { 147 | case 0: p = olc::Pixel(0, 0, 0, 255); break; 148 | case 1: p = olc::Pixel(255, 255, 255, 255); break; 149 | case 2: p = olc::Pixel(255, 120, 26, 255); break; 150 | case 3: p = olc::Pixel(79, 193, 255, 255); break; 151 | } 152 | spr.Sprite()->SetPixel(px, py, p); 153 | if (++px == 203) { py++; px = 0; } 154 | r <<= 2; 155 | } 156 | } 157 | 158 | spr.Decal()->Update(); 159 | vBoom.resize(spr.Sprite()->width * spr.Sprite()->height); 160 | vScale = { float(pge->ScreenWidth()) / 500.0f, float(pge->ScreenWidth()) / 500.0f }; 161 | fAspect = float(pge->ScreenWidth()) / float(pge->ScreenHeight()); 162 | vPosition = olc::vf2d( 163 | (250 - spr.Sprite()->width) / 2.0f, 164 | (250 - spr.Sprite()->height) / 2.0f / fAspect); 165 | for (int y = 0; y < spr.Sprite()->height; y++) 166 | for (int x = 0; x < spr.Sprite()->width; x++) 167 | vBoom[y * spr.Sprite()->width + x] = std::make_pair( 168 | vPosition + olc::vf2d(float(x), float(y)), 169 | olc::vf2d( 170 | (float(rand()) / float(RAND_MAX)) * 10.0f - 5.0f, 171 | (float(rand()) / float(RAND_MAX)) * 10.0f - 5.0f) 172 | ); 173 | } 174 | 175 | bool SplashScreen::OnBeforeUserUpdate(float& fElapsedTime) 176 | { 177 | if (bComplete) return false; 178 | 179 | fParticleTime += fElapsedTime; 180 | 181 | for (int y = 0; y < spr.Sprite()->height; y++) 182 | for (int x = 0; x < spr.Sprite()->width; x++) 183 | { 184 | 185 | 186 | if (fParticleTime < 1.0f) 187 | { 188 | 189 | } 190 | else if (fParticleTime < 2.0f) 191 | { 192 | vBoom[y * spr.Sprite()->width + x].first = 193 | olc::vf2d( 194 | (250 - spr.Sprite()->width) / 2.0f + float(x), 195 | (250 - spr.Sprite()->height) / 2.0f / fAspect + float(y) 196 | ) + 197 | olc::vf2d( 198 | (float(rand()) / float(RAND_MAX)) * 0.5f - 0.25f, 199 | (float(rand()) / float(RAND_MAX)) * 0.5f - 0.25f); 200 | } 201 | else if(fParticleTime < 5.0f) 202 | { 203 | vBoom[y * spr.Sprite()->width + x].first += vBoom[y * spr.Sprite()->width + x].second * fElapsedTime * 20.0f; 204 | } 205 | else 206 | { 207 | bComplete = true; 208 | } 209 | 210 | pge->DrawPartialDecal(vScale * vBoom[y * spr.Sprite()->width + x].first * 2.0f, spr.Decal(), olc::vf2d(float(x), float(y)), { 1, 1 }, vScale * 2.0f, olc::PixelF(1.0f, 1.0f, 1.0f, std::min(1.0f, std::max(4.0f - fParticleTime, 0.0f)))); 211 | } 212 | 213 | olc::vi2d vSize = pge->GetTextSizeProp("Copyright OneLoneCoder.com 2024"); 214 | pge->DrawStringPropDecal(olc::vf2d(float(pge->ScreenWidth()/2) - vSize.x/2, float(pge->ScreenHeight()) - vSize.y * 3.0f), "Copyright OneLoneCoder.com 2024", olc::PixelF(1.0f, 1.0f, 1.0f, 0.5f), olc::vf2d(1.0, 2.0f)); 215 | return true; 216 | } 217 | 218 | } 219 | 220 | #endif -------------------------------------------------------------------------------- /extensions/olcPGEX_TransformedView.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_TransformedView.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Transformed View v1.10 | 7 | +-------------------------------------------------------------+ 8 | 9 | NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES 10 | 11 | What is this? 12 | ~~~~~~~~~~~~~ 13 | This extension provides drawing routines that are compatible with 14 | changeable world and screen spaces. For example you can pan and 15 | zoom, and all PGE drawing routines will automatically adopt the current 16 | world scales and offsets. 17 | 18 | License (OLC-3) 19 | ~~~~~~~~~~~~~~~ 20 | 21 | Copyright 2018 - 2025 OneLoneCoder.com 22 | 23 | Redistribution and use in source and binary forms, with or without 24 | modification, are permitted provided that the following conditions 25 | are met: 26 | 27 | 1. Redistributions or derivations of source code must retain the above 28 | copyright notice, this list of conditions and the following disclaimer. 29 | 30 | 2. Redistributions or derivative works in binary form must reproduce 31 | the above copyright notice. This list of conditions and the following 32 | disclaimer must be reproduced in the documentation and/or other 33 | materials provided with the distribution. 34 | 35 | 3. Neither the name of the copyright holder nor the names of its 36 | contributors may be used to endorse or promote products derived 37 | from this software without specific prior written permission. 38 | 39 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 40 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 41 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 42 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 43 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 46 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 47 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 48 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | 51 | Links 52 | ~~~~~ 53 | YouTube: https://www.youtube.com/javidx9 54 | Discord: https://discord.gg/WhwHUMV 55 | Twitter: https://www.twitter.com/javidx9 56 | Twitch: https://www.twitch.tv/javidx9 57 | GitHub: https://www.github.com/onelonecoder 58 | Homepage: https://www.onelonecoder.com 59 | 60 | Author 61 | ~~~~~~ 62 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024, 2025 63 | 64 | Revisions: 65 | 1.00: Initial Release 66 | 1.01: Fix for rounding error when scaling to screen 67 | 1.02: Added DrawLineDecal for convenience 68 | 1.03: Removed std::floor from WorldToScreen() 69 | Added HandlePanAndZoom(...) convenience function 70 | Removed unused "range" facility in TileTransformView 71 | 1.04: Added DrawPolygonDecal() for arbitrary polygons 72 | 1.05: Clipped DrawSprite() to visible area, massive performance increase 73 | 1.06: Fixed error in DrawLine() - Thanks CraisyDaisyRecords (& Fern)! 74 | 1.07: +DrawRectDecal() 75 | +GetPGE() 76 | 1.08: +DrawPolygonDecal() with tint overload, akin to PGE 77 | 1.09: +SetScaleExtents() - Sets range that world scale can exist within 78 | +EnableScaleClamp() - Applies a range that scaling is clamped to 79 | These are both useful for having zoom clamped between a min and max 80 | without weird panning artefacts occuring 81 | 1.10: Hitched in some "shader" PGEX things 82 | */ 83 | 84 | #pragma once 85 | #ifndef OLC_PGEX_TRANSFORMEDVIEW_H 86 | #define OLC_PGEX_TRANSFORMEDVIEW_H 87 | 88 | #include "olcPixelGameEngine.h" 89 | 90 | 91 | 92 | namespace olc 93 | { 94 | class TransformedView : public olc::PGEX 95 | { 96 | public: 97 | TransformedView() = default; 98 | virtual void Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale = { 1.0f, 1.0f }); 99 | 100 | olc::PixelGameEngine* GetPGE(); 101 | 102 | public: 103 | void SetWorldOffset(const olc::vf2d& vOffset); 104 | void MoveWorldOffset(const olc::vf2d& vDeltaOffset); 105 | void SetWorldScale(const olc::vf2d& vScale); 106 | void SetViewArea(const olc::vi2d& vViewArea); 107 | olc::vf2d GetWorldTL() const; 108 | olc::vf2d GetWorldBR() const; 109 | olc::vf2d GetWorldVisibleArea() const; 110 | void ZoomAtScreenPos(const float fDeltaZoom, const olc::vi2d& vPos); 111 | void SetZoom(const float fZoom, const olc::vf2d& vPos); 112 | void StartPan(const olc::vi2d& vPos); 113 | void UpdatePan(const olc::vi2d& vPos); 114 | void EndPan(const olc::vi2d& vPos); 115 | const olc::vf2d& GetWorldOffset() const; 116 | const olc::vf2d& GetWorldScale() const; 117 | virtual olc::vf2d WorldToScreen(const olc::vf2d& vWorldPos) const; 118 | virtual olc::vf2d ScreenToWorld(const olc::vf2d& vScreenPos) const; 119 | virtual olc::vf2d ScaleToWorld(const olc::vf2d& vScreenSize) const; 120 | virtual olc::vf2d ScaleToScreen(const olc::vf2d& vWorldSize) const; 121 | virtual bool IsPointVisible(const olc::vf2d& vPos) const; 122 | virtual bool IsRectVisible(const olc::vf2d& vPos, const olc::vf2d& vSize) const; 123 | virtual void HandlePanAndZoom(const int nMouseButton = 2, const float fZoomRate = 0.1f, const bool bPan = true, const bool bZoom = true); 124 | void SetScaleExtents(const olc::vf2d& vScaleMin, const olc::vf2d& vScaleMax); 125 | void EnableScaleClamp(const bool bEnable); 126 | 127 | protected: 128 | olc::vf2d m_vWorldOffset = { 0.0f, 0.0f }; 129 | olc::vf2d m_vWorldScale = { 1.0f, 1.0f }; 130 | olc::vf2d m_vRecipPixel = { 1.0f, 1.0f }; 131 | olc::vf2d m_vPixelScale = { 1.0f, 1.0f }; 132 | bool m_bPanning = false; 133 | olc::vf2d m_vStartPan = { 0.0f, 0.0f }; 134 | olc::vi2d m_vViewArea; 135 | bool m_bZoomClamp = false; 136 | olc::vf2d m_vMaxScale = { 0.0f, 0.0f }; 137 | olc::vf2d m_vMinScale = { 0.0f, 0.0f }; 138 | 139 | public: // Hopefully, these should look familiar! 140 | // Plots a single point 141 | virtual bool Draw(float x, float y, olc::Pixel p = olc::WHITE); 142 | bool Draw(const olc::vf2d& pos, olc::Pixel p = olc::WHITE); 143 | // Draws a line from (x1,y1) to (x2,y2) 144 | void DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); 145 | void DrawLine(const olc::vf2d& pos1, const olc::vf2d& pos2, olc::Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); 146 | // Draws a circle located at (x,y) with radius 147 | void DrawCircle(float x, float y, float radius, olc::Pixel p = olc::WHITE, uint8_t mask = 0xFF); 148 | void DrawCircle(const olc::vf2d& pos, float radius, olc::Pixel p = olc::WHITE, uint8_t mask = 0xFF); 149 | // Fills a circle located at (x,y) with radius 150 | void FillCircle(float x, float y, float radius, olc::Pixel p = olc::WHITE); 151 | void FillCircle(const olc::vf2d& pos, float radius, olc::Pixel p = olc::WHITE); 152 | // Draws a rectangle at (x,y) to (x+w,y+h) 153 | void DrawRect(float x, float y, float w, float h, olc::Pixel p = olc::WHITE); 154 | void DrawRect(const olc::vf2d& pos, const olc::vf2d& size, olc::Pixel p = olc::WHITE); 155 | // Fills a rectangle at (x,y) to (x+w,y+h) 156 | void FillRect(float x, float y, float w, float h, olc::Pixel p = olc::WHITE); 157 | void FillRect(const olc::vf2d& pos, const olc::vf2d& size, olc::Pixel p = olc::WHITE); 158 | // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) 159 | void DrawTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p = olc::WHITE); 160 | void DrawTriangle(const olc::vf2d& pos1, const olc::vf2d& pos2, const olc::vf2d& pos3, olc::Pixel p = olc::WHITE); 161 | // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) 162 | void FillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p = olc::WHITE); 163 | void FillTriangle(const olc::vf2d& pos1, const olc::vf2d& pos2, const olc::vf2d& pos3, olc::Pixel p = olc::WHITE); 164 | // Draws an entire sprite at location (x,y) 165 | void DrawSprite(float x, float y, olc::Sprite* sprite, float scalex = 1, float scaley = 1, uint8_t flip = olc::Sprite::NONE); 166 | void DrawSprite(const olc::vf2d& pos, olc::Sprite* sprite, const olc::vf2d& scale = { 1.0f, 1.0f }, uint8_t flip = olc::Sprite::NONE); 167 | // Draws an area of a sprite at location (x,y), where the 168 | // selected area is (ox,oy) to (ox+w,oy+h) 169 | void DrawPartialSprite(float x, float y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, float scalex = 1, float scaley = 1, uint8_t flip = olc::Sprite::NONE); 170 | void DrawPartialSprite(const olc::vf2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, const olc::vf2d& scale = { 1.0f, 1.0f }, uint8_t flip = olc::Sprite::NONE); 171 | void DrawString(float x, float y, const std::string& sText, Pixel col, const olc::vf2d& scale); 172 | void DrawString(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale); 173 | 174 | 175 | // Draws a whole decal, with optional scale and tinting 176 | void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); 177 | // Draws a region of a decal, with optional scale and tinting 178 | void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); 179 | void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); 180 | // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours 181 | void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); 182 | //// Draws a decal with 4 arbitrary points, warping the texture to look "correct" 183 | void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); 184 | void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); 185 | void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); 186 | //// As above, but you can specify a region of a decal source sprite 187 | void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); 188 | void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); 189 | void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); 190 | //// Draws a decal rotated to specified angle, wit point of rotation offset 191 | void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); 192 | void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); 193 | // Draws a multiline string as a decal, with tiniting and scaling 194 | void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); 195 | void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); 196 | // Draws a single shaded filled rectangle as a decal 197 | void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); 198 | void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); 199 | 200 | // Draws a corner shaded rectangle as a decal 201 | void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); 202 | // Draws an arbitrary convex textured polygon using GPU 203 | void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); 204 | void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); 205 | void DrawPolygonDecal(olc::Decal* decal, const std::vector&pos, const std::vector&uv, const std::vector &tint); 206 | void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint); 207 | 208 | 209 | #if defined(OLC_USING_PGEX_SHADER) 210 | // Shader Specific 211 | void DrawDecal(olc::Shade& shader, const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & scale = { 1.0f,1.0f }, const olc::Pixel & tint = olc::WHITE); 212 | void DrawPartialDecal(olc::Shade& shader, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); 213 | void DrawPartialDecal(olc::Shade& shader, const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); 214 | #endif 215 | 216 | 217 | 218 | }; 219 | 220 | class TileTransformedView : public TransformedView 221 | { 222 | public: 223 | TileTransformedView() = default; 224 | TileTransformedView(const olc::vi2d& vViewArea, const olc::vi2d& vTileSize); 225 | 226 | public: 227 | olc::vi2d GetTopLeftTile() const; 228 | olc::vi2d GetBottomRightTile() const; 229 | olc::vi2d GetVisibleTiles() const; 230 | olc::vi2d GetTileUnderScreenPos(const olc::vi2d& vPos) const; 231 | const olc::vi2d GetTileOffset() const; 232 | 233 | }; 234 | } 235 | 236 | #ifdef OLC_PGEX_TRANSFORMEDVIEW 237 | #undef OLC_PGEX_TRANSFORMEDVIEW 238 | 239 | namespace olc 240 | { 241 | olc::PixelGameEngine* TransformedView::GetPGE() 242 | { 243 | return pge; 244 | } 245 | 246 | void TransformedView::Initialise(const olc::vi2d& vViewArea, const olc::vf2d& vPixelScale) 247 | { 248 | SetViewArea(vViewArea); 249 | SetWorldScale(vPixelScale); 250 | m_vPixelScale = vPixelScale; 251 | m_vRecipPixel = 1.0f / m_vPixelScale; 252 | } 253 | 254 | void TransformedView::SetWorldOffset(const olc::vf2d& vOffset) 255 | { 256 | m_vWorldOffset = vOffset; 257 | } 258 | 259 | void TransformedView::MoveWorldOffset(const olc::vf2d& vDeltaOffset) 260 | { 261 | m_vWorldOffset += vDeltaOffset; 262 | } 263 | 264 | void TransformedView::SetWorldScale(const olc::vf2d& vScale) 265 | { 266 | m_vWorldScale = vScale; 267 | if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); 268 | } 269 | 270 | void TransformedView::SetViewArea(const olc::vi2d& vViewArea) 271 | { 272 | m_vViewArea = vViewArea; 273 | } 274 | 275 | olc::vf2d TransformedView::GetWorldTL() const 276 | { 277 | return TransformedView::ScreenToWorld({ 0,0 }); 278 | } 279 | 280 | olc::vf2d TransformedView::GetWorldBR() const 281 | { 282 | return TransformedView::ScreenToWorld(m_vViewArea); 283 | } 284 | 285 | olc::vf2d TransformedView::GetWorldVisibleArea() const 286 | { 287 | return GetWorldBR() - GetWorldTL(); 288 | } 289 | 290 | void TransformedView::SetScaleExtents(const olc::vf2d& vScaleMin, const olc::vf2d& vScaleMax) 291 | { 292 | m_vMaxScale = vScaleMax; 293 | m_vMinScale = vScaleMin; 294 | } 295 | 296 | void TransformedView::EnableScaleClamp(const bool bEnable) 297 | { 298 | m_bZoomClamp = bEnable; 299 | } 300 | 301 | void TransformedView::ZoomAtScreenPos(const float fDeltaZoom, const olc::vi2d& vPos) 302 | { 303 | olc::vf2d vOffsetBeforeZoom = ScreenToWorld(vPos); 304 | m_vWorldScale *= fDeltaZoom; 305 | if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); 306 | olc::vf2d vOffsetAfterZoom = ScreenToWorld(vPos); 307 | m_vWorldOffset += vOffsetBeforeZoom - vOffsetAfterZoom; 308 | } 309 | 310 | void TransformedView::SetZoom(const float fZoom, const olc::vf2d& vPos) 311 | { 312 | olc::vf2d vOffsetBeforeZoom = ScreenToWorld(vPos); 313 | m_vWorldScale = { fZoom, fZoom }; 314 | if (m_bZoomClamp) m_vWorldScale = m_vWorldScale.clamp(m_vMinScale, m_vMaxScale); 315 | olc::vf2d vOffsetAfterZoom = ScreenToWorld(vPos); 316 | m_vWorldOffset += vOffsetBeforeZoom - vOffsetAfterZoom; 317 | } 318 | 319 | void TransformedView::StartPan(const olc::vi2d& vPos) 320 | { 321 | m_bPanning = true; 322 | m_vStartPan = olc::vf2d(vPos); 323 | } 324 | 325 | void TransformedView::UpdatePan(const olc::vi2d& vPos) 326 | { 327 | if (m_bPanning) 328 | { 329 | m_vWorldOffset -= (olc::vf2d(vPos) - m_vStartPan) / m_vWorldScale; 330 | m_vStartPan = olc::vf2d(vPos); 331 | } 332 | } 333 | 334 | void TransformedView::EndPan(const olc::vi2d& vPos) 335 | { 336 | UpdatePan(vPos); 337 | m_bPanning = false; 338 | } 339 | 340 | const olc::vf2d& TransformedView::GetWorldOffset() const 341 | { 342 | return m_vWorldOffset; 343 | } 344 | 345 | const olc::vf2d& TransformedView::GetWorldScale() const 346 | { 347 | return m_vWorldScale; 348 | } 349 | 350 | olc::vf2d TransformedView::WorldToScreen(const olc::vf2d& vWorldPos) const 351 | { 352 | olc::vf2d vFloat = ((vWorldPos - m_vWorldOffset) * m_vWorldScale); 353 | //vFloat = { std::floor(vFloat.x + 0.5f), std::floor(vFloat.y + 0.5f) }; 354 | return vFloat; 355 | } 356 | 357 | olc::vf2d TransformedView::ScreenToWorld(const olc::vf2d& vScreenPos) const 358 | { 359 | return (olc::vf2d(vScreenPos) / m_vWorldScale) + m_vWorldOffset; 360 | } 361 | 362 | olc::vf2d TransformedView::ScaleToWorld(const olc::vf2d& vScreenSize) const 363 | { 364 | return (olc::vf2d(vScreenSize) / m_vWorldScale); 365 | } 366 | 367 | olc::vf2d TransformedView::ScaleToScreen(const olc::vf2d& vWorldSize) const 368 | { 369 | //olc::vf2d vFloat = (vWorldSize * m_vWorldScale) + olc::vf2d(0.5f, 0.5f); 370 | //return vFloat.floor(); 371 | return (vWorldSize * m_vWorldScale); 372 | } 373 | 374 | bool TransformedView::IsPointVisible(const olc::vf2d & vPos) const 375 | { 376 | olc::vi2d vScreen = WorldToScreen(vPos); 377 | return vScreen.x >= 0 && vScreen.x < m_vViewArea.x&& vScreen.y >= 0 && vScreen.y < m_vViewArea.y; 378 | } 379 | 380 | bool TransformedView::IsRectVisible(const olc::vf2d& vPos, const olc::vf2d& vSize) const 381 | { 382 | olc::vi2d vScreenPos = WorldToScreen(vPos); 383 | olc::vi2d vScreenSize = vSize * m_vWorldScale; 384 | return (vScreenPos.x < 0 + m_vViewArea.x && vScreenPos.x + vScreenSize.x > 0 && vScreenPos.y < m_vViewArea.y&& vScreenPos.y + vScreenSize.y > 0); 385 | } 386 | 387 | void TransformedView::HandlePanAndZoom(const int nMouseButton, const float fZoomRate, const bool bPan, const bool bZoom) 388 | { 389 | const auto& vMousePos = pge->GetMousePos(); 390 | if (bPan) 391 | { 392 | if (pge->GetMouse(nMouseButton).bPressed) StartPan(vMousePos); 393 | if (pge->GetMouse(nMouseButton).bHeld) UpdatePan(vMousePos); 394 | if (pge->GetMouse(nMouseButton).bReleased) EndPan(vMousePos); 395 | } 396 | 397 | if (bZoom) 398 | { 399 | if (pge->GetMouseWheel() > 0) ZoomAtScreenPos(1.0f + fZoomRate, vMousePos); 400 | if (pge->GetMouseWheel() < 0) ZoomAtScreenPos(1.0f - fZoomRate, vMousePos); 401 | } 402 | } 403 | 404 | bool TransformedView::Draw(float x, float y, olc::Pixel p) 405 | { 406 | return Draw({ x, y }, p); 407 | } 408 | 409 | bool TransformedView::Draw(const olc::vf2d & pos, olc::Pixel p) 410 | { 411 | return pge->Draw(WorldToScreen(pos), p); 412 | } 413 | 414 | void TransformedView::DrawLine(float x1, float y1, float x2, float y2, olc::Pixel p, uint32_t pattern) 415 | { 416 | DrawLine({ x1, y1 }, { x2, y2 }, p, pattern); 417 | } 418 | 419 | void TransformedView::DrawLine(const olc::vf2d & pos1, const olc::vf2d & pos2, olc::Pixel p, uint32_t pattern) 420 | { 421 | pge->DrawLine(WorldToScreen(pos1), WorldToScreen(pos2), p, pattern); 422 | } 423 | 424 | void TransformedView::DrawCircle(float x, float y, float radius, olc::Pixel p, uint8_t mask) 425 | { 426 | DrawCircle({ x,y }, radius, p, mask); 427 | } 428 | 429 | void TransformedView::DrawCircle(const olc::vf2d & pos, float radius, olc::Pixel p, uint8_t mask) 430 | { 431 | pge->DrawCircle(WorldToScreen(pos), int32_t(radius * m_vWorldScale.x), p, mask); 432 | } 433 | 434 | void TransformedView::FillCircle(float x, float y, float radius, olc::Pixel p) 435 | { 436 | FillCircle({ x,y }, radius, p); 437 | } 438 | 439 | void TransformedView::FillCircle(const olc::vf2d & pos, float radius, olc::Pixel p) 440 | { 441 | pge->FillCircle(WorldToScreen(pos), int32_t(radius * m_vWorldScale.x), p); 442 | } 443 | 444 | void TransformedView::DrawRect(float x, float y, float w, float h, olc::Pixel p) 445 | { 446 | DrawRect({ x, y }, { w, h }, p); 447 | } 448 | 449 | void TransformedView::DrawRect(const olc::vf2d & pos, const olc::vf2d & size, olc::Pixel p) 450 | { 451 | pge->DrawRect(WorldToScreen(pos), ((size * m_vWorldScale) + olc::vf2d(0.5f, 0.5f)).floor(), p); 452 | } 453 | 454 | void TransformedView::FillRect(float x, float y, float w, float h, olc::Pixel p) 455 | { 456 | FillRect({ x, y }, { w, h }, p); 457 | } 458 | 459 | void TransformedView::FillRect(const olc::vf2d & pos, const olc::vf2d & size, olc::Pixel p) 460 | { 461 | pge->FillRect(WorldToScreen(pos), size * m_vWorldScale, p); 462 | } 463 | 464 | void TransformedView::DrawTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p) 465 | { 466 | DrawTriangle({ x1, y1 }, { x2, y2 }, { x3, y3 }, p); 467 | } 468 | 469 | void TransformedView::DrawTriangle(const olc::vf2d & pos1, const olc::vf2d & pos2, const olc::vf2d & pos3, olc::Pixel p) 470 | { 471 | pge->DrawTriangle(WorldToScreen(pos1), WorldToScreen(pos2), WorldToScreen(pos3), p); 472 | } 473 | 474 | void TransformedView::FillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, olc::Pixel p) 475 | { 476 | FillTriangle({ x1, y1 }, { x2, y2 }, { x3, y3 }, p); 477 | } 478 | 479 | void TransformedView::FillTriangle(const olc::vf2d & pos1, const olc::vf2d & pos2, const olc::vf2d & pos3, olc::Pixel p) 480 | { 481 | pge->FillTriangle(WorldToScreen(pos1), WorldToScreen(pos2), WorldToScreen(pos3), p); 482 | } 483 | 484 | void TransformedView::DrawSprite(float x, float y, olc::Sprite* sprite, float scalex, float scaley, uint8_t flip) 485 | { 486 | DrawSprite({ x, y }, sprite, { scalex, scaley }, flip); 487 | } 488 | 489 | void TransformedView::DrawSprite(const olc::vf2d & pos, olc::Sprite * sprite, const olc::vf2d & scale, uint8_t flip) 490 | { 491 | olc::vf2d vSpriteSize = olc::vf2d(float(sprite->width), float(sprite->height)); 492 | if (IsRectVisible(pos, vSpriteSize * scale)) 493 | { 494 | olc::vf2d vSpriteScaledSize = vSpriteSize * m_vRecipPixel * m_vWorldScale * scale; 495 | olc::vi2d vPixel; 496 | olc::vi2d vSpritePixelStart = WorldToScreen(pos); 497 | olc::vi2d vSpritePixelEnd = WorldToScreen((vSpriteSize * scale) + pos); 498 | 499 | olc::vi2d vScreenPixelStart = (vSpritePixelStart).max({0,0}); 500 | olc::vi2d vScreenPixelEnd = (vSpritePixelEnd).min({ pge->ScreenWidth(),pge->ScreenHeight() }); 501 | 502 | olc::vf2d vPixelStep = 1.0f / vSpriteScaledSize; 503 | 504 | for (vPixel.y = vScreenPixelStart.y; vPixel.y < vScreenPixelEnd.y; vPixel.y++) 505 | { 506 | for (vPixel.x = vScreenPixelStart.x; vPixel.x < vScreenPixelEnd.x; vPixel.x++) 507 | { 508 | olc::vf2d vSample = olc::vf2d(vPixel - vSpritePixelStart) * vPixelStep; 509 | pge->Draw(vPixel, sprite->Sample(vSample.x, vSample.y)); 510 | } 511 | } 512 | } 513 | } 514 | 515 | 516 | void TransformedView::DrawPartialSprite(float x, float y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, float scalex, float scaley, uint8_t flip) 517 | { 518 | DrawPartialSprite({ x,y }, sprite, { ox,oy }, { w, h }, { scalex, scaley }, flip); 519 | } 520 | 521 | void TransformedView::DrawPartialSprite(const olc::vf2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, const olc::vf2d& scale, uint8_t flip) 522 | { 523 | olc::vf2d vSpriteSize = size; 524 | if (IsRectVisible(pos, size * scale)) 525 | { 526 | olc::vf2d vSpriteScaledSize = olc::vf2d(size) * m_vRecipPixel * m_vWorldScale * scale; 527 | olc::vf2d vSpritePixelStep = 1.0f / olc::vf2d(float(sprite->width), float(sprite->height)); 528 | olc::vi2d vPixel, vStart = WorldToScreen(pos), vEnd = vSpriteScaledSize + vStart; 529 | olc::vf2d vScreenPixelStep = 1.0f / vSpriteScaledSize; 530 | 531 | for (vPixel.y = vStart.y; vPixel.y < vEnd.y; vPixel.y++) 532 | { 533 | for (vPixel.x = vStart.x; vPixel.x < vEnd.x; vPixel.x++) 534 | { 535 | olc::vf2d vSample = ((olc::vf2d(vPixel - vStart) * vScreenPixelStep) * size * vSpritePixelStep) + olc::vf2d(sourcepos) * vSpritePixelStep; 536 | pge->Draw(vPixel, sprite->Sample(vSample.x, vSample.y)); 537 | } 538 | } 539 | } 540 | } 541 | 542 | void TransformedView::DrawString(float x, float y, const std::string& sText, Pixel col, const olc::vf2d& scale) 543 | { 544 | DrawString({ x, y }, sText, col, scale); 545 | } 546 | 547 | void TransformedView::DrawString(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) 548 | { 549 | olc::vf2d vOffset = { 0.0f, 0.0f }; 550 | Pixel::Mode m = pge->GetPixelMode(); 551 | 552 | auto StringPlot = [&col](const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest) 553 | { 554 | return pSource.r > 1 ? col : pDest; 555 | }; 556 | 557 | pge->SetPixelMode(StringPlot); 558 | 559 | for (auto c : sText) 560 | { 561 | if (c == '\n') 562 | { 563 | vOffset.x = 0.0f; vOffset.y += 8.0f * m_vRecipPixel.y * scale.y; 564 | } 565 | else 566 | { 567 | int32_t ox = ((c - 32) % 16) * 8; 568 | int32_t oy = ((c - 32) / 16) * 8; 569 | DrawPartialSprite(pos + vOffset, pge->GetFontSprite(), { ox, oy }, { 8, 8 }, scale); 570 | vOffset.x += 8.0f * m_vRecipPixel.x * scale.x; 571 | } 572 | } 573 | pge->SetPixelMode(m); 574 | } 575 | 576 | 577 | void TransformedView::DrawDecal(const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & scale, const olc::Pixel & tint) 578 | { 579 | pge->DrawDecal(WorldToScreen(pos), decal, scale * m_vWorldScale * m_vRecipPixel, tint); 580 | } 581 | 582 | void TransformedView::DrawPartialDecal(const olc::vf2d & pos, olc::Decal * decal, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::vf2d & scale, const olc::Pixel & tint) 583 | { 584 | pge->DrawPartialDecal(WorldToScreen(pos), decal, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); 585 | } 586 | 587 | void TransformedView::DrawPartialDecal(const olc::vf2d & pos, const olc::vf2d & size, olc::Decal * decal, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::Pixel & tint) 588 | { 589 | pge->DrawPartialDecal(WorldToScreen(pos), size * m_vWorldScale * m_vRecipPixel, decal, source_pos, source_size, tint); 590 | } 591 | 592 | void TransformedView::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) 593 | { 594 | std::vector vTransformed(elements); 595 | for (uint32_t n = 0; n < elements; n++) 596 | vTransformed[n] = WorldToScreen(pos[n]); 597 | pge->DrawExplicitDecal(decal, vTransformed.data(), uv, col, elements); 598 | } 599 | 600 | void TransformedView::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) 601 | { 602 | std::array vTransformed = 603 | { { 604 | WorldToScreen(pos[0]), WorldToScreen(pos[1]), 605 | WorldToScreen(pos[2]), WorldToScreen(pos[3]), 606 | } }; 607 | 608 | pge->DrawWarpedDecal(decal, vTransformed, tint); 609 | } 610 | 611 | void TransformedView::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) 612 | { 613 | DrawWarpedDecal(decal, &pos[0], tint); 614 | } 615 | 616 | void TransformedView::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) 617 | { 618 | DrawWarpedDecal(decal, pos.data(), tint); 619 | } 620 | 621 | void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) 622 | { 623 | DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); 624 | } 625 | 626 | void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) 627 | { 628 | std::array vTransformed = 629 | { { 630 | WorldToScreen(pos[0]), WorldToScreen(pos[1]), 631 | WorldToScreen(pos[2]), WorldToScreen(pos[3]), 632 | } }; 633 | 634 | pge->DrawPartialWarpedDecal(decal, vTransformed, source_pos, source_size, tint); 635 | } 636 | 637 | void TransformedView::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) 638 | { 639 | DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); 640 | } 641 | 642 | void TransformedView::DrawRotatedDecal(const olc::vf2d & pos, olc::Decal * decal, const float fAngle, const olc::vf2d & center, const olc::vf2d & scale, const olc::Pixel & tint) 643 | { 644 | pge->DrawRotatedDecal(WorldToScreen(pos), decal, fAngle, center, scale * m_vWorldScale * m_vRecipPixel, tint); 645 | } 646 | 647 | void TransformedView::DrawPartialRotatedDecal(const olc::vf2d & pos, olc::Decal * decal, const float fAngle, const olc::vf2d & center, const olc::vf2d & source_pos, const olc::vf2d & source_size, const olc::vf2d & scale, const olc::Pixel & tint) 648 | { 649 | pge->DrawPartialRotatedDecal(WorldToScreen(pos), decal, fAngle, center, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); 650 | } 651 | 652 | void TransformedView::DrawStringDecal(const olc::vf2d & pos, const std::string & sText, const olc::Pixel col, const olc::vf2d & scale) 653 | { 654 | pge->DrawStringDecal(WorldToScreen(pos), sText, col, scale * m_vWorldScale * m_vRecipPixel); 655 | } 656 | 657 | void TransformedView::DrawStringPropDecal(const olc::vf2d & pos, const std::string & sText, const olc::Pixel col, const olc::vf2d & scale ) 658 | { 659 | pge->DrawStringPropDecal(WorldToScreen(pos), sText, col, scale * m_vWorldScale * m_vRecipPixel); 660 | } 661 | 662 | void TransformedView::FillRectDecal(const olc::vf2d & pos, const olc::vf2d & size, const olc::Pixel col) 663 | { 664 | pge->FillRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); 665 | } 666 | 667 | void TransformedView::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) 668 | { 669 | pge->DrawRectDecal(WorldToScreen(pos), (size * m_vWorldScale).ceil(), col); 670 | } 671 | 672 | void TransformedView::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) 673 | { 674 | pge->DrawLineDecal(WorldToScreen(pos1), WorldToScreen(pos2), p); 675 | } 676 | 677 | void TransformedView::GradientFillRectDecal(const olc::vf2d & pos, const olc::vf2d & size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) 678 | { 679 | pge->GradientFillRectDecal(WorldToScreen(pos), size * m_vWorldScale, colTL, colBL, colBR, colTR); 680 | } 681 | 682 | void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) 683 | { 684 | std::vector vTransformed(pos.size()); 685 | for (uint32_t n = 0; n < pos.size(); n++) 686 | vTransformed[n] = WorldToScreen(pos[n]); 687 | pge->DrawPolygonDecal(decal, vTransformed, uv, tint); 688 | } 689 | 690 | void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector &tint) 691 | { 692 | std::vector vTransformed(pos.size()); 693 | for (uint32_t n = 0; n < pos.size(); n++) 694 | vTransformed[n] = WorldToScreen(pos[n]); 695 | pge->DrawPolygonDecal(decal, vTransformed, uv, tint); 696 | } 697 | 698 | void TransformedView::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& colours, const olc::Pixel tint) 699 | { 700 | std::vector vTransformed(pos.size()); 701 | for (uint32_t n = 0; n < pos.size(); n++) 702 | vTransformed[n] = WorldToScreen(pos[n]); 703 | pge->DrawPolygonDecal(decal, vTransformed, uv, colours, tint); 704 | } 705 | 706 | 707 | 708 | #if defined (OLC_USING_PGEX_SHADER) 709 | 710 | void TransformedView::DrawDecal(olc::Shade &shade, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale, const olc::Pixel& tint) 711 | { 712 | shade.DrawDecal(WorldToScreen(pos), decal, scale * m_vWorldScale * m_vRecipPixel, tint); 713 | } 714 | 715 | void TransformedView::DrawPartialDecal(olc::Shade& shade, const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) 716 | { 717 | shade.DrawPartialDecal(WorldToScreen(pos), decal, source_pos, source_size, scale * m_vWorldScale * m_vRecipPixel, tint); 718 | } 719 | 720 | void TransformedView::DrawPartialDecal(olc::Shade& shade, const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) 721 | { 722 | shade.DrawPartialDecal(WorldToScreen(pos), size * m_vWorldScale * m_vRecipPixel, decal, source_pos, source_size, tint); 723 | } 724 | 725 | #endif 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | ///////////////////////////////////////////////////////// 734 | ///////////////////////////////////////////////////////// 735 | 736 | 737 | TileTransformedView::TileTransformedView(const olc::vi2d& vViewArea, const olc::vi2d& vTileSize) 738 | { 739 | Initialise(vViewArea, vTileSize); 740 | } 741 | 742 | 743 | olc::vi2d TileTransformedView::GetTopLeftTile() const 744 | { 745 | return ScreenToWorld({ 0,0 }).floor(); 746 | } 747 | 748 | olc::vi2d TileTransformedView::GetBottomRightTile() const 749 | { 750 | return ScreenToWorld(m_vViewArea).ceil(); 751 | } 752 | 753 | olc::vi2d TileTransformedView::GetVisibleTiles() const 754 | { 755 | return GetBottomRightTile() - GetTopLeftTile(); 756 | } 757 | 758 | olc::vi2d TileTransformedView::GetTileUnderScreenPos(const olc::vi2d& vPos) const 759 | { 760 | return ScreenToWorld(vPos).floor(); 761 | } 762 | 763 | const olc::vi2d TileTransformedView::GetTileOffset() const 764 | { 765 | return { int32_t((m_vWorldOffset.x - std::floor(m_vWorldOffset.x)) * m_vWorldScale.x), 766 | int32_t((m_vWorldOffset.y - std::floor(m_vWorldOffset.y)) * m_vWorldScale.y) }; 767 | } 768 | } 769 | 770 | #endif 771 | #endif 772 | -------------------------------------------------------------------------------- /extensions/olcPGEX_Wireframe.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_Wireframe.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Wireframe v1.0 | 7 | +-------------------------------------------------------------+ 8 | 9 | NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES 10 | 11 | What is this? 12 | ~~~~~~~~~~~~~ 13 | This extension provides drawing routines giving simple wireframe 14 | shapes and models constructed in a transform hierachy 15 | 16 | License (OLC-3) 17 | ~~~~~~~~~~~~~~~ 18 | 19 | Copyright 2018 - 2022 OneLoneCoder.com 20 | 21 | Redistribution and use in source and binary forms, with or without 22 | modification, are permitted provided that the following conditions 23 | are met: 24 | 25 | 1. Redistributions or derivations of source code must retain the above 26 | copyright notice, this list of conditions and the following disclaimer. 27 | 28 | 2. Redistributions or derivative works in binary form must reproduce 29 | the above copyright notice. This list of conditions and the following 30 | disclaimer must be reproduced in the documentation and/or other 31 | materials provided with the distribution. 32 | 33 | 3. Neither the name of the copyright holder nor the names of its 34 | contributors may be used to endorse or promote products derived 35 | from this software without specific prior written permission. 36 | 37 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 38 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 39 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 40 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 41 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 42 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 43 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 44 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 45 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 46 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 47 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 48 | 49 | Links 50 | ~~~~~ 51 | YouTube: https://www.youtube.com/javidx9 52 | Discord: https://discord.gg/WhwHUMV 53 | Twitter: https://www.twitter.com/javidx9 54 | Twitch: https://www.twitch.tv/javidx9 55 | GitHub: https://www.github.com/onelonecoder 56 | Homepage: https://www.onelonecoder.com 57 | 58 | Author 59 | ~~~~~~ 60 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022 61 | 62 | Revisions: 63 | 1.00: Initial Release 64 | 65 | */ 66 | 67 | #pragma once 68 | #ifndef OLC_PGEX_WIREFRAME_H 69 | #define OLC_PGEX_WIREFRAME_H 70 | 71 | #include "olcPixelGameEngine.h" 72 | 73 | namespace olc 74 | { 75 | #ifndef OLC_MAT3_DESC 76 | #define OLC_MAT3_DESC 77 | template 78 | struct mat3_generic 79 | { 80 | std::array m{ 0 }; 81 | constexpr size_t idx(size_t r, size_t c) const { return r * 3 + c; } 82 | T& operator()(size_t row, size_t col) { return m[idx(row, col)]; } 83 | const T& operator()(size_t row, size_t col) const { return m[idx(row, col)]; } 84 | mat3_generic() { identity(); } 85 | mat3_generic(const mat3_generic& m) = default; 86 | mat3_generic& operator=(const mat3_generic& m) = default; 87 | 88 | void clear() { std::fill(m.begin(), m.end(), T(0)); } 89 | void identity() { clear(); (*this)(0, 0) = 1; (*this)(1, 1) = 1; (*this)(2, 2) = 1; } 90 | 91 | void translate(float x, float y) { identity(); auto& m = (*this); m(2, 0) = x; m(2, 1) = y; } 92 | void translate(const olc::v2d_generic& v) { translate(v.x, v.y); } 93 | void scale(float x, float y) { identity(); auto& m = (*this); m(0, 0) = x; m(1, 1) = y; } 94 | void scale(const olc::v2d_generic& v) { return scale(v.x, v.y); } 95 | void rotate(float a) { identity(); auto& m = (*this); m(0, 0) = cos(a); m(0, 1) = sin(a); m(1, 0) = -m(0, 1); m(1, 1) = m(0, 0); } 96 | 97 | olc::v2d_generic operator * (const olc::v2d_generic& v) const 98 | { 99 | auto& m = *this; 100 | olc::v2d_generic vOut; 101 | vOut.x = m(0, 0) * v.x + m(1, 0) * v.y + m(2, 0) * T(1); 102 | vOut.y = m(0, 1) * v.x + m(1, 1) * v.y + m(2, 1) * T(1); 103 | T z = m(0, 2) * v.x + m(1, 2) * v.y + m(2, 2) * T(1); 104 | return (vOut / z); 105 | } 106 | 107 | mat3_generic operator * (const mat3_generic& rhs) const 108 | { 109 | auto& m = *this; 110 | mat3_generic out; 111 | for (size_t c = 0; c < 3; c++) 112 | for (size_t r = 0; r < 3; r++) 113 | out(r, c) = m(r, 0) * rhs(0, c) + m(r, 1) * rhs(1, c) + m(r, 2) * rhs(2, c); 114 | return out; 115 | } 116 | }; 117 | 118 | typedef mat3_generic Matrix2D; 119 | #endif 120 | 121 | namespace wire 122 | { 123 | typedef std::vector Mesh; 124 | //Mesh NullMesh; 125 | 126 | class Model 127 | { 128 | public: 129 | static constexpr uint8_t DRAW_ORIGIN = 0x01; 130 | static constexpr uint8_t DRAW_NODES = 0x02; 131 | static constexpr uint8_t DRAW_MEASURES = 0x04; 132 | public: 133 | Model() = default; 134 | 135 | public: 136 | void Attach(Model* child, const olc::vf2d& position = { 0.0f, 0.0f }, const float angle = 0.0f); 137 | void SetRotation(const float angle); 138 | void SetPosition(const olc::vf2d& position); 139 | void UpdateInWorld(const Matrix2D& matParent); 140 | olc::vf2d LocalToWorld(const olc::vf2d& local); 141 | void SetMesh(const Mesh& mesh); 142 | const Mesh& GetWorldPoints() const; 143 | const std::vector& GetChildren() const; 144 | 145 | protected: 146 | Mesh vLocalPoints;; 147 | Mesh vWorldPoints; 148 | olc::Matrix2D matLocalTranslation; 149 | olc::Matrix2D matLocalRotation; 150 | olc::Matrix2D matLocal; 151 | olc::Matrix2D matWorld; 152 | 153 | protected: 154 | std::vector vChildren; 155 | }; 156 | 157 | 158 | 159 | inline const Mesh MeshCircle(const float fRadius, const int nPoints = 100) 160 | { 161 | Mesh m; 162 | for (int i = 0; i < nPoints; i++) 163 | { 164 | float fTheta = (float(i) / float(nPoints)) * 2.0f * 3.14159f; 165 | m.push_back(olc::vf2d{ cos(fTheta), sin(fTheta) } *fRadius); 166 | } 167 | return m; 168 | } 169 | 170 | inline const Mesh MeshRectangle(const olc::vf2d& size, const olc::vf2d& offset = { 0.0f, 0.0f }) 171 | { 172 | return { -offset, {-offset.x + size.x, -offset.y}, -offset + size, {-offset.x, -offset.y + size.y} }; 173 | } 174 | 175 | inline const Mesh MeshGear(const int nTeeth, const float fOuterRadius, const float fInnerRadius) 176 | { 177 | Mesh m; 178 | for (int i = 0; i < nTeeth * 4; i++) 179 | { 180 | float fTheta = (float(i) / float(nTeeth * 4)) * 2.0f * 3.14159f; 181 | m.push_back(olc::vf2d{ cos(fTheta), sin(fTheta) } * 2.0f * (float((i / 2) % 2) ? fOuterRadius : fInnerRadius)); 182 | } 183 | return m; 184 | } 185 | 186 | template 187 | void DrawModel(T& render, Model& m, const olc::Pixel col = olc::BLACK, const uint8_t flags = -1) 188 | { 189 | const auto& points = m.GetWorldPoints(); 190 | for(size_t i = 0; i < points.size(); i++) 191 | render.DrawLine(points[i], points[(i+1)%points.size()], col); 192 | 193 | // Draw Nodes 194 | if (flags & Model::DRAW_NODES) 195 | for (size_t i = 0; i < points.size(); i++) 196 | render.FillCircle(points[i], render.ScaleToWorld({ 3,3 }).x, olc::RED); 197 | 198 | if (flags & Model::DRAW_ORIGIN) 199 | { 200 | render.FillCircle(m.LocalToWorld({ 0,0 }), render.ScaleToWorld({ 3,3 }).x, olc::BLUE); 201 | render.DrawLine( 202 | m.LocalToWorld({ 0,0 }), 203 | m.LocalToWorld(render.ScaleToWorld({ 10, 0 })), 204 | olc::BLUE); 205 | } 206 | 207 | // Draw Children 208 | for (auto& child : m.GetChildren()) 209 | DrawModel(render, *child, col, flags); 210 | } 211 | 212 | 213 | } 214 | } 215 | 216 | #ifdef OLC_PGEX_WIREFRAME 217 | #undef OLC_PGEX_WIREFRAME 218 | 219 | namespace olc 220 | { 221 | namespace wire 222 | { 223 | void Model::SetMesh(const Mesh& mesh) 224 | { 225 | vLocalPoints = mesh; 226 | vWorldPoints.clear(); 227 | vWorldPoints.resize(vLocalPoints.size()); 228 | } 229 | 230 | void Model::SetRotation(const float angle) 231 | { 232 | matLocalRotation.rotate(angle); 233 | matLocal = matLocalRotation * matLocalTranslation; 234 | } 235 | 236 | void Model::SetPosition(const olc::vf2d& position) 237 | { 238 | matLocalTranslation.translate(position); 239 | matLocal = matLocalRotation * matLocalTranslation; 240 | } 241 | 242 | void Model::Attach(Model* child, const olc::vf2d& position, const float angle) 243 | { 244 | if (child != nullptr) 245 | { 246 | child->SetPosition(position); 247 | child->SetRotation(angle); 248 | vChildren.push_back(child); 249 | } 250 | } 251 | 252 | olc::vf2d Model::LocalToWorld(const olc::vf2d& local) 253 | { 254 | return matWorld * local; 255 | } 256 | 257 | const Mesh& Model::GetWorldPoints() const 258 | { 259 | return vWorldPoints; 260 | } 261 | 262 | const std::vector& Model::GetChildren() const 263 | { 264 | return vChildren; 265 | } 266 | 267 | void Model::UpdateInWorld(const Matrix2D& matParent) 268 | { 269 | // Propagate matrix transform 270 | matWorld = matLocal * matParent; 271 | 272 | // Transform vertices 273 | for (size_t i = 0; i < vLocalPoints.size(); i++) 274 | { 275 | vWorldPoints[i] = matWorld * vLocalPoints[i]; 276 | } 277 | 278 | // Transform Children 279 | for (auto& child : vChildren) 280 | child->UpdateInWorld(matWorld); 281 | } 282 | } 283 | } 284 | 285 | #endif // OLC_PGEX_WIREFRAME 286 | #endif // OLC_PGEX_WIREFRAME_H 287 | 288 | -------------------------------------------------------------------------------- /olcExampleProgram.cpp: -------------------------------------------------------------------------------- 1 | #define OLC_PGE_APPLICATION 2 | #include "olcPixelGameEngine.h" 3 | 4 | class Example : public olc::PixelGameEngine 5 | { 6 | public: 7 | Example() 8 | { 9 | sAppName = "Example"; 10 | } 11 | 12 | public: 13 | bool OnUserCreate() override 14 | { 15 | // Called once at the start, so create things here 16 | return true; 17 | } 18 | 19 | bool OnUserUpdate(float fElapsedTime) override 20 | { 21 | // called once per frame 22 | for (int x = 0; x < ScreenWidth(); x++) 23 | for (int y = 0; y < ScreenHeight(); y++) 24 | Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); 25 | return true; 26 | } 27 | }; 28 | 29 | 30 | int main() 31 | { 32 | Example demo; 33 | if (demo.Construct(256, 240, 4, 4)) 34 | demo.Start(); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /tools/wasm/README.md: -------------------------------------------------------------------------------- 1 | Coming Soon, information on using olc::PixelGameEngine in web browsers 2 | -------------------------------------------------------------------------------- /tools/wasm/basic_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Emscripten-Generated Code 9 | 19 | 20 | 21 | 22 | 38 | 39 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /tools/wasm/pge2wasm.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Convenience Utility to build projects using olc::PixelGameEngine, using 3 | :: Emscripten, producing WASM based output. 4 | :: 5 | :: OneLoneCoder.com 2021 - Released under OLC-3 license 6 | :: 7 | :: v1.00: Initial Release 8 | 9 | setlocal enabledelayedexpansion enableextensions 10 | 11 | :: Customize here =========================================== 12 | 13 | :: Location of Emscripten SDK 14 | set EMSDK="e:\pge_ems\emsdk\" 15 | 16 | :: Location of olc::PixelGameEngine header file 17 | set OLCPGE=".\" 18 | set OLCPGE="e:\pge_dev\olcPixelGameEngine_dev\Deploy" 19 | 20 | :: ========================================================== 21 | 22 | set WORKINGDIR=%CD% 23 | 24 | if not exist %EMSDK% ( 25 | echo Error: No Emscripten SDK folder found! 26 | goto :fail 27 | ) 28 | 29 | if not exist %OLCPGE% ( 30 | echo Error: Invalid PGE Location! 31 | goto :fail 32 | ) 33 | 34 | if "%1"=="build" goto :build 35 | if "%1"=="run" goto :run 36 | if "%1"=="clean" goto :clean 37 | goto :error 38 | 39 | :build 40 | :: Configure path variables 41 | cd %EMSDK% 42 | call emsdk_env.bat 43 | 44 | :: Create working folder 45 | cd %WORKINGDIR% 46 | if not exist ".\WASM" ( 47 | echo Creating .\WASM output folder 48 | mkdir ".\WASM" 49 | ) 50 | 51 | :: Grab all cpp files if no specific file is given 52 | if "%~2"=="" goto :graball 53 | set CPP=%~2 54 | goto :embuild 55 | 56 | :graball 57 | echo Gathering *.cpp files from 58 | echo %CD% 59 | set CPP= 60 | for %%x in (%CD%\*.cpp) do set CPP=!CPP! %%x 61 | set CPP=%CPP:~1% 62 | 63 | :embuild 64 | 65 | echo %CPP% 66 | if exist "./assets" ( 67 | echo Starting Build with assets... 68 | call em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 %CPP% -o .\WASM\pge.html -I %OLCPGE% --preload-file .\assets 69 | ) else ( 70 | echo Starting Build without assets... 71 | call em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 %CPP% -o .\WASM\pge.html -I %OLCPGE% 72 | ) 73 | 74 | echo Build Completed 75 | goto :success 76 | 77 | :run 78 | :: Configure path variables 79 | cd %EMSDK% 80 | call emsdk_env.bat 81 | cd %WORKINGDIR% 82 | emrun .\WASM\pge.html 83 | goto :success 84 | 85 | :clean 86 | if exist ".\WASM" ( 87 | rmdir /s /q .\WASM 88 | ) 89 | goto :success 90 | 91 | :error 92 | echo Error: Incorrect Input 93 | goto :fail 94 | 95 | :success 96 | echo Exit With Success 97 | goto :leave 98 | 99 | :fail 100 | echo Exit with Failure 101 | goto :leave 102 | 103 | :leave 104 | exit 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /utilities/olcUTIL_Animate2D.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_Animate2D.h -------------------------------------------------------------------------------- /utilities/olcUTIL_Camera2D.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_Camera2D.h -------------------------------------------------------------------------------- /utilities/olcUTIL_Container.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_Container.h -------------------------------------------------------------------------------- /utilities/olcUTIL_DataFile.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_DataFile.h -------------------------------------------------------------------------------- /utilities/olcUTIL_Palette.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_Palette.h -------------------------------------------------------------------------------- /utilities/olcUTIL_QuadTree.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/utilities/olcUTIL_QuadTree.h --------------------------------------------------------------------------------