Camera Controllers | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is the Chennai welcome back to my dimension series last time we talked about something really exciting how to build a 2d renderer check out that video if you haven't already will be linked up there we've just begun this amazing journey it's gonna be so much fun but what I thought that we would do is kind of take a look at the quarry button now and it's true about all the good parts from it specifically the stuff in sandbox obviously what I'm talking about not just the general engine because most a bit of course is good code but specifically the stuff that's in sandbox we want to strip that out maybe put some of it into classes and we kind of want to eventually have a clean slate from which we can start preparing like a 2d sandbox and then that's gonna kind of be where we test everything and could even be the place where we actually make a 2-d game from the stuff that we actually build up in this series so today we're going to be talking about camera controllers nothing at the moment what we've got to do with specifically like with cameras is we just have a bunch of stuff in our on update function and I think that's about it and that's just the stuff that controls a camera and we've got this all the graphic camera class which contains a projection matrix that we make an orthographic projection matrix and then we we just have some loose kind of code around that to actually set the position and rotation that's fine but I don't want to be manipulating the camera in the kind of in this kind of example layer class that's obviously very temporary what I want to do is actually create an orthographic camera controller in the engine code base that you can use to actually control an orthographic camera and this isn't necessarily something that you would use in a game because most likely it's not going to behave exactly like you want a camera to behave like if you're making like a 2d platformer or a top-down game you probably want the camera to behave in a certain way and it's very likely that you know a camera is something that is so much kind of client code so much like game side code because it's the behavior of it is so intrinsic to the actual game that you're building that in real games you know you would build a camera as like as a Lua script or as some kind of script there's a part of like your actual like it like an entity in your sin that would be the camera you wouldn't just kind of use a camera that the engine comes with however when you're first starting out and you want a basic kind of like what unity would have in their level ever for 2d or for 3d you know you need that an engine should be able to provide that for you so that you don't have to be like okay cool I've started a new project in hazel I'm gonna have to just do a bunch of camera scripting before I can even see anything on the screen like that's not that's not something that we want so we need to kind of bring that code out we're gonna add stuff like it's like zooming in and out and a bunch of other stuff probably to the actual camera class well we'll create a new kind of camera controller class and we'll talk about that just so that we don't have to deal with all this code inside sandbox before we begin just want to thank all the patrons who made this series possible patriarchal force at the Turner is the best way to support this series and everything that I do here on YouTube you'll get a lot of really cool stuff in the Hazelden branch I actually pushed like three three commits like I think today each of that developing branch that was to do with Trudy stuff I'm going to actually merge a bunch of the Trudy stuff that I've done ahead of time into that hazel development branch because of course in the future 2d and 3d are gonna have to play nice so you guys want to jump ahead in both 2d and 3d and see what hazel is capable of and what we're building towards then helps both series and you will get access to that card okay I'm not gonna do too much talking we're actually gonna like jump in and do some code here because there's not too much to talk about we're basically going to make a class called orthographic camera controller that's going to be it's basically going to be a wrapper around a camera an orthographic camera which is a class what we already have and it's gonna add some extra stuff to actually control that it's gonna have like an update function where we can actually process key events and it's also gonna have an event and that'll be able to handle stuff like window resizing as well as you know Mouse scrolling and stuff like that so that we can zoom in now so let's jump in and take a look at the code okay so scrolling through this code we have a bunch of stuff inside our on update function this is sandbox app dot CPP you can see this is all inside example layout this is um and also this file also has our sandbox application this was kind of the first thing we started off with in hazel and it's great and it's a nice little sandbox because you want to have sandbox just to run random coding but we also want to actually have a whoops look we also want to have like an actual 2d sandbox that we can play around in so that as we're developing our 2d renderer or we can have like a scene setup or something like that and we can actually have an example of how to make a 2-d game without you know all of this kind of boilerplate code that we don't particularly need so that's what we're gonna do today we're gonna start cleaning stuff and in the future in the next few episodes I'll probably will have like a I probably will have like a dedicated maintenance episode to just clean up a bunch of stuff because obviously we kind of accumulate more and more tech debt there longer we write code and I'm kind of putting off fixing all of that episode 2 episode just because I want to get these concepts across and kind of be more effective I guess in my teaching style rather than actually cleaning up irrelevant code like as I come across it which is well it was something I would do in the real world if I was actually programming and I'm kind of going through this code and I see something that I don't like I might fix it just immediately then and there that's kind of a good way to go but with this of course I don't want to randomly be distracting myself being like oh I actually you know what we should fix this so I will have a dedicated maintenance maintenance episode and maybe maybe not the next steps over the tips but the episode after there just a fair warning I'm sure expecting something exciting then there's gonna be a missions episode soon all right cameras so let's take a look at this orthographic camera class so it's really simple as you can see it's really just a bunch of matrices and then some very very basic code around that everything has to be set inside this actual class I mean it's got its own it does keep track of its own position and rotation it's not literally just a projection and a view matrix and then that way you know you have to set everything manually from the kind of outside of this class nor it does have that stuff and that's right but we want to actually take it a step further and add input to this class now I don't want to do this right this is just data it's pure data is pure maths we don't want this to have anything to do with input we want to tie it like that at all because what this is and what does this represent and in fact I'd probably be inclined to even get rid position and all that stuff from this class because what this should be is a representation of an orthographic camera which is really just a representation of a projection matrix and a view matrix these two right all together a view projection matrix this is what it is and this is why it exists we want to send this data to the renderer that is all that this is anything extra is stuff that we add to make controlling this data a little bit easier but ultimately we need this to exist because we need to be able to get this data to our shader so that we can multiply that these matrices with our vertex positions and with our actual object transforms EEPROM if need be to actually position vertices on our screen inside our display area inside our rendering area correctly that's what this is for so we don't want to start adding extra stuff to this because the more we add to this class the the bigger it will be the more data it will contain the more bandwidth is gonna occupy on in our render submission queue when we actually get around to that there's a lot of implications that we don't want to deal with and that we don't have to deal with if we just kind of make an extra class that goes on top of this class basically and then that will be our controller class because obviously you know we don't need to submit we do need to submit an orthographic camera to the renderer but the controller should go nowhere near the renderer because it's just a it's just a user interaction a little module that basically could manipulate this data so hopefully that makes sense that's kind of what we're building and so because of that what I could do is just make another class right here call it like orthographic camera controller ortho camera controller but I like to be pretty specific with my class names we could do this but I don't want to do this in this file the reason being that we need a bunch of stuff for this we need events we need input we need basically a whole bunch of stuff that will basically have to be included but I don't want to do that stuff here because again we're trying to keep this kind of lightweight and I don't want to feel this this file with input so what we'll do instead I'll just copy this what we'll do instead is make a new file I'm alongside of this file so and we'll just call it so in the renderer we'll make a file and we might move this out in the future because again it's not really part of the round right in fact I don't really want to put it there now we're just gonna dump it in hazel in the mentions episode we'll go through some of these things and we'll move them to core or we'll move them to any anything else that where they might be long because yes this is getting cluttered and hazel dev is not it doesn't look like this at all so um something to take note of but anyway I'll put it in here for now I don't wanna I don't really want it to be part of the renderer because it's really not part of the renderer and then what we'll do here is make name space hazel will make that class called orthographic camera controller and then what that will be is basically as I said a wrapper around the orthographic camera so we'll have that autographic camera and we'll have a whole bunch of other things here as well so when we create an orthographic camera controller the way this is gonna work is basically going to be a viewport into our actual view that we have here so really the only thing we need for that is to turn is to determine what kind of projection like I'll say units we want to deal with so whether we wanted to be per pixel or something a little bit more abstract I'm gonna go with something a bit more abstract I don't want this to be 3rd pixel per pixel meaning we would literally set left and right to zero and like 1280 respectively so that when we use one unit in this camera it's one pixel I'm not gonna do anything like that because we don't really need to and it's gonna make other things harder what I'm gonna end up doing is basically just setting this to by default probably have a zoom level of one and if we have a zoom level of one that's basically going to be what our y-axis is going to be like so vertically by default when this camera starts up I want there to be two units of space no matter the what the screen resolution is really and then horizontally will basically have to multiply our aspect ratio and by those two units so that we get the correct aspect ratio horizontally and then by zooming in and out we'll simply adjust this accordingly so that basically if we zoom in we obviously widen the area sorry if we zoom out we widen the area so the more as interview and if we zoom we're effectively just increasing or decreasing the space that we have in this actual projection matrix and the orthographic projection matrix so that we basically cover less area and thus everything will be bigger so you will kind of see this in action and it will make more sense I think so what we'll do is we'll actually take in a width and height now this is just gonna be used to calculate an aspect ratio we could even just take in an aspect ratio as a single flirt with an height yeah in fact I think we will do that just because this might be a little bit confusing because ultimately it's not gonna matter if we put in 16:9 literally 16 and 9 for width and height or 1280 720 it's ultimately going to be the same that might confuse people wait it's gonna take in an esper an aspect ratio okay so we'll store that aspect ratio and then the other thing I want to store is an actual zoom level so our zoom level will set to one to begin with note that I'm creating the stuff before the orthographic camera that's important because we're gonna use this stuff to initialize the orthographic camera and the inner fly say the order of initialization is the order that in which these variables are actually these moment variables are declared in so make sure that you don't try and do anything like this because if you try and do that even if you do initialize a person in the constructor this will be initialized before these which means that this will be uninitialized memory because they've just simply because they haven't been initialized yet so make sure that you initialize these two first okay cool so we'll have that other things we need is an on update function with a time step of course this is the this is what I mean like you know we need to include time step now not something that orthographic camera needs at all because it's just a bunch of data it's just a bunch of math why not put it needed time step so we'll just do renderer slash orthographic camera and then we'll include our time step will also include events so we'll do events slash we are in yeah I'll just do it hazel / I really don't like doing because we won't move this folder in the future so I really don't like doing relative parts because everything since we are here we could just do render a slash event / but it's better to do hazel slash everything that we kind of need because that way if we if and when we move this file everything will still work and we won't have to refactor everything okay so hazel events events dot H will need two specific types of events in cycle but we don't need this here at all application event and mouse events so mouse for like scrolling and application for the window resize that I talked about okay so avoid on update void on events event E and then private functions will have is this stuff that I mentioned for scrolling so this will be bull on Mouse scrolled let's take a scroll event E and then on window resized this will be a window resize event okay cool so we've got these events now that's great I'm just trying to think I think that's I mean we'll probably want other stuff as well but for now that should be enough to get us started so if we pop over to the CP file I will include the precompiled header and then I will also include orthographic camera of course diet orthographic camera by name space hazel and now we can just use a visual assist to create everything for us okay cool sir let's take a look at what we currently had and sorry this is kind of controller and we can basically copy if there's a whole bunch of code from here so if we look at what we had here this was our on update function as far as the cameras and then you'll see we also did a set position and set rotation so what we'll do is move that to on update we still need this stuff to be in the on update function what we don't need is well the mouse scrolling and window resizing and going to be event but this stuff still needs to be as actual like an update kind of loop things otherwise it won't be smooth the scrolling doesn't have to be smooth because our mouse wheel is not smooth in increments usually so because of that it's fine to use a like an event for it but otherwise you could also implement the mouse scrolling for example in here you could implement window resize in here you don't need to do them as events but we will because that's a little bit better okay cool sir will do input for all this stuff we are inside Hazel's so we don't need to do the Havel namespace at all I'll just get rid of this stuff I'm actually gonna change the keys that we chose here the reason being that you know left-right up-down I'm that might be more of like a character controller thing at the moment the way that my hand rests obviously it would be better to use WASD for the camera just because we wouldn't have to deal with moving my hand all the way over to the right side of the keyboard to access those arrow keys I think everyone would probably prefer WASD so we'll just change all these to WASD and then for rotation which I will make optional by the way I'll just make Q and E rotation and in fact what I'll do is I'll add a rotation bullying over here to the constructor so this will be false by default because I don't think camera rotation is that useful for 2d but if you want you you know you'll be able to turn it on and use it so we'll kind of leave it up to whoever press this class to decide so we need to analyze that yeah ok cool and then we'll set a rotation here so let's talk about our constructor we need to basically create a camera so the camera is going to be basically the left side of the camera is going to be the aspect ratio on the negative aspect ratio multiplied with the current level which will be won by default source of literally just negative aspect ratio and then the right side will be the same but positive and then the bottom will be basically the same so negative zoom level right the same without the aspect ratio is what I mean and then zoom level will be the top side so basically this is effectively negative one to one in the vertical axis and then negative well whatever aspect ratio is which is probably one point seven eight to positive one negative one point seven eight to positive one point seven eight so those are kind of the units we're dealing with and then we'll also set up the rotation so AM rotation and then rotation okay sorry this stuff we will only do if rotation is actually enabled otherwise we don't have to worry about that at all camera rotation our variables we will probably need truck as well as the speed so what I'll do is will include input first of all so I think that's in Haysville slash input is it yep okay so input dot H and then the key cards as well so say stuff like this this is exactly why I wanted to make a separate file because there's way too much stuff to deal with okay coming back into here let's think about what we need so we need two camera rotation we in the counter position camera rotation is just going to be a float which will set equal to zero by default and then camera position will be a GLM effect three and camera position and we'll set that equal to 0 as 0 is 0 by default iam making sure to actually initialize at this time okay roof speed and rotation speed I will also kind of move over here so we will have you know mouths of babes essentially slow flirts and will say camera translate speed equals 1 maybe we'll figure this out in a minute and then camera rotation speed equals 1 as well we'll see what they like so I'll just react to this to be a camera translation speed you may want this translation speed to actually be a function based on the current zoom level so you might want to translate faster if you're zoomed further away and then slower if you zoomed in but that might be something that we do in the future ok cool on event so we're basically to steal some existing events that we've got I'm sure we've got them somewhere I mean we don't necessarily need to there isn't I say that is just easier to copy and paste this stuff but we have this hazel bind event function macro that we can use which is really suited like easy to use so we'll use the event dispatcher will basically give it the event and then will dispatch based on what we deal with so we want to obviously handle the mouse scroll event so mouse scroll event and then we just bind an event function to that and then I think because I have not used this in a while we literally just give it the function so it's just gonna be author graphic camera controller on mouse world so this function here and that should be pretty much it I think now we should have mouse scrolled event already included here so that should be fine hopefully that compiles and then we'll do let's like just putting miss something with the resize event which is our type and then orthographic and camera controller on window resize like that that should be pretty good I think that's it it doesn't look like it wants to compile that maybe just be because now this is inside event let's see if this got changed actually to be out of it to be not a standard function that was a little quest that we accepted yeah let's just hit f7 so again see this is related much to be intellisense in the meantime we'll just return false here as well so that we don't get that so dispatches on remember I am okay so I just made a stupid mistake of course this has to be the statue dispatch okay cool that looks better so that's basically how we dispatch our events now we need to figure out what to do so mouse crawled event gives us a birth of horizontal and a vertical scroll so we'll use Y offset what we'll do is apply basically there's change the zoom level by that offset so we'll just say zoom level minus equals get Y offset and we'll see what that gives us and then we have to recreate the camera so instead of recreating the whole camera I'm gonna have a function called avoid set projection this will basically do what the constructor did so we'll grab this stuff its code put it in here copy this I'll probably put it we'll make it the first thing just because it's it's pretty important and also it basically does what the constructor does so we'll grab this card put it here there's a nice little live carding session because I had spent a while I think since I've typed it this much card and live as well sir let's see so over here we basically have to recreate Oh what am i doing this is completely wrong sorry this is orthographic oh did you see that I went into didn't I hit alt or yeah that's weird anyway I somehow gone to the wrong sitting people never mind I was gonna say there's a lot of code in that class and here we are trying to keep it simple so orthographic camera set projections so we're gonna copy this and paste it here so this is now going to be just setting up that projection matrix and now obviously we need to reset that view projection matrix and that's it you could change the constructor to do this but doing so not as ideal I guess because then we'd be initializing projection matrix - it's kind of default value first and then overriding it which is not something I want to do really so we'll keep this separated like this but I think that's it we should need to touch the view matrix at all because there should be already set to whatever needs to be set - okay cool so now that we've got that we can actually reset so we'll probably have to actually set a minimum for this room level but we'll worry about that in a minute we'll say camera what is a camera dot set a projection and then we're basically going to set it to the same thing that it was set to in the constructor so we'll grab this all the way over here now the zoom level has changed of course they will recompute everything so basically the idea is mouse scroll is changing the zoom level whereas window resize is changing the aspect ratio so we're basically multiplying those two values so in the window resize aspect ratio is going to equal Y dot get a width divided by e dot get high and these I think are integers so we need to make sure that we actually cost them to four otherwise we will get the zero probably or one or something like that okay cool so there we go regretting everything that should be a cab not actually sure hazel if this branch of hazel supports window resizing so that will be fun to see if not we'll have an episode on that soon okay that's looking pretty good to me actually so we need to set the camera position we're not doing that so a camera position sorry I'm camera I'm camera adults set position to em camera position and then if we do if we cover tation enabled I guess we'll set rotations our camera don't set rotation to em camera rotation all right so that's it this is our camera controller you can see that basically all it is is just a way to set all the different values we have inside our camera class now if we go back to sandbox app we can strip the rest of this card out so we don't need anything to do with camera at all in fact we can just remove the camera if I scroll down over here I will change orthographic camera to orthographic camera control oh and this make I'll just pull it em camera controller this will be something that I guess we will need to include inside hazel so we have all the graphic camera or that's part of the renderer whereas I guess what I'll do is maybe over here will include just the orthographic camera controller okay so now we have this set up this should be okay let's see where else we got rid of camera so begin scene it still needs a camera of course so we'll do camera control if I can get camera and I will have that function so get back to orthographic camera we will sorry camera controller which doesn't show up yeah great okay here we are now we'll just do a orthographic camera reference get camera first mm camera and then I guess I'll also provide a constant version just in case we need it okay cool so we have a constant and on conservation of get camera all right so that should be it this is still sad namespace as much as you try and build this and see what happens if it still doesn't work um okay so there's no such file or directory okay I don't have show old file selected that's probably why so if I come back here you can see I accidentally out of it make sure you use this show all files thing in Visual Studio because if you don't it will just make stuff at the root of your project directory so if I actually put this into hazel now and everything else by the way compiled because we used absolute parts not relative part so in other words we use relative paths through the actual hazel directory which was a compiler include directory so everything like yeah but of course an exam box nothing worked so we'll just we compile that code okay so no appropriate default constructor for camera control okay yep so this is gone now and we need to initialize camera controller instead and camera controller so this is just the aspect ratio which again you could just kind of we know the way we're dealing with like a 16:9 thing we're making our window is at 1280 by 720 so what you could do is literally just work it out by doing something like this really simple you could fit in with / height here you could put in one point seven seven seven eight or whatever you want to do here if you know that a 16-9 but we'll just keep it simple 1280 by 720 that is what our width and height years so we'll keep we'll set that as our aspect ratio I think that's it let's camera position and all that stuff can also go so we'll kind of clean this class up a little bit convocation speed so what did we order just be politically 180 okay I said it's like one is that should be fun and moves people's five so will kind of remember that just in case we need to set our camera values appropriately if I just run this program now that we know that it could piles hopefully we'll get something actually rendering on the screen and maybe we'll even be able to move our camera and do all that fun stuff okay so we get a blank screen and I yeah we just don't see anything at all so let's take a look at why that might be happening oh well actually I just realized I'm not calling any of the functions we made at all so obviously it's not enough to just make a camera control you actually have to hook into the methods that you need so basically on update we should be doing camera controller on update with the time step so this will kind of be our update and this will kind of be our render um they're combined into one function because there really is no render function on the game thread on the kind of application thread what we do is something called a render submission which does happen on update and then the render thread will actually evaluate that render queue in the future so I think I had a question about this a while ago yes there's deliberately just an update because in any implementation if you had on Renta it would just be called like immediately after an update or whatever there's no point dividing it into two functions and just you know making the code base more complicated than it needs to be okay cool so there's the arm plate and then we also need on event so over here is to camera controller dot on an event I'm and I'll actually just call it because some weird syntax highlighting occurs because I think well event is a c-sharp keyword but apparently it's sometimes get used in C++ as well not really sure but visuals Julia likes highlighting it anyway okay cool still nothing I mean we should be seeing it we should be seeing stuff even if we didn't have this update stuff so that's not that far-fetched so let's see what happens here so camera controller we set to 1280 divided by 720 which is of course that aspect ratio rotation is false by default we set camera to negative aspect ratio times zoom level which I'm sure is 1 ok aspect ratio gets initialized first does it no it doesn't aspect ratio doesn't get initialized at all so aspect ratio should be set to aspect ratio like that in fact we probably don't need aspect ratio we do it here we're still storing it but that's fine just make sure that um make sure that you're actually initializing it let's hit f5 again another way to debug that apart from just reading the code what binges put a breakpoint in and see that aspect ratio wouldn't have been a proper value okay so don't you ASD moves everything mouse wheel as you can see scroll everything to the point where we go upside down which is fine and then Q&A do nothing embark if I recreate this with rotation set to true by just adding another parameter over here in our camera controller initializer then acuity should in fact rotate our views let's take a look at that once it compiles and launches and here we go ok CUNY do in fact work they're very slow as predicted because it did used to be 180 that used to be the speed mount one so maybe let's set the speed to 180 will be great to have a an actual getter and setter for that but we can add that as needed by the way that is our attention speed here translation speed like yeah like five maybe not sure as I said it's probably nice to have it as a function and in fact I might make an episode just talking about this stuff because you could have you could have like a curve to that rotation based on the zoom level starting up to the rotation but to the actual translation speed based on the zoom level so you wouldn't have to deal with that also the zooms the zoom field a little bit I'll actually multiply that by five point five a little bit too quick I think so I just multiply that by 0.5 we also want to make sure that our zoom level is never less than so basically what does Clampett we'll say that if the zoom level just like whichever is max so we'll do MCM level or 0.25 so basically we weren't go below 0.25 that should be about good so that we don't actually kind of go true Phi and you can see the conquer you know further than that otherwise it looks pretty good to me I'm probably actually make this point to five because it doesn't does feel a little bit too fast the zooming you can have a zoom speed this well if you wanted to but I think we're probably okay and yeah you can see one way in we move kind of way to be false I think and when we're out it feels like we moved too slow so to fix that what you could do technically speaking and I probably will talk about that in a dedicated episode just because it's quite fun to play around with curves and stuff like that but basically what you could say is that this you know camera translation speed that we set could basically be equivalent to our zoom level car zoom level so if you do something like this and zoom level by the way actually the higher zoom level is the further out you zoomed in so that makes sense so you can see when we're zoomed in we move nice and slow but then when we zoom out you know we move nice and fast which is good in fact a linear curve like this looks pretty good so we might keep it as that for now that looks pretty good to me but we'll talk more about functions and stuff like that in the next episode probably so there we go over what rotation we've got movement which is pretty cool if we go back we'll just disable rotation but I think we need that we haven't tested resizing we haven't tested resizing so I'll just do that now quickly if we boot this up okay cool so if I resize the window then our camera will hopefully resize but you can see that I don't think we support resizing at all properly so because this viewport you can see is get to cut off up here so we definitely need to talk about resizing in the future maybe next episode or the episode after but other than that there we go there's our camera controller it's super simple it's a class officially just designed to be a camera controller I think everything else is pretty good you could clamp the out zoom level as well just to anything use s to be clamp or s city min as well as max to actually make sure that you don't seem too far out but I think that's fine I think the big problem is Dominion too far because then you basically wind up with you know flipping your view upside down but anyway that's it hope you guys enjoyed this episode please hit the like button if you did as mentioned earlier you can also help support this series by drawing up a drunk on for size that sure no huge thank you to everyone who does help support this yeah I mean that's a camera controller it's really cool to kind of take this out into its own class and tweak it as much as long as we need to get this to be like the perfect camera controller next time as I mentioned we might take a look at and I I did want to actually make an episode talking about like mathematical curves that we can apply to certain properties so instead of having like a boring camera translation speed which is what we had now you can see that we basically set it to be the zoom level so actually realizing how that works I think is very important not to mention that instead of having it linear like it currently is it might be nice to have like an exponential curve or something like that so it might be something that I talk about next episode we'll see other than that we also need to fix some window resizing and then we need to have a maintenance episode so I'm a mice how much stuff we end up having for the maintance episode I might make window resizing part of that but I think window resizing is probably a decent topic in itself to have an insert video so that's the stuff is coming in the future yeah thank you guys for watching and I'll see you in the next episode good bye [Music]
Info
Channel: The Cherno
Views: 19,623
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, game engine, how to make a game engine, game engine series
Id: q7rwMoZQSmw
Channel Id: undefined
Length: 35min 49sec (2149 seconds)
Published: Thu Sep 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.