Programming Panning & Zooming

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello it's just too hot to be programming indoors today so I've come outside into my garden anyway in this video we're going to be looking at something that's become so ubiquitous you probably don't even realize that it's there I'm going to be looking at panning and zooming to demonstrate panning and zooming I've created a small application in the COTS or game engine and it displays a grid at different spatial scales I can use my middle mouse button to pick up the grid and move it around relative to work iver position the mouse cursor and this is quite important because it doesn't matter where I collect the cursor location the grid moves relative to wordly cursor is on the screen and this gives the really compelling effect that I have indeed picked up the screen space and I'm moving it around with my hand it's a very tactile user interface tactic I've also added the facility to zoom in and out and we can see here now that the grid zooms a little bit strangely because of the very low resolution I'm using in this application so you see the cells aren't quite the same size but that that's irrelevant here what we do see is that the grid grows and shrinks but most importantly it depends where the mouse cursor is as to where the focus of the zoom point is you can see I went to the top left there and I'm moving back out and if I go somewhere down here in the middle just move the cursor out so you can see it on the video and it's always ensures that the point you're zooming into remains in the same location this is to avoid the user getting lost it also doesn't matter how far zoomed in we are when we start doing pan operations the same strategy still applies and in fact once you have established a suitable strategy for world space 2 screen space transformation we can do everything through that transformation so I can even select cells in the grid by clicking on them and it doesn't really matter what the spatial scale or position of the grid is and I can also move the mouse cursor whilst and zooming to get a slight rotation effect there so let's have a quick look at what's going on and we'll start with the easier one panning we've actually done panning several times before in previous videos for example the RPG series as the screen panned around and the Jerry Oh platformer game series and both of those relied on a simple concept that we have a screen space where our top left is zero zero and we have some world space that represents the space we want to draw on the screen I'll just draw that in green here and the reason I'm drawing it off-center like this is to make a point but we can assume that the world space had a one-to-one relationship with the screen so top left of the world space was up here and we also knew that to be zero zero however to pan this around we needed to store two offsets one which was here we're sure it was offset X and one here offset Y and so if we take a point somewhere in world space let's say in this instance it's 10 5 and we wanted to draw that in the corresponding screen space which let's say for argument's sake is about here then the translation was quite simple we simply subtract the offsets from world space to screen space so we go in the X direction and then again in the Y direction therefore without any scaling we can get some very simple equations that the screen space X location is equal to the world X location - some offset X and of course exactly the same goes for the y axis and we'll see this a lot there's a all these things happen to both axes simultaneously and with some very simple algebra we can work out the transformation between world space and screen space so here we've got world x equals screen x plus the offset simple stuff we're going to be using these transformations so much that I really recommend that we have a world to screen function and a screen to world function let's start writing some code to see if we can implement panning I'm going to start with an empty console game engine project and this time I'm going to construct a console which is a 160 characters wide by a hundred characters high and each character is 8 by 8 pixels so it's reasonably low resolution and in the on user update function I'm going to draw a grid but the first thing I'll do is clear the screen to black if I want to draw a grid of 10 by 10 cells I need to draw 11 vertical lines and 11 horizontal lines I'll do this with a for loop but this for loop you may notice goes to Y is less than or equal to 10 so this effectively gives us 11 iterations through the for loop each cell in my grid is going to be one unit across by one unit high and because I want to keep this code as an example for others to easily follow and implement in their application I'm going to be quite verbose with how I name the variables and I'm leaving out a lot of optimizations so for my line I want to create a start point and an end point designated by the SX and sy and e ex and ey values here and typically what I would do is call the draw line function to draw a line between these coordinates however we need to factor in our offset so we'll come back to this in a little while somewhere in our system we need to store the transformation variables so I'll have two floating point values F offset X and F offset Y I'll initialize those to 0 to begin with and now I'll add my two transformation functions so this is world 2 screen and a corresponding screen to world so let's look at the arguments I'm going to pass in a coordinate in world space X&Y these will be floating point but I'm going to return via the arguments the corresponding screen space coordinate and these are integers because our screen is discretized now we don't have any scaling at the moment so what I'm going to do is just handle the panning so we'll take our screen coordinate and we'll need to cast that to an integer because the operation is going to be performed in floating-point domain and I'm just going to implement the equations that we saw before if I can type there we go and of course we don't want to both of these to be ex we won't want these to be Y in the same way let's handle the screen to world transformation this time we'll need to cast or float our end screen variables let's go back to where we were drawing the grid now what we can see is that the coordinates here are in world space each one of my cells is one by one I'm going to need to convert these to screen space so I'm going to create an additional four variables which are the screen space equivalent of the world space variables and then I'm going to call my world two screen function now all draw routines will only occur in screen space so it's important that we use only the new variables to do this and I'm going to draw the line as a solid white line this code is handled 10 horizontal lines let's take the same code and repeat it this isn't optimal we could do everything in the same loop but I'm trying to keep it as clear as possible and we'll use this loop to draw ten vertical lines as you can see the rest of the function remains unchanged let's take a look well we see a rather boring white square at the top left of our screen well that's kind of what we expect because this square is 10 by 10 pixels because us grid was in unitary cell size remember and our offset is currently 0 so our world space is a direct mapping 1 to 1 of our screen space in on user create I'm going to default our offset variables to point to the middle of the screen so I'm going to take the screen width and screen height and divide it by 2 but I need to invert this number because the offset is negative because if we study our world two screen function we're going to be subtracting this negative and adding it let's take a look now so now we can see that the top left of our world space is mapped to the middle of our screen space let's push the drawing routines to the bottom of our own user update function and given this is going to be a heavily mouse-driven application I'm going to capture the current mouse coordinates at the start of the routine of course the mouse operates in screen space so whenever the user clicks with the mouse draw a crude mouse cursor in here we need world space to be transformed around where the mouse has selected because we want world space to always remain relative to where the mouse has been pressed and to do this we need to store some additional offset information from the top left of our world space to the current mouse position in world space so we need to store this offset too I'm going to call this start pan X and start pattern Y and I only want to update these variables when the user presses the mouse button so I'll add an if statement that checks to see has my left mouse button been pressed if it has it captures the current screen space mouse coordinates and stores them in start panics and start pan Y as the user drags the mouse we want to update our offset positions to move the world around so I'll respond to that on the be held flag as the user drags the mouse around the screen the mouse coordinates may change but the start pan position doesn't therefore we want to set our offset by the difference between the mouse coordinates and the start pan coordinate I also need to now update my start pan location so in between frames it's as if start pan has been clicked each time I should point out that it doesn't matter if you're using an event-driven framework the concept is exactly the same you just respond to your on click and on left up and on left down or appropriate events so let's take a look now we've got a rectangle in the screen and it doesn't matter where I select with the mouse cursor the world is translated into the screen relative to my mouse cursor so let's try a really odd one down here there we go and of course let's try right in the middle of the rectangle very nice and we should also handle something in the negative domain as well so we know in our world space the current screen cursor will be in negative world space and that works just fine to put that back in the middle for next time and that's all there is to panning it's pretty simple isn't it however zooming is a little bit more complicated zooming requires that we somehow scale the world space as part of our world to screen and screen to world transforms so I'm going to store two additional variables scale X and scale Y and by default I'm going to set them to one so that maintains our direct mapping between world and screen space now to my shame I have no mechanism for capturing the mouse wheel as part of the console game engine I certainly will add that in the future but typically you would want to use the pressing of the mouse wheel as part of the panning operation and the scrolling of the mouse wheel as part of the zooming operation I'm going to emulate this with the Q and a keys being held down so I want to zoom in ie make things larger when I hold down the Q key and all I'm doing here is increasing the scale value by 0.1 percent and on the contrary if I want to zoom out ie make things smaller I need to decrease my scale value also by one percent different policies for changing the scale variables can be implemented here in this instance this is a very smooth scaling effect you might want to scale in discrete steps but the downside to this approach is it is effectively linear so as you start to zoom in you need to zoom in a lot more to see any difference on the screen space but don't let that put you off trying this method this gives a very nice feeling zoom now as I've already mentioned scaling is simply adding multiplication of the scaling value somewhere in our transform and so when we're going from world to screen once we've transformed the offset we just want to multiply by our scale value scale X and F scale Y and if we do the algebra that means in our screen to world transform we want to divide by F scale X here an F scale Y if you're just starting out on your programming adventure you always want to be careful when you see divides like this because you might hit a divide by zero error however we've ensured that that can't happen with the way that we're handling the zoom value this can never be zero unless you're one of those people that would like to start arguing about the properties of floating-point numbers so let's take a look here's the grid and if I press the Q key to zoom in or we see the grid sort of shoots off to the bottom right and if i zoom out it gets smaller enlarges so I'm confident that the scaling is actually working but the position of where the scale is occurring around doesn't seem right and that's because our scale is completely centered around zero zero and so the offset from will to screen space is also scaled this isn't quite right and we can demonstrate this to be the case if I push our world space to be right up in the top left of our screen space this will be approximate and I choose to zoom in we see it zooms in reasonably appropriately but as soon as I start to pan it all starts to fall apart it becomes very difficult for the user to use instead I want the world space to scale around where the current mouse cursor is in world space so here the black line outlines my screen space and the green line implements my world space before I have done a zoom if I want to zoom around this location so that's where my mouse cursor is and I choose to zoom in the effect that I want is that this point remains the same unfortunately we already have the facilities to handle all of this because if I know where my mouse coordinate is in world space before I zoom it's here after the scaling my mouse coordinate has also been zoomed and if we assume that scaling is happening around zero zero which it currently is so my zoom has actually happened here so my new mouse ordinate after the zoom is about here well if I look at the difference between the presumed mouse coordinates and the post zoomed mouse coordinates I can create a vector which I can use to offset the new world space because this vector will be the same as the vector that we need to displace the world so before we do any zooming I'm going to capture the mouse position in world space and this is very simply done because we have the screen to world function after the zoom I want to do the same thing but don't forget the scale has changed so the internals of the screen to world function has also changed and once I have the before zoom coordinate and the after zoom coordinate I can subtract them and update the offset accordingly to displace the world around the mouse cursor so let's take a look well start to zoom in now oh it seems to be a bit jittery and all over the place that said it's not going completely out of control it still doesn't feel quite right why might this be well the answer lies here and it's where we create the offset for the mouse this currently only ever exists in screen space we're not doing any scaling here so I'm going to divide by our scale values - and you might think well why was that necessary well if you look at this this is translating mouse coordinates differences into a world space offset and so that's a screen to world transform slightly different in this context but we can see in our screen - world transform we're also dividing by the scale value - let's try again hmmm well it's still a bit jittery but at least now the zooming is actually happening around the mouse cursor and if I start to pan the scene that works equally well - so what's going on with this horrible jitter and it doesn't take long for me to discover the problem it's that I clearly don't know how to program at all here I'm casting this floating point calculation to an integer before multiplying it by our scaling value I've got brackets in the wrong place I need to enclose all of this calculation and now you can see the zoom is very smooth indeed Schoolboy area though never mind well that was all quite embarrassing but whenever you see sort of jittering like this that's usually a solid indication that there is an integer to float or the other way around conversion not happening quite as you'd expect it to now that we have working panning and zooming we can start to do other operations using our will to screen and screen to world transforms so the first one might be to actually select something in world space I'm going to add the two variables selected cell X and selected cell Y and at that location in world space I'm going to draw a red circle so after I've drawn the grid I'm going to transform the selected cell in world space to screen space don't forget my cells were one by one so if I want the circle to appear in the middle of the grid I have to add point five to it in this instance I also need to scale the radius of the circle and I'm just going to use the X scaling factor to do this throughout this program we've kept scale X and scale Y independent and this would allow later on to scale independently in those axes if you've got a plotting program for example that might be quite a useful thing but in this case I know that my scale X and scale y always change as a pair so I can take one of them they always assume the same value and now that I have my transformed circle coordinates I can simply draw a circle in screen space to work out which cell the user has selected I need to take my mouse coordinate and translate it to world space now I already have grabbing the mouse coordinates is the first thing that I do in my program and I could probably get away with using that but the most accurate Mouse coordinate is now the mouse coordinate after any zooming has occurred and so I'm going to set my selected cell coordinate based on my transformed mouse world coordinate after any zooming has occurred I'm going to do this on a right-click let's take a look so I'll just zoom in a little bit here and we can see in the top left we have a circle crudely drawn again low resolution in the cell zero zero and if I right-click somewhere else on the grid we can see it's working quite nicely and of course I can pick it up and pan it and still select and it selects the right cells it doesn't matter what the zoom level is either the transformation is fine you can imagine this having lots of applications in level editors image editors and I think all sorts of different 2d applications that's why this is such a useful tool to have in your programming toolbox there are also a number of other optimizations we can exploit now now that we have a mapping between world space and screen space why draw parts of the world that you can't see how can we clip it well I could very simply create four variables which represent the left top right and bottom of the world in screen space and I use the screen to world function to take my top left coordinates and put them in world space and I can do the same for the bottom right by using the size of the console and I can use these values to decide what I draw to the screen or not I mean after all why draw things that you can't see so to demonstrate this I'm going to keep a track of how many lines that I'm drawing with the lines drawn variable if a line gets drawn I'm going to increment that variable and what we do for white will also do for X and because we've chosen a low resolution means I can easily draw strings and we can read them at the same time let's take a look so currently all the lines of the grid are drawn that's all 22 of them as we start to zoom in we can see our lines drawn count is starting to decrease significantly let's get it down to just the one there we go so we can optimize our rendering framework very nicely in fact you could take this further at the moment it's still testing all of the lines that's not necessary either why not only start drawing from a location which you know can be seen you don't even need to do the tests at all then and finally and very much just for show enough I'll let you study this on your own time we can also plot a graph so let's make a lambda function which always returns the sine f function the reason I'm doing this is we can easily change the nature of this function and explore it in different ways now this looks a little complicated but I'll try and explain what's going on I'm going to scroll across my entire screen so I'm looking for all of the visible pixels from left to right in world space and in order to do that I need to know what my world space per screen pixel is because I don't want to draw things unnecessarily but I want to draw things at high resolution as we zoom in so calculating this screen width per pixel is not that difficult I just take the world right and world left values that I calculated earlier and divide it by the screen width that tells me how much world space is attributed to each pixel I'm going to store four integer coordinates which are my pixels to be drawn which is the current pixel and the previous pixel because I don't want gaps in my function either I wanted to draw a line between successive values and this line here is just the very first value to draw so it doesn't draw from zero zero each time on the function it draws on the first visible valid location along the x axis on the screen so I'm going to loop from the left hand side of the screen in world space to the right hand side of the screen in world space that's my x axis and for each valid location I'm going to calculate the function in this case it's up here in this lambda function it's sign and I'm going to offset it accordingly in world space so my grid is effectively ten by ten I want to locate it to the middle of the grid and don't forget when we're rendering on a computer screen positive Y tends to be going down the screen this is contradictory to mathematics as we know it so I'm going to throw in a minus sign so the sine function is the right way up for each world space pixel I'm going to calculate the screen space equivalent and I'm going to draw a line so let's take a look and we can see here we've got something that looks crudely like a sine wave but I need to zoom in but it draws from the left to the right of the screen at all times the nice thing is it doesn't draw beyond those locations and I can zoom in and zoom in and zoom in and the function never loses any accuracy and I can also pan so a little bit of a rush at the end there but it shows you just how sophisticated having these very simple transforms can be and so that's that a very simple yet powerful programming tool to add to your programmers toolbox if you've enjoyed this video give me a big thumbs up have a think about subscribing all of this source code is available on github to download and play with you should come along to the discord server and have a chat growing everyday and take care I'll see you next time
Info
Channel: javidx9
Views: 35,193
Rating: undefined out of 5
Keywords: one lone coder, onelonecoder, learning, programming, tutorial, c++, beginner, olcconsolegameengine, command prompt, ascii, game, game engine, panning, zooming, pan, zoom, transforms, scaling
Id: ZQ8qtAizis4
Channel Id: undefined
Length: 25min 6sec (1506 seconds)
Published: Sun Jun 10 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.