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