parent
893b8ad68e
commit
375e33b5e0
@ -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 <string> |
||||
|
||||
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; |
||||
} |
Loading…
Reference in new issue