diff --git a/OneLoneCoder_PanAndZoom.cpp b/OneLoneCoder_PanAndZoom.cpp new file mode 100644 index 0000000..0aef6f9 --- /dev/null +++ b/OneLoneCoder_PanAndZoom.cpp @@ -0,0 +1,249 @@ +/* +OneLoneCoder.com - Programming Panning & Zooming +"Left a bit, bit more, there..." - @Javidx9 + +License +~~~~~~~ +One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; See license for details. +Original works located at: +https://www.github.com/onelonecoder +https://www.onelonecoder.com +https://www.youtube.com/javidx9 +GNU GPLv3 +https://github.com/OneLoneCoder/videos/blob/master/LICENSE + +From Javidx9 :) +~~~~~~~~~~~~~~~ +Hello! Ultimately I don't care what you use this for. It's intended to be +educational, and perhaps to the oddly minded - a little bit of fun. +Please hack this, change it and use it in any way you see fit. You acknowledge +that I am not responsible for anything bad that happens as a result of +your actions. However this code is protected by GNU GPLv3, see the license in the +github repo. This means you must attribute me if you use it. You can view this +license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE +Cheers! + +Background +~~~~~~~~~~ +Used in absolutely everything now, panning and zooming is an important +tool to know how to implement. Even though it appears simple, getting +your head around different spatial transforms can be challenging! + +Author +~~~~~~ +Twitter: @javidx9 +Blog: http://www.onelonecoder.com +Discord: https://discord.gg/WhwHUMV + +Video: +~~~~~~ +https://youtu.be/ZQ8qtAizis4 + +Last Updated: 10/06/2018 +*/ + +#include "olcConsoleGameEngine.h" +#include + +class OneLoneCoder_PanAndZoom : public olcConsoleGameEngine +{ +public: + OneLoneCoder_PanAndZoom() + { + m_sAppName = L"Pan And Zoom"; + } + +private: + + float fOffsetX = 0.0f; + float fOffsetY = 0.0f; + float fScaleX = 1.0f; + float fScaleY = 1.0f; + + float fStartPanX = 0.0f; + float fStartPanY = 0.0f; + + float fSelectedCellX = 0.0f; + float fSelectedCellY = 0.0f; + + +protected: + + virtual bool OnUserCreate() + { + // Initialise offset so 0,0 in world space is middle of the screen + fOffsetX = -ScreenWidth() / 2; + fOffsetY = -ScreenHeight() / 2; + return true; + } + + // Convert coordinates from World Space --> Screen Space + void WorldToScreen(float fWorldX, float fWorldY, int &nScreenX, int &nScreenY) + { + nScreenX = (int)((fWorldX - fOffsetX) * fScaleX); + nScreenY = (int)((fWorldY - fOffsetY) * fScaleY); + } + + // Convert coordinates from Screen Space --> World Space + void ScreenToWorld(int nScreenX, int nScreenY, float &fWorldX, float &fWorldY) + { + fWorldX = ((float)nScreenX / fScaleX) + fOffsetX; + fWorldY = ((float)nScreenY / fScaleY) + fOffsetY; + } + + // Called by olcConsoleGameEngine + virtual bool OnUserUpdate(float fElapsedTime) + { + // Just grab a copy of mouse coordinates for convenience + float fMouseX = (float)GetMouseX(); + float fMouseY = (float)GetMouseY(); + + // For panning, we need to capture the screen location when the user starts + // to pan... + if (GetMouse(2).bPressed) + { + fStartPanX = fMouseX; + fStartPanY = fMouseY; + } + + // ...as the mouse moves, the screen location changes. Convert this screen + // coordinate change into world coordinates to implement the pan. Simples. + if (GetMouse(2).bHeld) + { + fOffsetX -= (fMouseX - fStartPanX) / fScaleX; + fOffsetY -= (fMouseY - fStartPanY) / fScaleY; + + // Start "new" pan for next epoch + fStartPanX = fMouseX; + fStartPanY = fMouseY; + } + + // For zoom, we need to extract the location of the cursor before and after the + // scale is changed. Here we get the cursor and translate into world space... + float fMouseWorldX_BeforeZoom, fMouseWorldY_BeforeZoom; + ScreenToWorld(fMouseX, fMouseY, fMouseWorldX_BeforeZoom, fMouseWorldY_BeforeZoom); + + + // ...change the scale as required... + if (GetKey(L'Q').bHeld) + { + fScaleX *= 1.001f; + fScaleY *= 1.001f; + } + + if (GetKey(L'A').bHeld) + { + fScaleX *= 0.999f; + fScaleY *= 0.999f; + } + + // ...now get the location of the cursor in world space again - It will have changed + // because the scale has changed, but we can offset our world now to fix the zoom + // location in screen space, because we know how much it changed laterally between + // the two spatial scales. Neat huh? ;-) + float fMouseWorldX_AfterZoom, fMouseWorldY_AfterZoom; + ScreenToWorld(fMouseX, fMouseY, fMouseWorldX_AfterZoom, fMouseWorldY_AfterZoom); + fOffsetX += (fMouseWorldX_BeforeZoom - fMouseWorldX_AfterZoom); + fOffsetY += (fMouseWorldY_BeforeZoom - fMouseWorldY_AfterZoom); + + // Clear Screen + Fill(0, 0, ScreenWidth(), ScreenHeight(), PIXEL_SOLID, FG_BLACK); + + + // Clip + float fWorldLeft, fWorldTop, fWorldRight, fWorldBottom; + ScreenToWorld(0, 0, fWorldLeft, fWorldTop); + ScreenToWorld(ScreenWidth(), ScreenHeight(), fWorldRight, fWorldBottom); + + auto function = [](float x) + { + return sinf(x); + }; + + + // Draw Main Axes a 10x10 Unit Grid + // Draw 10 horizontal lines + int nLinesDrawn = 0; + for (float y = 0.0f; y <= 10.0f; y++) + { + if (y >= fWorldTop && y <= fWorldBottom) + { + float sx = 0.0f, sy = y; + float ex = 10.0f, ey = y; + + int pixel_sx, pixel_sy, pixel_ex, pixel_ey; + + WorldToScreen(sx, sy, pixel_sx, pixel_sy); + WorldToScreen(ex, ey, pixel_ex, pixel_ey); + + DrawLine(pixel_sx, pixel_sy, pixel_ex, pixel_ey, PIXEL_SOLID, FG_WHITE); + nLinesDrawn++; + } + } + + // Draw 10 vertical lines + for (float x = 0.0f; x <= 10.0f; x++) + { + if (x >= fWorldLeft && x <= fWorldRight) + { + float sx = x, sy = 0.0f; + float ex = x, ey = 10.0f; + + int pixel_sx, pixel_sy, pixel_ex, pixel_ey; + + WorldToScreen(sx, sy, pixel_sx, pixel_sy); + WorldToScreen(ex, ey, pixel_ex, pixel_ey); + + DrawLine(pixel_sx, pixel_sy, pixel_ex, pixel_ey, PIXEL_SOLID, FG_WHITE); + nLinesDrawn++; + } + } + + // Draw selected cell + + // We can easily determine where the mouse is in world space. In fact we already + // have this frame so just reuse the values + if (GetMouse(1).bReleased) + { + fSelectedCellX = (int)fMouseWorldX_AfterZoom; + fSelectedCellY = (int)fMouseWorldY_AfterZoom; + } + + // Draw selected cell by filling with red circle. Convert cell coords + // into screen space, also scale the radius + int cx, cy, cr; + WorldToScreen(fSelectedCellX + 0.5f, fSelectedCellY + 0.5f, cx, cy); + cr = 0.3f * fScaleX; + FillCircle(cx, cy, cr, PIXEL_SOLID, FG_RED); + DrawString(2, 2, L"Lines Drawn: " + to_wstring(nLinesDrawn)); + + + // Draw Chart + float fWorldPerScreenWidthPixel = (fWorldRight - fWorldLeft) / ScreenWidth(); + float fWorldPerScreenHeightPixel = (fWorldBottom - fWorldTop) / ScreenHeight(); + int px, py, opx = 0, opy = 0; + WorldToScreen(fWorldLeft-fWorldPerScreenWidthPixel, -function((fWorldLeft - fWorldPerScreenWidthPixel) - 5.0f) + 5.0f, opx, opy); + for (float x = fWorldLeft; x < fWorldRight; x+=fWorldPerScreenWidthPixel) + { + float y = -function(x - 5.0f) + 5.0f; + WorldToScreen(x, y, px, py); + DrawLine(opx, opy, px, py, PIXEL_SOLID, FG_GREEN); + opx = px; + opy = py; + } + + return true; + } +}; + + +int main() +{ + OneLoneCoder_PanAndZoom demo; + demo.ConstructConsole(160, 100, 8, 8); + demo.Start(); + return 0; +} \ No newline at end of file