From 75683314a568b54ac1f4015ff7e4cf210615b4c1 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 19 Apr 2020 01:26:26 +0100 Subject: [PATCH] Fixed duplicate shape bug Thanks "howlevergreen" from discord! A great find, and a great fix! --- Videos/OneLoneCoder_PGE_Polymorphism.cpp | 1078 +++++++++++----------- 1 file changed, 542 insertions(+), 536 deletions(-) diff --git a/Videos/OneLoneCoder_PGE_Polymorphism.cpp b/Videos/OneLoneCoder_PGE_Polymorphism.cpp index 134cde3..424e2a1 100644 --- a/Videos/OneLoneCoder_PGE_Polymorphism.cpp +++ b/Videos/OneLoneCoder_PGE_Polymorphism.cpp @@ -1,536 +1,542 @@ -/* - OLC::CAD - A practical example of Polymorphism - "Damn Gorbette, you made us giggle..." - javidx9 - - License (OLC-3) - ~~~~~~~~~~~~~~~ - - Copyright 2018-2019 OneLoneCoder.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions or derivations of source code must retain the above - copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions or derivative works in binary form must reproduce - the above copyright notice. This list of conditions and the following - disclaimer must be reproduced in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Instructions: - ~~~~~~~~~~~~~ - Press & Hold middle mouse mutton to PAN - Use Scroll wheel (or Q & A) to zoom in & out - Press L to start drawing a line - Press C to start drawing a circle - Press B to start drawing a box - Press S to start drawing a curve - Press M to move node under cursor - - Relevant Video: https://youtu.be/kxKKHKSMGIg - - Links - ~~~~~ - YouTube: https://www.youtube.com/javidx9 - https://www.youtube.com/javidx9extra - Discord: https://discord.gg/WhwHUMV - Twitter: https://www.twitter.com/javidx9 - Twitch: https://www.twitch.tv/javidx9 - GitHub: https://www.github.com/onelonecoder - Patreon: https://www.patreon.com/javidx9 - Homepage: https://www.onelonecoder.com - - Author - ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2019 -*/ - -#define OLC_PGE_APPLICATION -#include "olcPixelGameEngine.h" - -// Forward declare shape, since we use it in sNode -struct sShape; - -// Define a node -struct sNode -{ - sShape *parent; - olc::vf2d pos; -}; - -// Our BASE class, defines the interface for all shapes -struct sShape -{ - // Shapes are defined by the placment of nodes - std::vector vecNodes; - uint32_t nMaxNodes = 0; - - // The colour of the shape - olc::Pixel col = olc::GREEN; - - // All shapes share word to screen transformation - // coefficients, so share them staically - static float fWorldScale; - static olc::vf2d vWorldOffset; - - // Convert coordinates from World Space --> Screen Space - void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) - { - nScreenX = (int)((v.x - vWorldOffset.x) * fWorldScale); - nScreenY = (int)((v.y - vWorldOffset.y) * fWorldScale); - } - - // This is a PURE function, which makes this class abstract. A sub-class - // of this class must provide an implementation of this function by - // overriding it - virtual void DrawYourself(olc::PixelGameEngine *pge) = 0; - - // Shapes are defined by nodes, the shape is responsible - // for issuing nodes that get placed by the user. The shape may - // change depending on how many nodes have been placed. Once the - // maximum number of nodes for a shape have been placed, it returns - // nullptr - sNode* GetNextNode(const olc::vf2d &p) - { - if (vecNodes.size() == nMaxNodes) - return nullptr; // Shape is complete so no new nodes to be issued - - // else create new node and add to shapes node vector - sNode n; - n.parent = this; - n.pos = p; - vecNodes.push_back(n); - - // Beware! - This normally is bad! But see sub classes - return &vecNodes[vecNodes.size() - 1]; - } - - // Test to see if supplied coordinate exists at same location - // as any of the nodes for this shape. Return a pointer to that - // node if it does - sNode* HitNode(olc::vf2d &p) - { - for (auto &n : vecNodes) - { - if ((p - n.pos).mag() < 0.01f) - return &n; - } - - return nullptr; - } - - // Draw all of the nodes that define this shape so far - void DrawNodes(olc::PixelGameEngine *pge) - { - for (auto &n : vecNodes) - { - int sx, sy; - WorldToScreen(n.pos, sx, sy); - pge->FillCircle(sx, sy, 2, olc::RED); - } - } -}; - -// We must provide an implementation of our static variables -float sShape::fWorldScale = 1.0f; -olc::vf2d sShape::vWorldOffset = { 0,0 }; - - - -// LINE sub class, inherits from sShape -struct sLine : public sShape -{ - sLine() - { - nMaxNodes = 2; - vecNodes.reserve(nMaxNodes); // We're gonna be getting pointers to vector elements - // though we have defined already how much capacity our vector will have. This makes - // it safe to do this as we know the vector will not be maniupulated as we add nodes - // to it. Is this bad practice? Possibly, but as with all thing programming, if you - // know what you are doing, it's ok :D - } - - // Implements custom DrawYourself Function, meaning the shape - // is no longer abstract - void DrawYourself(olc::PixelGameEngine *pge) override - { - int sx, sy, ex, ey; - WorldToScreen(vecNodes[0].pos, sx, sy); - WorldToScreen(vecNodes[1].pos, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col); - } -}; - - -// BOX -struct sBox : public sShape -{ - sBox() - { - nMaxNodes = 2; - vecNodes.reserve(nMaxNodes); - } - - void DrawYourself(olc::PixelGameEngine *pge) override - { - int sx, sy, ex, ey; - WorldToScreen(vecNodes[0].pos, sx, sy); - WorldToScreen(vecNodes[1].pos, ex, ey); - pge->DrawRect(sx, sy, ex - sx, ey - sy, col); - } -}; - - -// CIRCLE -struct sCircle : public sShape -{ - sCircle() - { - nMaxNodes = 2; - vecNodes.reserve(nMaxNodes); - } - - void DrawYourself(olc::PixelGameEngine *pge) override - { - float fRadius = (vecNodes[0].pos - vecNodes[1].pos).mag(); - int sx, sy, ex, ey; - WorldToScreen(vecNodes[0].pos, sx, sy); - WorldToScreen(vecNodes[1].pos, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); - - // Note the radius is also scaled so it is drawn appropriately - pge->DrawCircle(sx, sy, (int32_t)(fRadius * fWorldScale), col); - } -}; - -// BEZIER SPLINE - requires 3 nodes to be defined fully -struct sCurve : public sShape -{ - sCurve() - { - nMaxNodes = 3; - vecNodes.reserve(nMaxNodes); - } - - void DrawYourself(olc::PixelGameEngine *pge) override - { - int sx, sy, ex, ey; - - if (vecNodes.size() < 3) - { - // Can only draw line from first to second - WorldToScreen(vecNodes[0].pos, sx, sy); - WorldToScreen(vecNodes[1].pos, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); - } - - if (vecNodes.size() == 3) - { - // Can draw line from first to second - WorldToScreen(vecNodes[0].pos, sx, sy); - WorldToScreen(vecNodes[1].pos, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); - - // Can draw second structural line - WorldToScreen(vecNodes[1].pos, sx, sy); - WorldToScreen(vecNodes[2].pos, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); - - // And bezier curve - olc::vf2d op = vecNodes[0].pos; - olc::vf2d np = op; - for (float t = 0; t < 1.0f; t += 0.01f) - { - np = (1 - t)*(1 - t)*vecNodes[0].pos + 2 * (1 - t)*t*vecNodes[1].pos + t * t * vecNodes[2].pos; - WorldToScreen(op, sx, sy); - WorldToScreen(np, ex, ey); - pge->DrawLine(sx, sy, ex, ey, col); - op = np; - } - } - - } -}; - - - -// APPLICATION STARTS HERE - -class Polymorphism : public olc::PixelGameEngine -{ -public: - Polymorphism() - { - sAppName = "Polymorphism"; - } - -private: - // Pan & Zoom variables - olc::vf2d vOffset = { 0.0f, 0.0f }; - olc::vf2d vStartPan = { 0.0f, 0.0f }; - float fScale = 10.0f; - float fGrid = 1.0f; - - // Convert coordinates from World Space --> Screen Space - void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) - { - nScreenX = (int)((v.x - vOffset.x) * fScale); - nScreenY = (int)((v.y - vOffset.y) * fScale); - } - - // Convert coordinates from Screen Space --> World Space - void ScreenToWorld(int nScreenX, int nScreenY, olc::vf2d &v) - { - v.x = (float)(nScreenX) / fScale + vOffset.x; - v.y = (float)(nScreenY) / fScale + vOffset.y; - } - - - // A pointer to a shape that is currently being defined - // by the placment of nodes - sShape* tempShape = nullptr; - - // A list of pointers to all shapes which have been drawn - // so far - std::list listShapes; - - // A pointer to a node that is currently selected. Selected - // nodes follow the mouse cursor - sNode *selectedNode = nullptr; - - // "Snapped" mouse location - olc::vf2d vCursor = { 0, 0 }; - - // NOTE! No direct instances of lines, circles, boxes or curves, - // the application is only aware of the existence of shapes! - // THIS IS THE POWER OF POLYMORPHISM! - -public: - bool OnUserCreate() override - { - // Configure world space (0,0) to be middle of screen space - vOffset = { (float)(-ScreenWidth() / 2) / fScale, (float)(-ScreenHeight() / 2) / fScale }; - return true; - } - - bool OnUserUpdate(float fElapsedTime) override - { - // Get mouse location this frame - olc::vf2d vMouse = { (float)GetMouseX(), (float)GetMouseY() }; - - - // Handle Pan & Zoom - if (GetMouse(2).bPressed) - { - vStartPan = vMouse; - } - - if (GetMouse(2).bHeld) - { - vOffset -= (vMouse - vStartPan) / fScale; - vStartPan = vMouse; - } - - olc::vf2d vMouseBeforeZoom; - ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseBeforeZoom); - - if (GetKey(olc::Key::Q).bHeld || GetMouseWheel() > 0) - { - fScale *= 1.1f; - } - - if (GetKey(olc::Key::A).bHeld || GetMouseWheel() < 0) - { - fScale *= 0.9f; - } - - olc::vf2d vMouseAfterZoom; - ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseAfterZoom); - vOffset += (vMouseBeforeZoom - vMouseAfterZoom); - - - // Snap mouse cursor to nearest grid interval - vCursor.x = floorf((vMouseAfterZoom.x + 0.5f) * fGrid); - vCursor.y = floorf((vMouseAfterZoom.y + 0.5f) * fGrid); - - - if (GetKey(olc::Key::L).bPressed) - { - tempShape = new sLine(); - - // Place first node at location of keypress - selectedNode = tempShape->GetNextNode(vCursor); - - // Get Second node - selectedNode = tempShape->GetNextNode(vCursor); - } - - - if (GetKey(olc::Key::B).bPressed) - { - tempShape = new sBox(); - - // Place first node at location of keypress - selectedNode = tempShape->GetNextNode(vCursor); - - // Get Second node - selectedNode = tempShape->GetNextNode(vCursor); - } - - if (GetKey(olc::Key::C).bPressed) - { - // Create new shape as a temporary - tempShape = new sCircle(); - - // Place first node at location of keypress - selectedNode = tempShape->GetNextNode(vCursor); - - // Get Second node - selectedNode = tempShape->GetNextNode(vCursor); - } - - if (GetKey(olc::Key::S).bPressed) - { - // Create new shape as a temporary - tempShape = new sCurve(); - - // Place first node at location of keypress - selectedNode = tempShape->GetNextNode(vCursor); - - // Get Second node - selectedNode = tempShape->GetNextNode(vCursor); - } - - // Search for any node that exists under the cursor, if one - // is found then select it - if (GetKey(olc::Key::M).bPressed) - { - selectedNode = nullptr; - for (auto &shape : listShapes) - { - selectedNode = shape->HitNode(vCursor); - if (selectedNode != nullptr) - break; - } - } - - - // If a node is selected, make it follow the mouse cursor - // by updating its position - if (selectedNode != nullptr) - { - selectedNode->pos = vCursor; - } - - - // As the user left clicks to place nodes, the shape can grow - // until it requires no more nodes, at which point it is completed - // and added to the list of completed shapes. - if (GetMouse(0).bReleased) - { - if (tempShape != nullptr) - { - selectedNode = tempShape->GetNextNode(vCursor); - if (selectedNode == nullptr) - { - tempShape->col = olc::WHITE; - listShapes.push_back(tempShape); - } - } - } - - - - // Clear Screen - Clear(olc::VERY_DARK_BLUE); - - int sx, sy; - int ex, ey; - - // Get visible world - olc::vf2d vWorldTopLeft, vWorldBottomRight; - ScreenToWorld(0, 0, vWorldTopLeft); - ScreenToWorld(ScreenWidth(), ScreenHeight(), vWorldBottomRight); - - // Get values just beyond screen boundaries - vWorldTopLeft.x = floor(vWorldTopLeft.x); - vWorldTopLeft.y = floor(vWorldTopLeft.y); - vWorldBottomRight.x = ceil(vWorldBottomRight.x); - vWorldBottomRight.y = ceil(vWorldBottomRight.y); - - // Draw Grid dots - for (float x = vWorldTopLeft.x; x < vWorldBottomRight.x; x += fGrid) - { - for (float y = vWorldTopLeft.y; y < vWorldBottomRight.y; y += fGrid) - { - WorldToScreen({ x, y }, sx, sy); - Draw(sx, sy, olc::BLUE); - } - } - - // Draw World Axis - WorldToScreen({ 0,vWorldTopLeft.y }, sx, sy); - WorldToScreen({ 0,vWorldBottomRight.y }, ex, ey); - DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); - WorldToScreen({ vWorldTopLeft.x,0 }, sx, sy); - WorldToScreen({ vWorldBottomRight.x,0 }, ex, ey); - DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); - - // Update shape translation coefficients - sShape::fWorldScale = fScale; - sShape::vWorldOffset = vOffset; - - // Draw All Existing Shapes - for (auto &shape : listShapes) - { - shape->DrawYourself(this); - shape->DrawNodes(this); - } - - // Draw shape currently being defined - if (tempShape != nullptr) - { - tempShape->DrawYourself(this); - tempShape->DrawNodes(this); - } - - // Draw "Snapped" Cursor - WorldToScreen(vCursor, sx, sy); - DrawCircle(sx, sy, 3, olc::YELLOW); - - // Draw Cursor Position - DrawString(10, 10, "X=" + std::to_string(vCursor.x) + ", Y=" + std::to_string(vCursor.y), olc::YELLOW, 2); - return true; - } -}; - - -int main() -{ - Polymorphism demo; - if (demo.Construct(1600, 960, 1, 1)) - demo.Start(); - return 0; -} - - - - +/* + OLC::CAD - A practical example of Polymorphism + "Damn Gorbette, you made us giggle..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Instructions: + ~~~~~~~~~~~~~ + Press & Hold middle mouse mutton to PAN + Use Scroll wheel (or Q & A) to zoom in & out + Press L to start drawing a line + Press C to start drawing a circle + Press B to start drawing a box + Press S to start drawing a curve + Press M to move node under cursor + + Relevant Video: https://youtu.be/kxKKHKSMGIg + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Forward declare shape, since we use it in sNode +struct sShape; + +// Define a node +struct sNode +{ + sShape *parent; + olc::vf2d pos; +}; + +// Our BASE class, defines the interface for all shapes +struct sShape +{ + // Shapes are defined by the placment of nodes + std::vector vecNodes; + uint32_t nMaxNodes = 0; + + // The colour of the shape + olc::Pixel col = olc::GREEN; + + // All shapes share word to screen transformation + // coefficients, so share them staically + static float fWorldScale; + static olc::vf2d vWorldOffset; + + // Convert coordinates from World Space --> Screen Space + void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) + { + nScreenX = (int)((v.x - vWorldOffset.x) * fWorldScale); + nScreenY = (int)((v.y - vWorldOffset.y) * fWorldScale); + } + + // This is a PURE function, which makes this class abstract. A sub-class + // of this class must provide an implementation of this function by + // overriding it + virtual void DrawYourself(olc::PixelGameEngine *pge) = 0; + + // Shapes are defined by nodes, the shape is responsible + // for issuing nodes that get placed by the user. The shape may + // change depending on how many nodes have been placed. Once the + // maximum number of nodes for a shape have been placed, it returns + // nullptr + sNode* GetNextNode(const olc::vf2d &p) + { + if (vecNodes.size() == nMaxNodes) + return nullptr; // Shape is complete so no new nodes to be issued + + // else create new node and add to shapes node vector + sNode n; + n.parent = this; + n.pos = p; + vecNodes.push_back(n); + + // Beware! - This normally is bad! But see sub classes + return &vecNodes[vecNodes.size() - 1]; + } + + // Test to see if supplied coordinate exists at same location + // as any of the nodes for this shape. Return a pointer to that + // node if it does + sNode* HitNode(olc::vf2d &p) + { + for (auto &n : vecNodes) + { + if ((p - n.pos).mag() < 0.01f) + return &n; + } + + return nullptr; + } + + // Draw all of the nodes that define this shape so far + void DrawNodes(olc::PixelGameEngine *pge) + { + for (auto &n : vecNodes) + { + int sx, sy; + WorldToScreen(n.pos, sx, sy); + pge->FillCircle(sx, sy, 2, olc::RED); + } + } +}; + +// We must provide an implementation of our static variables +float sShape::fWorldScale = 1.0f; +olc::vf2d sShape::vWorldOffset = { 0,0 }; + + + +// LINE sub class, inherits from sShape +struct sLine : public sShape +{ + sLine() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); // We're gonna be getting pointers to vector elements + // though we have defined already how much capacity our vector will have. This makes + // it safe to do this as we know the vector will not be maniupulated as we add nodes + // to it. Is this bad practice? Possibly, but as with all thing programming, if you + // know what you are doing, it's ok :D + } + + // Implements custom DrawYourself Function, meaning the shape + // is no longer abstract + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col); + } +}; + + +// BOX +struct sBox : public sShape +{ + sBox() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawRect(sx, sy, ex - sx, ey - sy, col); + } +}; + + +// CIRCLE +struct sCircle : public sShape +{ + sCircle() + { + nMaxNodes = 2; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + float fRadius = (vecNodes[0].pos - vecNodes[1].pos).mag(); + int sx, sy, ex, ey; + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // Note the radius is also scaled so it is drawn appropriately + pge->DrawCircle(sx, sy, (int32_t)(fRadius * fWorldScale), col); + } +}; + +// BEZIER SPLINE - requires 3 nodes to be defined fully +struct sCurve : public sShape +{ + sCurve() + { + nMaxNodes = 3; + vecNodes.reserve(nMaxNodes); + } + + void DrawYourself(olc::PixelGameEngine *pge) override + { + int sx, sy, ex, ey; + + if (vecNodes.size() < 3) + { + // Can only draw line from first to second + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + } + + if (vecNodes.size() == 3) + { + // Can draw line from first to second + WorldToScreen(vecNodes[0].pos, sx, sy); + WorldToScreen(vecNodes[1].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // Can draw second structural line + WorldToScreen(vecNodes[1].pos, sx, sy); + WorldToScreen(vecNodes[2].pos, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col, 0xFF00FF00); + + // And bezier curve + olc::vf2d op = vecNodes[0].pos; + olc::vf2d np = op; + for (float t = 0; t < 1.0f; t += 0.01f) + { + np = (1 - t)*(1 - t)*vecNodes[0].pos + 2 * (1 - t)*t*vecNodes[1].pos + t * t * vecNodes[2].pos; + WorldToScreen(op, sx, sy); + WorldToScreen(np, ex, ey); + pge->DrawLine(sx, sy, ex, ey, col); + op = np; + } + } + + } +}; + + + +// APPLICATION STARTS HERE + +class Polymorphism : public olc::PixelGameEngine +{ +public: + Polymorphism() + { + sAppName = "Polymorphism"; + } + +private: + // Pan & Zoom variables + olc::vf2d vOffset = { 0.0f, 0.0f }; + olc::vf2d vStartPan = { 0.0f, 0.0f }; + float fScale = 10.0f; + float fGrid = 1.0f; + + // Convert coordinates from World Space --> Screen Space + void WorldToScreen(const olc::vf2d &v, int &nScreenX, int &nScreenY) + { + nScreenX = (int)((v.x - vOffset.x) * fScale); + nScreenY = (int)((v.y - vOffset.y) * fScale); + } + + // Convert coordinates from Screen Space --> World Space + void ScreenToWorld(int nScreenX, int nScreenY, olc::vf2d &v) + { + v.x = (float)(nScreenX) / fScale + vOffset.x; + v.y = (float)(nScreenY) / fScale + vOffset.y; + } + + + // A pointer to a shape that is currently being defined + // by the placment of nodes + sShape* tempShape = nullptr; + + // A list of pointers to all shapes which have been drawn + // so far + std::list listShapes; + + // A pointer to a node that is currently selected. Selected + // nodes follow the mouse cursor + sNode *selectedNode = nullptr; + + // "Snapped" mouse location + olc::vf2d vCursor = { 0, 0 }; + + // NOTE! No direct instances of lines, circles, boxes or curves, + // the application is only aware of the existence of shapes! + // THIS IS THE POWER OF POLYMORPHISM! + +public: + bool OnUserCreate() override + { + // Configure world space (0,0) to be middle of screen space + vOffset = { (float)(-ScreenWidth() / 2) / fScale, (float)(-ScreenHeight() / 2) / fScale }; + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Get mouse location this frame + olc::vf2d vMouse = { (float)GetMouseX(), (float)GetMouseY() }; + + + // Handle Pan & Zoom + if (GetMouse(2).bPressed) + { + vStartPan = vMouse; + } + + if (GetMouse(2).bHeld) + { + vOffset -= (vMouse - vStartPan) / fScale; + vStartPan = vMouse; + } + + olc::vf2d vMouseBeforeZoom; + ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseBeforeZoom); + + if (GetKey(olc::Key::Q).bHeld || GetMouseWheel() > 0) + { + fScale *= 1.1f; + } + + if (GetKey(olc::Key::A).bHeld || GetMouseWheel() < 0) + { + fScale *= 0.9f; + } + + olc::vf2d vMouseAfterZoom; + ScreenToWorld((int)vMouse.x, (int)vMouse.y, vMouseAfterZoom); + vOffset += (vMouseBeforeZoom - vMouseAfterZoom); + + + // Snap mouse cursor to nearest grid interval + vCursor.x = floorf((vMouseAfterZoom.x + 0.5f) * fGrid); + vCursor.y = floorf((vMouseAfterZoom.y + 0.5f) * fGrid); + + + if (GetKey(olc::Key::L).bPressed) + { + tempShape = new sLine(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + + if (GetKey(olc::Key::B).bPressed) + { + tempShape = new sBox(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + if (GetKey(olc::Key::C).bPressed) + { + // Create new shape as a temporary + tempShape = new sCircle(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + if (GetKey(olc::Key::S).bPressed) + { + // Create new shape as a temporary + tempShape = new sCurve(); + + // Place first node at location of keypress + selectedNode = tempShape->GetNextNode(vCursor); + + // Get Second node + selectedNode = tempShape->GetNextNode(vCursor); + } + + // Search for any node that exists under the cursor, if one + // is found then select it + if (GetKey(olc::Key::M).bPressed) + { + selectedNode = nullptr; + for (auto &shape : listShapes) + { + selectedNode = shape->HitNode(vCursor); + if (selectedNode != nullptr) + break; + } + } + + + // If a node is selected, make it follow the mouse cursor + // by updating its position + if (selectedNode != nullptr) + { + selectedNode->pos = vCursor; + } + + + // As the user left clicks to place nodes, the shape can grow + // until it requires no more nodes, at which point it is completed + // and added to the list of completed shapes. + if (GetMouse(0).bReleased) + { + if (tempShape != nullptr) + { + selectedNode = tempShape->GetNextNode(vCursor); + if (selectedNode == nullptr) + { + tempShape->col = olc::WHITE; + listShapes.push_back(tempShape); + tempShape = nullptr; // Thanks @howlevergreen /Disord + } + + } + else + { + selectedNode = nullptr; + } + } + + + + // Clear Screen + Clear(olc::VERY_DARK_BLUE); + + int sx, sy; + int ex, ey; + + // Get visible world + olc::vf2d vWorldTopLeft, vWorldBottomRight; + ScreenToWorld(0, 0, vWorldTopLeft); + ScreenToWorld(ScreenWidth(), ScreenHeight(), vWorldBottomRight); + + // Get values just beyond screen boundaries + vWorldTopLeft.x = floor(vWorldTopLeft.x); + vWorldTopLeft.y = floor(vWorldTopLeft.y); + vWorldBottomRight.x = ceil(vWorldBottomRight.x); + vWorldBottomRight.y = ceil(vWorldBottomRight.y); + + // Draw Grid dots + for (float x = vWorldTopLeft.x; x < vWorldBottomRight.x; x += fGrid) + { + for (float y = vWorldTopLeft.y; y < vWorldBottomRight.y; y += fGrid) + { + WorldToScreen({ x, y }, sx, sy); + Draw(sx, sy, olc::BLUE); + } + } + + // Draw World Axis + WorldToScreen({ 0,vWorldTopLeft.y }, sx, sy); + WorldToScreen({ 0,vWorldBottomRight.y }, ex, ey); + DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); + WorldToScreen({ vWorldTopLeft.x,0 }, sx, sy); + WorldToScreen({ vWorldBottomRight.x,0 }, ex, ey); + DrawLine(sx, sy, ex, ey, olc::GREY, 0xF0F0F0F0); + + // Update shape translation coefficients + sShape::fWorldScale = fScale; + sShape::vWorldOffset = vOffset; + + // Draw All Existing Shapes + for (auto &shape : listShapes) + { + shape->DrawYourself(this); + shape->DrawNodes(this); + } + + // Draw shape currently being defined + if (tempShape != nullptr) + { + tempShape->DrawYourself(this); + tempShape->DrawNodes(this); + } + + // Draw "Snapped" Cursor + WorldToScreen(vCursor, sx, sy); + DrawCircle(sx, sy, 3, olc::YELLOW); + + // Draw Cursor Position + DrawString(10, 10, "X=" + std::to_string(vCursor.x) + ", Y=" + std::to_string(vCursor.x), olc::YELLOW, 2); + return true; + } +}; + + +int main() +{ + Polymorphism demo; + if (demo.Construct(800, 480, 1, 1, false)) + demo.Start(); + return 0; +} + + + +