5 hours 41 minutes 40 seconds
🇬🇧 English
Speaker 1
00:00
Dr. Radu is back with another advanced JavaScript course. He'll teach you how to build a virtual environment entirely from the ground up. Get ready to use basic HTML, CSS, and JavaScript to craft a world you can use in all kinds of projects.
Speaker 1
00:15
He'll show you how to fill the world with self-driving cars, but you can fill it with anything you like.
Speaker 2
00:46
Hi, and welcome to phase 2 of the self-driving car course, a virtual world. This is actually a standalone, so you don't need to have watched phase 1 to follow along. We'll start building this world from scratch, using some basic HTML, CSS, and a lot of JavaScript.
Speaker 2
01:06
And whether you'll fill it with self-driving cars or something else is completely up to you. The focus will be on generating the world itself, and I'll only integrate the self-driving car code at the end. I made this course to prepare for phase 3, understanding AI, where we'll focus on neural networks and other AI techniques. I hope that by the end of phase 3 you'll understand why neural networks work, why hidden layers are needed, and things like that.
Speaker 2
01:36
But for that, we need this fancy world to generate complex scenarios. The 1 we had earlier is too simple to teach you all that. So if neural networks is what you're waiting for, be patient and enjoy phase 2 meanwhile. You'll learn a lot of things in this 1 as well.
Speaker 2
01:54
A set of skills I really benefited from during my PhD and my time at Microsoft and I'm sure they'll be useful to you as well in your career. We first learn about graphs, and I'll show you how to implement a spatial graph from scratch. I'll also show you how to make an editor for it as well. It will have a dynamic viewport, so we can pan and zoom to edit graphs of virtually any size.
Speaker 2
02:20
When building this editor, I'll explain the mathematics and logic behind good user interfaces in modern pieces of software, and we implement something similar ourselves. Then we use geometry to generate the road borders around the graph. I'll teach you about polygons and some useful operations using them. We'll apply the same techniques in a slightly different way to generate the buildings and the trees as well.
Speaker 2
02:46
We spent 2 lessons on these because I want to show you how to implement this 3D look as well. They're not really 3D models, just a little bit of math that gives that illusion. Then we implement the way to add markings to the road. This will make phase 3 even more interesting because we'll need to teach the car to respect all kind of traffic rules as well.
Speaker 2
03:08
Now we'll focus on making this world editor a pleasure to use, minimizing the number of clicks and maximizing the amount of auto-generated content. But I'll also make videos on how to load real-world data from OpenStreetMap so the car can drive in your city as well. This is Joensuu, the car just passed by Karelia University of Applied Sciences. That's the Wärtsilä campus where I'm currently filming, and it started off at Tikkarinne, the other campus, and now is heading towards city center where I've marked some IT companies on the map.
Speaker 2
03:41
Looks like the car is heading towards Arbonaut, which is on the other side of town. I'll add a link to this demo in the description. Check it out. It's a good way to learn about Yoensuu if you plan to come study with us.
Speaker 2
03:54
You'll find many useful links there as well. Oh, and I'll also make a video for creating this mini-map. It really helps when the world is big, and the mini-map was probably the most requested feature I got in phase 1. I've tried answering to all requests when making this, but there's still a few I'm leaving for phase 3, they fit better there.
Speaker 2
04:15
Now, to be clear, this project is complex, especially because we're going to code everything ourselves and not use libraries. It won't be easy to wrap our minds around it all. But I divided it into components, lessons that I believe are easy to understand on their own. I consider this course to be at an intermediate level, but I do encourage beginners to give it a shot as well to get an idea of what a more complex project looks like.
Speaker 2
04:42
I explain everything the best I can, and if you have difficulties, just ask in the comments or on my Discord. The prerequisites are high school math and if you have trouble with that, this playlist will have you covered. You'll also need to know some JavaScript. If you completed phase 1, you'll have no problems here.
Speaker 2
05:01
But if you need to brush up on it, I explain some tricky JavaScript syntax in these sketches, so check them out if needed. I hope you're excited and thanks for being patient this long. Can't believe it took me over a year to make this, but I wanted to do it justice since you liked phase 1 so much. Now let's begin.
Speaker 2
05:21
I'm going to code everything using Visual Studio Code and we'll begin somewhere in an empty folder on your computer. And let's create our first file here. It's going to be index.html. And we begin with basic HTML.
Speaker 2
05:41
We write the doc type here and the HTML page structure. In the head I'm going to add a title and the title of this will be the world editor. This will make it appear in the browser tab and let's make it also visible by typing it inside of the body in an h1 tag like this. Now, most of our functionality is going to be built on top of an HTML canvas element.
Speaker 2
06:21
So let's add canvas element with ID my canvas onto the page as well. Now let's open this in our browser. I'm going to be using Google Chrome. And remember to open the developer tools as well.
Speaker 2
06:40
It's really important to know if you make any mistakes, the errors will pop up here. Now, you can see here the title of the page is correct and the h1 tag is here, but the canvas is not really visible. It is there. If you press this button right here, you can find it on the page, but it's transparent and we can't see it.
Speaker 2
07:03
So let's add some styles to this page. I'm gonna go here below the title and link a CSS file like this called styles.css. Like this, called styles.css. We're going to create this file here, styles.css, and for the body, I'm going to give it a black background color.
Speaker 2
07:36
And I'm going to align everything to center, like this. The H1, I'm going to give it a white color, so that we can read it on the black background, and set its font to Arial. I think it looks better. And finally, the canvas, I'm going to give it a background color that we can see on this black background.
Speaker 2
08:03
And I'm just gonna use this shade of green that I like. Let's save this and refresh. And it now looks like this. I'm gonna make the canvas bigger and we're gonna use JavaScript for that.
Speaker 2
08:19
In index.html, we'll write JavaScript code here at the end of the body. We begin with the script tag and I'm gonna set my canvas width to 600 and my canvas height also to 600. Because it fits my screen well pretty much, but you can choose other values if you want. Now, we're going to be drawing on this canvas, and for that we're going to need to access the 2D drawing context of this canvas, like so.
Speaker 2
09:02
And we will draw a graph. Let's pretend that we have a way to define a graph like so, and that the graph has a way to draw itself on a given context. So we're going to need to implement these functionalities, the graph itself and the draw method. And we'll do that in a separate JavaScript file.
Speaker 2
09:30
I'm going to include it here. We're going to place JavaScript files in a folder called JS. But this is going to be a pretty big project, so we need to think about code structure early on. And I'm going to place the graph itself in a subfolder called math and then graph.js, like so.
Speaker 2
09:56
So let's create the js folder first and inside of this js folder we're going to create math, another folder, and inside of math, we create our JavaScript file, graph.js. Graphs are data structures made from a set of nodes, also called vertices, and a set of edges, or links, that represent relationships between the nodes. Like in a social network, a graph can store which users are friends. These relationships are bidirectional here, but in other applications like here on YouTube, I may be subscribed to you, but you may not be subscribed to me.
Speaker 2
10:41
There is very much to say about graphs, like we already used graphs in phase 1 when implementing the neural network. There the links had weights, so we were dealing with a weighted graph, and there was a link between every input and every output, so we used a matrix to represent those relationships in memory. But now we're going to implement a new type of graph, called a geometric or spatial graph, where nodes are not just abstract things. They will represent road intersections or places where the road geometry changes like this.
Speaker 2
11:13
You can see there are not so many links here really. So a matrix would be mostly empty and not the right choice in this case. When implementing a spatial graph, the proper way would be to separate the node metadata, the points, from the connections themselves. But I think it makes things too complicated.
Speaker 2
11:32
And since this whole project revolves around geometry, I will simply say these are the points, and they will be connected by segments with 1 point at each end. So a bit unconventional here, but I think it simplifies things in our case. Now let's code 1 of these graphs in JavaScript. It's gonna be a simple class, and its constructor is going to have 2 parameters.
Speaker 2
11:59
We build the graph using points and segments. I initialize them to empty arrays so that you can create an empty graph as well. Now, let's store these parameters as attributes, like so, and implement our draw() method that takes the canvas context as a parameter. And I'm just going to look through all of the segments that we have and tell each of those segments to draw themselves on the canvas context.
Speaker 2
12:48
We don't have these segment objects yet, but they will be there and they will have the same kind of structure as the graph has. It's important to be consistent with these things. And then I'm gonna do the same thing for the points. Like so.
Speaker 2
13:09
And I draw the points after the segments because I don't want the segment lines to be on top of the points. It doesn't look good. Now let's implement these segment and point objects. In index.html I'm going to copy this line And I'm going to place these in a folder called Primitives.
Speaker 2
13:34
Points and segments are primitive objects. So let's write here Primitives.Point and Primitives.Segment like this. And inside of this JavaScript folder, the main 1 here, not inside of math, I'm going to create a new folder called primitives. So it's next to the math folder here and in it we create a new file point.js.
Speaker 2
14:14
It's also going to be a very simple class. Constructor takes in x and y as parameters where we want the point to be and we set these parameter values as class attributes like so, so that the object knows where it is, pretty much. And then in the draw method, under given context, I'm also going to pass 2 more parameters to style these points a little bit. The first 1 is going to be a size, maybe 18 pixels, and a color.
Speaker 2
14:53
I'm just going to use black. We're going to draw the points as circles and the circles need a radius. I'm going to just calculate that as half of whatever this size is. And we begin a path, set the fill style to this color that we have in the parameter list, and use the arc method of the drawing context.
Speaker 2
15:22
And draw this at this X and this Y. We pass the radius next. And the last 2 parameters specify that we want a full circle. So starting at 0 degrees and ending at 360 degrees.
Speaker 2
15:41
But we have to write this using radians. That's just how the function works. So 360 degrees is 2 pi radians. Now we fill.
Speaker 2
15:53
And we're done with this point for now. Let's do the same thing with the segment, so create a new file called segment.js. File called segment.js. Our segment class, the constructor, will have points this time, p1 and p2.
Speaker 2
16:15
Let's save these as attributes of the class and implement our draw method. Give in a context and again I'm gonna pass 2 parameters here as well. The width, the line width, how thick we want the segment to be. I'm just gonna give it the default value of 2 and a color.
Speaker 2
16:42
Again I will set it to black, Like so. And we begin a path. We set the line width to the given width. And we set the stroke style, this time, to the given color.
Speaker 2
17:03
We move to the first point P1x P1y and now we're going to copy this line and line to and line to p2x, p2y. And then we stroke. And we're done with these core functionalities. The only thing left to do is test.
Speaker 2
17:37
So make sure all your files are saved. Go back to index.html and let's pass some actual points to this graph. To this graph. I'm gonna go here, above where we defined the graph, and say p1 is a new point at 200, 200 pixels.
Speaker 2
18:02
Let's copy this few more times and have here p2, p3 and p4. Maybe p2 is going to be at 500, 200, then 400, 400 and 100, 300. And let's pass these as an array to our graph here as well. This will be the points parameter of the graph.
Speaker 2
18:34
And the segments will be the second parameter, but we can actually test now already. So, save the file, refresh the page, and there's our graph. It has the 4 different points. This is the 200, 200.
Speaker 2
18:50
This is the 500, 200. Keep in mind that 0, 0 is this top left corner, so 500, 200. Like so. Let's add segments as well.
Speaker 2
19:05
I'm going to go here and say s1 is a new segment between p1 and p2. And let's copy this 2 more times, maybe S2 and S3, between... Let's just link P1 for now. P1 with P3 and P1 with P4.
Speaker 2
19:31
And pass these as the second attribute here. Save and refresh and we can see now the segments as well. Let's connect these 2 as well. Well.
Speaker 2
19:46
So I'm gonna copy this as S4 between P2 and P3 and passing S4 here. Like so. Good. Now, this is a static structure.
Speaker 2
20:07
We can't really edit it in any way. And the next thing we'll do is add some functionality to add points, remove points, add segments, remove segments, and so on. We'll do that in a very basic way, by adding here buttons that just add some of those elements on the screen. I want to teach it like this because it's how interfaces were made 30 years ago.
Speaker 2
20:31
And then we're gonna make it modern. I want you to see how things have changed since then. So let's go up here, and below our canvas element I'm going to define a div with an ID of controls, and this is going to contain some buttons that we can press to do operations with our graph. The first button, On click, it will call a function called addRandomPoint.
Speaker 2
21:08
Let's call this button addPoint, like this. Give it the label. And let's implement this addRandomPoint function next. I'm gonna just make it here, on top of this script.
Speaker 2
21:24
These functions will go away at some point, we just need them to develop the core functionality. So I'm not gonna worry about the code structure too much. And addRandomPoint... It's going to tell the graph to add a point.
Speaker 2
21:48
The Graph object will have this functionality. So, let's pass here as the point a new point and give it random coordinates. So, the X is going to be random and I'm gonna scale this by the width of the canvas so that the value is not between 0 and 1 what this gives but between 0 and 600 pixels And the height in the same way. Now, to see this point after it was added, we have to refresh the canvas.
Speaker 2
22:31
We do that by clearing the whole canvas, starting at 0,0, top left corner, and width, height, bottom right corner, and then we tell the graph to draw itself again. So, graph, draw, and the context. So let's implement this addPoint function real quick. We are going to go to graph.js And here, below the constructor, we say add point, a given point, and we just take the points attribute of the class and push the given point, like so.
Speaker 2
23:20
Now let's refresh and when we press this button you can see a new point was added there. You can try pressing it multiple times and it's gonna work pretty much always. Unless we run out of memory, but that's not gonna happen anytime soon. Now there is 1 situation that I want to take care of.
Speaker 2
23:42
I don't want to have 2 points at the same location. It makes no sense. Points are identified by x and y, and if you have 2 of them with the same x and y, then that's the same point. So let's see how we do that.
Speaker 2
23:59
We're gonna need a function that checks if the graph already contains a point. The red. And I'm gonna write it like so, using the array.find() method, which finds inside of these points a point that equals the given point like so. What happens here is that the find loops through all of the points here and let's call them P and then returns the 1 that equals point and if it doesn't find that point then it's gonna return nothing so something and nothing will also correspond to true and false in Boolean logic and we're going to use this as such it's just how JavaScript works so equals here we need to implement as a method to the Point class We need to check if p is equal to the Point somehow, so inside of the Point class I'm going to implement equals Point and return if this x is equal to point x and this y is equal to point y, like so.
Speaker 2
25:28
This will only be true if point and this are the same. And now we could go here and check to see if the graph contains the point before adding it. Or we could add a new method to the graph that tries to add a point and then maybe we monitor here the success. Did it succeed to add a point or not?
Speaker 2
25:59
Let's log this in the console right here. So we can go inside of Graph.js here and tryAddPoint() is going to check if this point is not part of the graph at the moment, then we add the point. And notice here how I'm reusing the method we defined earlier. Then we also return true.
Speaker 2
26:39
It was a success. Otherwise, we return false, like this. Let's save, refresh, and when we press this add point, same thing as before happens pretty much. But we always get here this true, and that's because it's really unlikely to randomly generate another point on top of an existing 1.
Speaker 2
27:07
But to test, we should force that to happen somehow. So I'm going to go here in index.html, copy this entire function here, paste it here in the console, and instead of having random I'm going to write 0.5 and 0.5. This means that I'm going to overwrite this function so that it generates the points always in the middle of the screen. So if we press multiple times on that button, the same point will come again and again.
Speaker 2
27:45
To overwrite we have to press ENTER now. And let's press this point again once. And this is the point in the middle of the screen. And when we try adding another 1, we get our false as well.
Speaker 2
28:02
So it wasn't a success. And the number of points in our graph... Graph.points... 18...
Speaker 2
28:18
Doesn't change. Now we have a way to add points to our graph, let's add segments as well. We're going to go here and copy this button and call the other 1 AddSegment. And the function will be AddRandomSegment.
Speaker 2
28:44
And we define that function here. AddRandomSegment. This will work a little bit differently. It first gets the points that we want to connect, and then creates the segment.
Speaker 2
29:01
Let's get these points again at random by using floor of random times the number of points. Floor makes sure that we have an integer value and let's copy this for another index 2 different points and then tell the graph to add a segment, a new segment, between the points at index 1 and points at index 2, like this. Now, to see any changes after we press this button we also need this thing right here, clearing and redrawing the graph. So let's save this and go to Graph.js to implement this addSegment method.
Speaker 2
30:05
We go here, below the points section, and let's implement addSegment. Let's call it seg, and it's going to take the segments of the graph and push this new 1 at the end. Let's save this, refresh And now when we press add segment, it added a segment here, and now I pressed again and nothing happened. Let's try again.
Speaker 2
30:44
Nothing. Nothing, Nothing. What's wrong? Can you figure out what the problem is?
Speaker 2
30:53
Pause the video now and try to figure out the answer for yourself. Or just listen to me carry on. The issue is that here where we are adding these segments sometimes index1 and index2 could be the same so we create the segment from 1 point to itself. We could check here and say success is false and then if index1 is different than index2, success is true.
Speaker 2
31:35
Let's also add the segment only in that case, because a segment from 1 point and to itself doesn't really make sense. And let's log this success here as well. Refresh and when we press add segment, we get the true and we see the segment when it appears, but sometimes we get the false when we try to link the same point to itself. And if we press more times...
Speaker 2
32:08
Oh! We got the true now, but I didn't see any change. What happened? Try to figure out what happened, pause the video, and now I'm gonna tell you the answer.
Speaker 2
32:23
So, turns out it's possible that you add the same segment multiple times as well. Like, maybe now this segment was just added again. Let's actually see here in the console the segments. And the last segment between 200, 200 and 100, 300.
Speaker 2
32:53
I think that's this 1 here. Yeah, and you can see they're exactly the same. Let's try adding a few more segments, even though they are going to be copies of themselves. And now we see here 18 segments, but we can really only count 123456.
Speaker 2
33:19
So it doesn't make sense to have more than 6 segments here. We wouldn't be able to see them and we wouldn't be able to tell that there are duplicates there. This last 1 actually is the same that we had previously, but in reverse. P1 is P2 and vice versa.
Speaker 2
33:44
We're gonna need to handle that case as well. So let's go back here and convert this into a tryAddSegment as well and move that to the success and have the same kind of structure as here, pretty much. Let's go to the graph and implement this method. TryAddSegment() And it's going to be very similar to the try add point, but we first check if this contains the segment, and if it doesn't, then we add segment and return true and here we return false like so this contain segment is going to be here and what we do is again use the find method on the segments this time.
Speaker 2
34:59
I'm going to loop through them using this S and find the 1 that equals the given segment. Like so. We're going to need to implement equals functionality for the segment as well. So here equals is going equals is going to be return p1 equals seg.p1 and p2 equals seg.p2.
Speaker 2
35:38
But we also want the opposite check if the points are flipped. So we're gonna do OR equals seg p2 and p2 equals seg p1. Now if we refresh and add a few segments we can see they're false appearing many times. Eventually 1 of them will work.
Speaker 2
36:08
Okay, so now we have all 6 of them here, and when we go in the console and type graph segments, they are indeed 6, so it works. But before we move on, this line of code is quite complicated here, checking so many things, and it's always good to try to simplify. 2 segments are the same if they include the same points. So we could make a helper method here called includePoint() and return this p1 equals point or this p2 equals point.
Speaker 2
36:53
So now we have a way to check if the segment includes a point. And 2 segments are equal if this includes p1 and this includes p2. It makes sense. And if you save and refresh, it still works.
Speaker 2
37:25
But it's much nicer to read in this way. And you'll see that later we'll need this includes for other reasons as well. Now this part here where we check to see if the indices are different we could incorporate it part of the try add segment. So try to see if the points are equal and if they are it won't add the segment.
Speaker 2
37:50
So we could actually have success here defined as so, and we don't need to use let here, we can use const again. So tryAddSegment now is going to not only check if it doesn't contain the segment, but also if p1 is not equal p2. Let's save, refresh, and try adding segments again. And we still have 6 here.
Speaker 2
38:38
Great. Now let's go to index.html and have a way to remove these segments also. I'm going to copy this and say here remove a segment at random and remove segment like this and the function for removing is going to be like this. I'm gonna first check to see if there are any segments.
Speaker 2
39:11
So if graphSegmentsLength is 0, You can also have methods added to the graph that check if it has segments or returns the point count if you want to do a really, really proper job. But this is JavaScript and I don't bother too much. So here I'm going to log no segments just as a warning but no bother trying to remove something there's nothing to remove and return and otherwise we are here where we get a random index from the segments this time and remove segment the 1 at that index, like so. We also need to copy this code here, otherwise we won't see the change after it happens.
Speaker 2
40:21
And now let's implement this remove segment. I'm going to go here and say remove segment splice. Segment splice. So Splice removes elements at the given index.
Speaker 2
40:51
Here we get the index of this segment. And I want to remove just 1 element, the segment itself. Now, You can add many other methods here, maybe removing the segment by the index. We already had the index here to begin with.
Speaker 2
41:09
Or try to remove a segment similar as those other ones, but they won't be needed in our project and I'm not gonna over-engineer this. Refresh and remove segment. Okay. Okay.
Speaker 2
41:26
Okay. All good. And if we try 1 more time, no segments written in the console. Great.
Speaker 2
41:35
Let's remove the points as well. For that I'm going to add another button here. Remove random point. Remove point.
Speaker 2
41:48
And point, removePoint, and let's copy this removeRandom segment like this and have here removeRandom.point. If Graph.points are empty, no points. Index is going to come from the points this time. RemovePoint, graphPoints, and let's implement the function removePoint in the graph file.
Speaker 2
42:32
Let's copy this remove segment and put it here in the points section and rename it to remove point, the given point, and it's gonna look inside of the points array and splice the points index of point. Would have been faster to just rewrite this but I wanted to show you how similar these functions are. Now let's save this, refresh, and when we remove a point we can see it's gone, but I'm removing all the points and now we also get that message there, but the segments are still there. And that makes no sense.
Speaker 2
43:22
How can you have segments between points if there are no points? So let's make it so that when we remove a point, it also removes all the segments that contain this point. So the new logic will be here. Let's first get some segments with point the given point and then for each segment of this small list, we remove the segment like so, using the method that we have defined earlier.
Speaker 2
44:09
So we need to implement this getSegmentsWithPoint() and I'm gonna do that here I'm gonna do that here in the segments section getSegmentsWithPoint we start off with an empty array and now loop through all the segments of this graph if The segment includes this point then we add it to this array. And at the end here we just return the array of segments that contain this point. Simple enough. Let's refresh and remove point, and you can see that the segments connected to it are also gone.
Speaker 2
45:11
Let's try adding some points, adding some random segments. Question for you, given a number of points, how many segments can there be between them? Like in this case, We have 17 points. How many segments can there be here?
Speaker 2
45:35
Let me know in the comments. And now we can also try removing some segments, removing some points. Good, everything seems to work. But 1 more useful button to have here would be to clear everything, to remove everything.
Speaker 2
45:54
I'm gonna implement the method for that here. It's really easy. Let's say this will be dispose of the graph. And I'm just going to set the points and the segments to empty.
Speaker 2
46:11
Setting the length attribute like this doesn't generate new objects for them, so it reuses the same array object. And in index.html let's copy this and say removeAll(), removeAll() and the removeAll() function And the removeAll() function calls graph, dispose, and we need to copy this again if we want to see the update. Refresh. And it works.
Speaker 2
46:50
Now, you could build an interface like this. Maybe when adding point it wouldn't just be a random point, but have there some inputs for the X and the Y. Adding the segments, maybe it can have a drop-down for the first point and the second point. Removing again a drop-down with all the segments and all the points.
Speaker 2
47:14
That interface would work, but it would have so many clicks and so much use of the keyboard that nobody would use it nowadays. I will teach you how to make a modern interface, so we won't need these buttons here. Let's remove them, actually. I will keep the Controls section here because we might use it later, but no need for these functions at all.
Speaker 2
47:43
All that was important was to have here all these useful methods inside of the Graph class. A good user interface is 1 that minimizes the number of clicks and keyboard input. I'll teach you how to build a Graph editor that does that. Now, in other courses, I've considered mobile as well, but in this series, I'll focus on the mouse because I think it's the best tool when creating graphs.
Speaker 2
48:08
It has 2 easy-to-press buttons we can use for adding, dragging, and removing points. We'll implement the editor step by step, adding new features when needed, and polishing the existing ones until we're happy with the final logic. At least I'll be happy with it, but if you're not, I hope you'll get the skills to edit things in or out just the way you want. We will define our graph editor here after we have the graph and we initialize it like so.
Speaker 2
48:44
New Graph Editor, passing the canvas. It's gonna need that for event listeners and the graph. And it's gonna also be doing all the drawing on the canvas. So actually we don't need this call anymore.
Speaker 2
49:02
It's gonna be handled by the graph editor. And the editor is going to be very interactive. It's going to change all the time depending on our mouse moves. And the simple way to implement this is using an animation loop.
Speaker 2
49:16
So a function that we call here and start to animate and it's gonna be really simple. All it does is it clears the canvas because it's gonna redraw again and again, so clearing starting at 0, 0 and all the way to the canvas width and canvas height. And then we'll have our graph editor display itself. It knows where the canvas is, because it has it here as a parameter.
Speaker 2
49:53
And finally, we call requestAnimationFrame() and pass this animate() call again and again and again. It's going to be kind of a recursive call where the browser will try to recall this function 60 times per second. So we're ready to implement this Graph Editor, and we'll do that in a new file. I'm just gonna define it here in the main JavaScript folder.
Speaker 2
50:28
So it's gonna be called Graph Editor. And in JS create a new file GraphEditor.js. The class is a simple class and the constructor will get the canvas and the graph as parameters. Now let's set these values as attributes so that the editor doesn't forget them.
Speaker 2
51:04
And let's have a reference to the canvas drawing context as well, defined here as a class attribute so that we have easy access to it, like so. And the display method... For now, let's just have it draw the graph on this context, save the file, refresh the page, and it looks pretty much the same as before. But now if you go to the console and you type for example p1.x is equal to 400 and press enter you're going to see immediately that point jump to a new position because it is rendering again and again and again all the time you just don't see it.
Speaker 2
51:55
So we are ready to start adding event listeners for the mouse action. I'm going to create a private function, private method for this here. So it starts with the hashtag and then addEventListeners and this method is going to be here below the constructor. The first event listener we add to our canvas is going to be for mouse down and we get the information from this event and write the arrow function.
Speaker 2
52:35
So this arrow function, let's close it here and now implement the body. The first thing we'll do is get the mouse location from the mouseDown event from the click. So mouse is going to be a new point, and the information is inside of this EVT.offsetX and EVT.offsetY are the XY location of the click. Now, with this point, we can just tell the graph to add the point, and the code should work.
Speaker 2
53:15
So, let's refresh and click somewhere, and there you go. Points added anywhere we want them. So it's an improvement over the randomness we had previously. Now 1 thing that we'll really need in this editor is the ability to select a point.
Speaker 2
53:35
And let's implement that so that the last point that we add is also selected. We'll have a more complex logic for the selection later, but now it's just like that to test. So I'm passing here this selected is equal to the mouse and this selected will be attribute of the class, initially null. It can be null if we don't select anything, and then in the display here let's draw it separately in a different way.
Speaker 2
54:11
So if we do have a selected point I'm going to tell this point to draw itself under context, but this is just going to draw it on top of itself already. It's already there from this graph draw. So I want to specify a little bit different properties for this selected point. And let's give it an outline.
Speaker 2
54:38
We don't have this functionality yet, so let's save this and go to our point primitive here, and in the draw method add an outline here. Now, the more parameters you add here, the more difficult it's going to be to remember their order. So I like to group these as an object, like this, and now you could pass them in any order you want. You just need to specify these properties, the name of these properties here, like what we just did.
Speaker 2
55:16
So if you want to pass this without these options, we write here equal to an empty object. And then you can just call draw as we did before in the graph. Now this outline, I'm just going to go here below. Going to go here below and if we do have an outline we begin another path set a line width of 2 for example and let's give it a stroke style of yellow and draw a new arc at X and Y but our radius will be a little bit smaller.
Speaker 2
55:59
I don't want it to be exactly the outline I want it to be kind of inward. I like the look of that. So let's say radius times 0.6. And again, 0, 2pi for drawing a complete circle.
Speaker 2
56:17
And again, if you want this selected point to look different, just make it different. It's your code now. Let's save this, refresh, and now when we click somewhere, we're also going to have that point that we just added selected. And this is how it looks like.
Speaker 2
56:38
And the next thing we do in our logic is when we click on a point that is already existing, I don't want to create a new point on it or close to it. So I actually want to select that point. And at the moment this doesn't happen, it just creates a new point on top of it pretty much. So let's see how we do that.
Speaker 2
57:03
We first have to figure out what point are we hovering over. So going back to our graph editor in this mouse down event listener, after we have our mouse location, we get the hovered point by looking at the nearest point. So we're gonna need the function getNearestPoint() from the mouse, from all the graph points, Like this. And then if this hovered point exists, we are going to select it.
Speaker 2
57:46
Like so. And now I'm going to return. So the code doesn't go here anymore for adding a new point and selecting it. It just selects.
Speaker 2
57:59
Let's define hovered also in the constructor here, same as selected. Hovered, initially null. And what we need to do is implement this get nearest point somewhere. And what I like to do is create a new file and link it here in index.html.
Speaker 2
58:21
It's gonna be also part of math and we will call this new file utils.js. This will have utility functions, mostly math functions, that will help us during our project. So in math, create a new file called utils.js And the function for getting the nearest point to a location from a set of points is going to be a basic minimum search. So we look for the minimum distance from our location to all the points and select the point with the minimum distance.
Speaker 2
59:09
We do that by initializing this minimum distance to a very large value like this and the nearest point let's just have it null in the beginning, and then we loop through all the points in the array like this and calculate the distance between that point and the location that we have here as a parameter. If this distance was found to be less than our supposed minimum distance, then minimum distance becomes distance, and we record the point with this minimum distance as well in this nearest attribute. Then we go here at the bottom and just record
Omnivision Solutions Ltd