Using WebGL Overlay View in a React App (Advanced)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
♪ [music] ♪ Hey, I'm Leigh Halliday. In this video, we're going to work on a really cool demo where we combine React with Google Maps with directions with Three.js. We're going to combine them all together to create this demo here where we have directions between 27 Front Street to 75 Yonge Street in Toronto, and then we animate a car in 3D along that route. We can zoom in and see the car in detail moving along that path. That's where we're going to end up, but we're starting here, just a blank screen that says the word "App." First things first, if we pop over to the code, we need to get the Google Maps script loading on the screen, and we're going to use the <i>Wrapper</i> import for that. You'll see there's a number of other imports here, but we'll talk about each one as we use them to not introduce them all at once. Right here, we need to, first, load the Google Maps script, and we do that using <i>Wrapper.</i> To <i>Wrapper</i> as a prop, we have to pass the API key. API key, I'm storing in an environment variable in <i>.env.local.</i> We will copy this map API key, and we'll do <i>process.env.</i> and then paste the key. Inside, as children to this <i>Wrapper</i> component, what it does is it renders what's inside of this once it has loaded the script. What do we want to load when the script is ready? We want to render the map, so we'll call <i>MyMap</i> like this, which doesn't exist yet, but we can go and create it below. Just like that. In <i>MyMap,</i> we need to, first, render a <i>div</i> that the map will then be put onto or attach to. We'll return a <i>div.</i> This <i>div</i> can be self-closing because it actually isn't going to do anything, but we need to attach a <i>ref</i> to this because that's how we're going to point Google Maps to the actual DOM node. Because we're using a <i>ref,</i> we need to set up that here. We'll say <i>const ref = useRef,</i> just like that. When this renders, it will attach this <i>div</i> to this <i>ref</i> so that we can pass that to Google Maps. Last thing I need to do is just I'm going to attach an ID of map to that. Why? Because I have some style set up that will make it 100% width and 100 VH height, so it fills the whole screen. It's going to render through once that will attach the <i>ref,</i> and then we'll create a <i>useEffect</i> hook. This runs after the first render. Inside of this, what we're going to do is basically create an instance of the Google Maps itself. We're going to store that in <i>state,</i> so we'll say <i>map</i> and <i>setMap</i> in <i>state,</i> and we'll say <i>useState</i> hook like that. Then we can come below, and we can say <i>setMap.</i> What do we want to do is we want to create a new instance of the Google Maps, so we'll say <i>new window.google.maps.Map.</i> Then here, we can pass two things. We need to pass the <i>ref</i> to the actual <i>div</i>-- so <i>ref.current</i>-- and then we need to pass <i>mapOptions.</i> You might have saw up here that I've already set up our <i>mapOptions.</i> We'll pass them in, and then we'll just quickly talk about what they are. The <i>mapID.</i> This is important because any time you're using Three.js or WebGL Overlay with Google Maps, you need to have a vector-based map. I set one up over here in the Google Maps Platform, the Developer Console. Here's my map ID. You can see that it's a vector-based JavaScript map, and I've attached some gray styling to it, so the car we add will really pop out on the screen. That is also in environment variable. You need to tell the map where to load at the beginning, where the center is. I just placed it in Toronto. I set the zoom to 18, so it's fairly close zoomed in. I removed the default UI, and then I rotated the map by setting its heading, and then I tilted the map by setting the <i>tilt</i> here. If we save this, come back, and look at the screen, we have Google Maps loading in Toronto. It's tilted. It's on a heading, and we're good to go onto the next step. Now that we have the map set up, what I want to do is focus on getting the directions between two locations. For that, the first thing I'm going to do is I'm going to store the <i>route</i> up here in <i>state.</i> We don't really need this yet, but we will need this when we draw the track onto the map, and then we animate a car across it. We're just going to get this set up now. <i>route</i> and <i>setRoute</i> is <i>useState,</i> and it can start as an empty value. Then what we're going to do is come down here, and we're going to render a <i>direction</i> component that doesn't yet exist, but we'll go create it right now. I'm putting in a fragment so that below here, I can say if there's a map-- so <i>map &&</i>-- we want to render the <i>directions.</i> Two <i>directions,</i> we're just going to pass the <i>setRoute</i> prop, <i>setRoute,</i> so that from <i>directions,</i> once they load, we can call this <i>setState</i> function so that we put the <i>route</i> up here in the <i>state,</i> and then we'll be able to later use it on for animating the car. We save this. If I were to look, we're going to get a big error that says "Direction is not defined." That is true. Let's fix that. We'll come down here, and we'll say <i>Direction.</i> Remember, we're going to be receiving <i>setRoute.</i> Then in here, inside of the <i>Direction</i> component, what we're going to do is set up two pieces of <i>state.</i> For this demo, we don't actually need <i>state</i> for this, but I wanted to set it up in the future if somebody wants to take this and expand it to make choosing the two-- the origin and the destination-- dynamic. We're just going to hardcode them in this video, but it's set up, ready to go if you want to attach Google Places to that, to allow the user to search between two destinations. The <i>origin,</i> that will be equal to <i>useState.</i> We're going to just hardcode this to 27 Front Street East in Toronto. Then the <i>destination</i> will be <i>useState,</i> and it's going to go to 75 Yonge Street in Toronto, just like that. We're going to return a <i>div,</i> and in the <i>div,</i> we're going to display some of the information. First things first, I need to give it a class name because if you saw, it's in the top-right corner. It's got a black background, white text. I set that up with a class of <i>Directions.</i> That's over here in the <i>styles</i> > <i>global</i> we find <i>Directions,</i> you can see its positioning, <i>absolute,</i> top-right corner, what its width is, padding is, etc. We'll come back, and then in here, we'll put an <i>h2,</i> and we'll say <i>Directions.</i> Where are we going? We're going from an <i>Origin.</i> Then what that <i>Origin</i> is will be the <i>origin,</i> like that. We're going from the origin to a destination. We'll put that on the screen, and the <i>Destination</i> is <i>destination</i> like that. If I were to come back, we now see we have the <i>Directions</i> here, but we don't actually know how to get from this location to this location. We need to use the directions service from Google Maps in order to get those directions. What we're going to do is allow that to render, but we're going to use another <i>useEffect</i> hook that we'll run after the render, and it's going to rerun any time the origin or the destination change. In here, what we're going to do is we are going to call a function that doesn't yet exist, but we're going to call it <i>fetchDirections.</i> We're going to pass in the <i>origin,</i> the <i>destination,</i> and then we're going to pass the <i>setRoute</i> function to this function so that when we get the directions, we can call this, and that will update the <i>route</i> to place it up here, so we can later render the animation. Let's go and create <i>fetchDirections.</i> It's going to be an <i>async</i> function, so we'll say <i>async function,</i> and we called it <i>fetchDirections.</i> What do we need to pass to it? We need to pass the <i>origin,</i> the <i>destination,</i> and the <i>setRoute</i> function. Okay, I made it <i>async</i> because there's a few <i>Promises</i> that we need to use in here. The first thing we need to do is basically call the Google Maps service to convert this string of 27 Front Street into latitude and longitude because you always ask for directions between two points, but we're starting with a string, so we need to, first convert to get the latitude and longitude. For that, I'm using a package called <i>use-places-autocomplete,</i> but specifically, we're using<i> getGeocode</i> and <i>getLatLng.</i> Because we need to call this twice-- one for the origin, one for the destination-- I'm going to take advantage of JavaScript being able to do that concurrently or in parallel by using <i>Promise.all.</i> I'll call <i>Promise.all.</i> We're doing two things here. We're getting the geocode, so we'll call that function, and we're getting it of an address, and the address is the origin. Then we need to do that again. We're going to get the geocode of the address, but this time, it's the destination. Because this is going to return us two results when they both resolve, what we're going to do is we're going to place them into a variable, and we're going to <i>await</i> for that. What we're going to call them are the <i>originResults</i> and the <i>destinationResults.</i> Save that like that. What this gives us back are two results sets, but there's another function here called <i>getLatLng</i> where given a result, it will convert that into actual locations or latitude/longitudes. For that, it's the same thing. We're going to do a <i>Promise.all</i> because it is asynchronous. We're just going to copy that and then change the variable names. The first one will be called<i> originLocation,</i> the second one, <i>destinationLocation,</i> and instead of <i>getGeocode,</i> we are going to call <i>getLatLng.</i> What you need to pass to this is one of the results, so they might give you many different options for this address. We're going to choose the first because it's probably the most accurate. We're going to say <i>originResults,</i> the first one, and then we'll do the same thing down here, and we'll say <i>destinationResults,</i> the first one. If we want to see what this looks like, why don't we <i>console.log?</i> Because we've been working blindly for a lot of what we've done so far. We'll pass this here, and then we'll pass the <i>destinationLocation.</i> We'll see what happens on the screen. There's no error. That's a good sign to start with. Let's check the <i>Console.</i> See what's going on here. If I pop open this, we have the <i>destinationLocation</i> is this latitude and longitude, and the origin is this latitude and longitude, so it worked, we were able to call Google Maps with two addresses, get results, convert them to latitude and longitude. Now, with these two points, we can use the directions service, and the directions service will get us the actual route to get from the origin to the destination. Let's set that up right now. We're going to create a new variable called <i>service,</i> and that is going to be a <i>new google.maps.DirectionsService</i> like that. We don't pass anything at the beginning to this, but what we're going to do is we're going to call <i>service.route.</i> Then to this, we're going to pass a few things. The first thing we're going to pass is the origin. We've got the <i>originLocation</i> like that. Then we're going to pass the destination with the <i>destinationLocation</i> like that. Then we need to tell it how we're traveling because Google Maps, you can travel walking, by bike, by car. We're traveling by car, so that's driving. We'll say <i>travelMode,</i> and that is<i> google.maps.TravelMode.DRIVING.</i> All capital letters like that because it's a constant. Okay. Like that, what we need to do is pass a second argument here. The first one is this object here, and the second one is a callback function. We'll just create the function first like that. We'll save it, so we get formatting. What we receive here are the result of routing between two locations and the status like that. We're only going to act on this if things worked okay. Ideally, we would have error handling and show the user a message, but we'll just assume it worked in this case. What we're going to do is we're going to say if the <i>status</i> is <i>=== "OK"</i> and there are results-- so just like that, <i>&& result</i>-- then what we need to do is basically take this <i>result</i> and convert it into the format we want to eventually work with. What I want is an array where each element in the array is an object, and each object has latitude with the number and long, <i>lng,</i> with the number. We want to end up with an array of things that look like that. To do that, what we'll do is we'll say... Let's put it in a variable-- so <i>route</i>-- and we'll say <i>result.</i> Again, they give you multiple ways you can get from one location to the other. We're going to choose the first one-- so <i>result.routes</i>-- and then we want the <i>overview_path.</i> Just like that. With the <i>overview_path,</i> that's an array, but we're going to convert it slightly into the format we want. We're going to map it, and we'll say each path or each destination along, what we want to do is return, so I'm going to do it shorthand here with brackets, object. We want to return latitude, and we'll save <i>path.lat--</i> it's a function, and longitude, <i>path.lng.</i> It's also a function. If I save it, we get autoformatting. Why don't we take a look at the result, so we can see if we're getting it in the right format. We've got <i>route.</i> If I come back in the <i>Console,</i> we have starting point at this location, and then it sort of along the way, each corner or each time you need to take a turn, and then you finally end up at your destination, which is the last element here. There's ten stops or ten spots to get from 27 to 75-- that address. Now that we have the route, we can call <i>setRoute,</i> passing in the <i>route,</i> and that will update <i>state.</i> If we call this here, that will get passed and bubbled up all the way to the top, so now we have the route. If we wanted to just see what it looks like, maybe we could <i>console.log</i> that, so any time this renders, we'll just say <i>console.log route</i> like that. It may render a number of times. That's okay, but you can see here that yeah, every time it rerenders, we have the route showing up here. Perfect! Now, our next step will be moving on to taking that route and then actually working with Three.js to draw the path and then animate the car along that path. In order to get the animation rendered out in the screen, we are, first, going to call an <i>Animation</i> component. What do we need for this? We need a map, and we need the route. If those are both true, we can render the animation like that. Now, we're going to be passing to the <i>Animation</i> both the map and the route. We'll pass it as a prop-- so <i>map</i>-- and then <i>route</i> as <i>route,</i> just like that. I'm going to place this right here, so we can keep it nice and close where we're working even though it actually gets rendered after the <i>Directions.</i> We'll create the <i>Animation</i> here. <i>Animation.</i> We need to receive the map and receive the route as a prop. Then we can do that. Strangely enough, <i>Animation</i> is not going to, in a traditional sense, render anything, so we could see render <i>null.</i> Why are we actually using a component? Just so we can take advantage of React's ability to rerender and to watch for changes, to call <i>useEffects,</i> and things like that. That's why we're working within a component, and it just fits well to render this when we have the map and the route, and we're going to be wanting to rerender this any time the route changes. We're going to set up a <i>useEffect</i> just like this. We are going to... Really, what we're watching is any time the route changes, we want to rerun this <i>useEffect.</i> The first thing we're going to do in the <i>useEffect</i> is basically just center the map to where the route is going. If you look at the screen now, it's centered in the middle of Toronto in Queen's Park, but the directions are down in this area. Let's take them up there. What we're going to do is we're going to call <i>map,</i> and we're going to say <i>setCenter,</i> and then to <i>setCenter,</i> we need to, somewhere along that route, get the middle point. This may not be 100% accurate, but what I'm going to do is I'm going to access the route, and then I'm going to say I want the middle point, so the <i>route.length / 2,</i> but because that may give us a decimal place, I'll say <i>Math.floor</i> to just round that down to the nearest integer. Just like that. Whenever we set the center, we can also zoom. We're just going to change the zoom slightly to be 17, which works for the demo that we're doing. We'll save this. We'll make sure that it's working. You can see that it's already centered us down here. When it loads <i>Directions,</i> it will take us down here. I wouldn't worry about these errors. It seems to occur because we're working in Dev mode within React, and it's doing some hot module reloading and stuff like that. I haven't seen them on Production, so I think we're fine to ignore those. Okay, we've centered the map. The next thing we need to do is set up a Three.js overlay. I am using a package from Ubilabs,<i> threejs-overlay-view,</i> to make this a little bit easier to work with. You can do this directly using the WebGL Overlay View from Google, but when you use that, you need to set up a few things, like the camera, the lighting, the scene, and stuff like that. This package takes care of that for us, so it makes that a little bit easier. We're going to store that in a <i>ref</i> because we don't want this to cause rerenders, so we're going to say<i> const overlayRef = useRef</i> like that. Then we can come here. Because we only ever want one <i>overlay,</i> we can just say if there's not one already, <i>overlayRef.current,</i> then we'll set one up. We'll say <i>overlayRef.current =,</i> and we're going to create a new instance of this. It wants to know where to center the map, so we're going to say <i>mapOptions.center.</i> Just like that. It's not where to center the map, but it's where to point the camera from. I suppose what we could also do is use this here, but this one works. We just need to tell it where to center itself. Once we have that, the next thing we need to do is tell it what map to attach itself to. We'll say <i>overlayRef.current,</i> and then we'll call <i>setMap</i> on that, and we'll pass in the <i>map.</i> Now we have the overlay, nothing's changed because we haven't put anything on the overlay yet, so that's what we're going to work on next. We're going to need to access the scene so that we can add things to the scene. You do that by saying <i>overlayRef.current,</i> and then we say <i>getScene.</i> We call that function here. The first thing we're going to work on, is rendering that path on the map. We need to get <i>points</i> because when we're working in Three.js, we want X, Y, and Z coordinates, but all we have right now in the <i>route</i> are latitude and longitude, so we need to convert that. We're going to take the <i>route</i> and map it, so iterate over each latitude and longitude, and convert it into the right format. We'll say we've got a point in the path. We want to take that <i>p,</i> and we want to call <i>overlayRef.current,</i> and they give us a function called <i>latLngAlt-- altitude-- ToVector3</i> to give us those X, Y, Z coordinates, and we're going to pass in <i>p.</i> If we get that back... It's complaining because I didn't have an equals. Why don't we just <i>console.log</i> what it looks like? <i>points</i> here. If you come down here, you can see we started with <i>console.log(route).</i> We started with latitudes and longitudes, but now we have a whole bunch of vector 3s, which give us the X, Y... Z is just 0, but gives us the X, Y coordinate onto the map. We're making progress although we haven't seen that progress yet. We've got all these points. What we're going to do is, from those points, create a curve. We'll call it <i>curve.</i> It's a Catmull. <i>CatMullRomCurve3,</i> and we create that from <i>points.</i> What is this? It's basically a line that runs itself through all of the points you give it. I imagined an Olympic skier, a downhill skier, they have to pass through all of those flags. If you were going to look at the route that the skier takes to get down the hill going through all of the correct checkpoints, that's what <i>CatmullRomCurve</i> does: given the checkpoints, create a curve that runs through them all. From that <i>curve,</i> we can start to work on rendering out the track onto the map or onto the Three.js overview. We're also going to store this in a <i>ref,</i> so we'll say <i>trackRef = useRef.</i> Because this may rerender, we're going to do this little thing where we first to see if there's a previous <i>track</i> already set up, so if there's a <i>trackRef.current,</i> we're going to remove it from the <i>scene</i> before we add a new one to it. This is especially an issue when you're doing local development, and it's hot module reloading and constantly rerendering. You can end up with a whole bunch of tracks on your map. This will help us out here. We're going to say<i> scene.remove(trackRef.current).</i> Just like that. Now we've got a clean slate, and we can create a new one. We're going to say <i>trackRef.current,</i> and that is going to call a function. We'll call it <i>createTrackFromCurve,</i> and we'll pass in the <i>curve.</i> This doesn't exist. We're going to come down here and just create that, so <i>function createTrackFromCurve.</i> It receives the <i>curve.</i> Now, we need to go create that. What do we actually do? We're going to take this <i>curve.</i> We started with ten points or something like that. Well, that could lead many gaps or space between points, so we're going to ask the <i>curve</i> for new points, but we're going to ask for ten times as many. We'll say, "Curve, give us new points"-- so <i>getSpacedPoints.</i> How many do we want? Let's say we want the <i>curve.points.length,</i> so however many there were at the beginning, and we're going to multiply them by ten. You could multiply by four, and that would give you a smoother track to run your car along, but I found that, for us, this works well. Now we need to take these new points, and we need to get them in a slightly different format, so rather than the vector 3s... Let's actually take a look at what they look like. <i>points.</i> We have 101 vector 3s now. In order to draw the line, it doesn't want it as vector 3s. It wants something called "positions." We're going to convert from points to positions, and we're going to say <i>points.map</i>-- so map each point. Take the point, which is a vector 3, and convert it to an array, so that will flatten that out. But then we need to take all these array of arrays, and we need to flatten them. If you take a look now... <i>positions.</i> We should just have a massive array of positions, so 303 elements of numbers, but you can see that there are X, Y, 0 for Z, X, Y, 0 for Z, so we've just done that conversion. With the positions, we can now create a line. We're going to return that line directly, and we're going to create an instance of <i>Line2.</i> Why <i>Line2</i> and not <i>Line?</i> Well, it turns out that the line that comes from 3 itself, it can't ever have a width to it. You can't make it a fatter line for some reason. There's a bug. I looked into it, and I didn't quite understand, but they do give you these examples of something called "Line2," which does work, and you can make fatter, so a lot of people, in their examples, end up using<i> Line2</i> from the examples rather than from the library itself. Strange, but it is what it is. All right, let's get back to where we were. There's a lot of code here. The line, the first thing it needs is a geometry, basically where to draw the line. We'll say <i>new LineGeometry,</i> and to <i>LineGeometry,</i> we don't need to pass anything, but that we need to set it from <i>positions.</i> That's where we're using this big array of numbers here. Any time you're working with an object in Three.js, typically there's the geometry, sort of what it looks like from a spatial perspective of where on the map it is and how big and its start and stop, but you also need material, how is it going to appear. We'll create a <i>LineMaterial.</i> We're going to pass in the <i>color.</i> You'll see in a sec this is like a yellowish color, but it's ffb703. I mentioned you can't set the thickness or the line width easily on the <i>Line.</i> On <i>Line2,</i> it does work well, so you can set the line width, and we'll set that to 8. You can figure out what looks good and what doesn't. <i>createTrackFromCatmullRomCurve.</i> If we come back up here, we have placed this in our <i>ref,</i> but it's not in our <i>scene</i> yet. Anything you add needs to be placed into the <i>scene.</i> Otherwise, you'll never see it. We need to take the <i>scene</i> and add the <i>track</i> to it, so <i>trackRef.current</i> like that, I don't think it will quite show up yet. We need to do something small in order for it to work. What we need to do, basically, is the Three.js overlay is constantly rerendering, and we access that rendering loop by <i>overlayRef.current,</i> so access the overlay, and there's an <i>update</i> function. In other places, you may see this called the <i>draw</i> function, but it's the thing that's run every time. What we're going to do is access the <i>trackRef,</i> and we're going to get it to <i>material,</i> and we're going to set its resolution or the right size, and we'll set that from the <i>overlay,</i> the <i>current</i> because it's a <i>ref,</i> and the viewport size. <i>getViewportSize</i> like that. This may also not render it quite yet. Ah, it does! Great! There we do. But let me just add one line of code that I know we're going to need. We need to ask the <i>overlay</i>-- so <i>overlayRef</i>-- to request a redraw because any time you're changing things on it, you need to rerender that. The track will never move, but the car we're going to place on the track will move, so we're going to need this in the future. If I refresh, we get a beautiful track! If you wanted to, you could change its color. This is the yellow I set up. You could change its thickness if you wanted to cover more of the street, but we've rendered it out. That's fantastic. One thing I'm going to do before I forget is... Actually, this needs to be inside of <i>update.</i> Just like that. But what I'm going to do is return a cleanup function from this <i>useEffect</i> hook. Any time... When a <i>useEffect</i> hook runs, you can return a function, and when a subsequent <i>useEffect</i> is triggered, it calls the prior cleanup method, so you can clean up anything before doing the next thing. What we're going to clean up is we're going to take the <i>scene</i> and just remove, again, <i>trackRef.current.</i> You may say, "Leigh, you did this up here." I'm just being extra cautious. I don't want the track on it if we're going to be redrawing a new track. Good to go. Refresh here. There we do. We have this drawing itself on the map. Okay. Now, let's place a car on the map so that we can then eventually animate that car along the track. We're going to come here. If this here is dealing with the track, this here is dealing with the car or the model. What we're going to do is we're going to call a function called <i>loadModel.</i> This doesn't exist yet. We need to go and create this <i>loadModel</i> function. We're going to come down here. We'll just place it right below, <i>function loadModel</i> like that. It's an async function because it's going to have some <i>Promises</i> in it. Just like that. What we're going to do is we're going to, first, create an instance of our <i>loader.</i> <i>loader</i> comes from Three.js, and we're going to be using the new instance of it, <i>GLTFLoader.</i> We don't need to pass anything to it, but then we can tell our <i>loader</i> to <i>loadAsync.</i> We are going to load our car from this <i>public</i> folder, <i>low_poly_car,</i> this <i>scene.gltf</i> file. You can also check out the license of where to find this. If we come back here, that would be accessed by <i>low_poly_car/scene.gltf</i> like that. This is an <i>async</i> function, so we're going to say let's get the <i>object</i> and <i>await</i> it to be loaded. Then once we have the <i>object,</i> we'll get the <i>group</i> within it. This might be better off called the <i>model,</i> but we'll stick with <i>object.</i> That's what we're loading. We ask <i>object.group.</i> A grouping is just you're combining a bunch of different individual pieces. You'll see the car is made up of wheels, of hoods, of windshields, and whatnot, so all of those are inside of the <i>group.</i> If we access that, now with the <i>group,</i> we need to set its size a little bit. Otherwise, it may not show up correctly on the screen. I found for this car, it's shrinking it in half. Otherwise, it's a massively large car, but if you're using different models, you may have to work with different scale or sizes. Just like that, and we'll return the <i>group.</i> If we come up, we've got our <i>loadModel</i> here, but remember, it's an <i>async</i> function because it has <i>awaits</i> in it, so it's going to give us a <i>Promise.</i> What we're going to do is we're going to say<i> then</i> we can access the <i>model</i> like that, and we need to do something with the model. Very much like the <i>track,</i> we're going to store the car in a <i>ref.</i> We'll say <i>carRef = useRef</i> like that. We're going to double check we don't have two cars, so we're just going to say <i>carRef.current.</i> We're going to ask the <i>scene</i> to remove the previous car-- <i>carRef.current</i>-- before we add a new car. We're going to say <i>carRef.current</i> is equal to this model, so what we receive from our <i>Promise</i> when it resolved. Then we need to take that and add it to the <i>scene.</i> We can say <i>scene.add carRef.current.</i> Just like that. Okay. I'm not quite sure if it will... Let's see. Looks like I made an error. <i>group.scale.</i> <i>object.group.</i> Error reading... Okay. Well, let's go figure out what that is. We come down here. We <i>await</i> this, and that gives us the <i>object.</i> Then the <i>object</i>... We're actually asking for the <i>scene</i> is what I think I made a mistake of. If we wanted to, we could rename this to <i>scene</i> so that it makes a little bit more sense, but the <i>scene</i> is very similar to the <i>group</i> in the sense of it's just a grouping or a combination of everything involved to render the car. We come back. We don't have any errors yet, but the car is nowhere to be seen. Let's work on placing it in the correct position. What we're going to do, first of all, is because we're going to be animating this car, let's set up a couple variables that we need to both keep track of the animation, but also what a good position is of the car if it were facing forward. We'll go into that a little bit in a second. We're going to say <i>ANIMATION_MS,</i> and we'll say it's going to take 10 seconds or 10,000 milliseconds to complete its route along the path. We're also going to set up this variable that I call <i>FRONT_VECTOR.</i> It's a vector 3, and because we're going to be rotating the car in accordance with the angle of the track, we always need a starting point to base that rotation off of. These numbers here of an X of 0, a Y of -1, and a Z of 0, I figured out by trial and error. I tried +1, -1. I found this is the one that gave me the angling that I wanted. We're going to come down here inside of our <i>update</i> function, and we're going to work on animating the car. First, because the car is loaded async, it may not exist, so let's just check: do we have a car on our map? <i>carRef.current.</i> Okay. If we do, we need to figure out how far along the path or what is the percentage that the car has traveled. We'll call that the <i>progress.</i> Our <i>progress,</i> what we can say is we're going to use a function from JavaScript called <i>performance,</i> which gives us the time, and we can get the Modulus-- the remainder-- using how long we want to animate. Then if we divide that by the time we want to animate, it gives us the percentage of how much of the animation it has completed. Why don't we just render <i>progress</i> out and let's see what it's doing? Let's make sure that we've got this number showing up. Okay, you can see it's 30%, 40%, 50%, 60%. Once it gets to 100%, it kicked back to 10%, 20%, so it would be animating. That's the progress right there. Once we have the progress or how far along, we can ask the <i>curve</i> to get the "point at." If you're 50% through the curve, what is the point at that level of progress? Or if you're at 1% or 99%, give me the point that corresponds to that percentage of progress. Then we can copy that point into the <i>carRef.current.position.</i> The second parameter here, it copies the point into where the car is. If we reload now, we have the car going along the path although it's hard to travel in this car when it's on its nose like that, but that's where we got to now work on rotating the car in the correct way. First things first, if I just take the car-- so <i>carRef.current</i>-- and we rotate the X-axis, and it wants an angle, and the angle that I found works for this is Pi divided by two, which I believe is a 45 degree or 90 degree, but you can play with that. If we reload, it's doing weird things. It's going a bit haywire, but I think if we apply this next thing-- It's constantly flipping itself around, but if we apply this other rotation to keep it in line with the path, this one will make a lot more sense. Why don't we keep that there like now? We'll add it back in in a second. But what I wanted to do before I forget is to just do the scene cleanup of the car, so we'll say <i>carRef.current</i> like that. Okay. Now., what we're going to do is try to rotate the car in accordance with where it's at along that curve. We can say <i>carRef.current</i>-- so access the <i>carRef</i>-- and one of the ways that you can rotate things in 3D space is called a <i>quaternion.</i> I've looked at the Wikipedia page, and to be honest, it's a little past my understanding, but it gives us a function of <i>setFromUnitVectors.</i> Basically, rotate it according to two points. The first point that we're going to use is this <i>FRONT_VECTOR</i> or the front of the car. We'll come down here, and we'll say <i>FRONT_VECTOR,</i> and then rotate it according to where we're at along the curve, so get the tangent, or the angle, at the level of progress that we're at. Given these two things, we can come back here and refresh. The car is now traveling along the path, and you can see that it's turning itself when it gets to the corners. Now, if I reapply this animation right here, and we just refresh, we've got it animating along the track, and it's turning along the corners, and it's always restarting once it gets to the ten second animation, so it starts, it's rotating, and then it's starting again. We've got where we wanted to end up. If we just rehash what we've done-- there's a lot of lines of code here, we're at almost 180 lines of code-- but if we just break it down, starting at the top, we loaded the Google Maps script. From that, we could render the map onto the screen. Once we have the map loaded, we could render our <i>directions.</i> The <i>directions</i> not only to display them, but it's called the "directions service" to get the points to get from the origin to the destination. That got passed up back here in <i>state,</i> in <i>route,</i> and then given the <i>route,</i> we could then do the <i>animation.</i> Then what the <i>animation</i> does is it, first, starts by creating a Three.js overlay. Then onto that overlay, we could place the <i>track,</i> which was based on the <i>points</i> and creating a <i>CatmullRomCurve.</i> Then we added that to the <i>scene,</i> which is what's visible on to the Three.js overlay. We loaded our car model and place that onto the <i>scene</i> as well. Then we took care of every time it refreshes, so every time it rerenders, which is typically either 30 times a second, 30 frames a second, or 60 frames a second, and we're constantly taking the car and repositioning it along that curve, given the percentage of progress it's made-- ten seconds to start from beginning to end. Hope you enjoyed this video. You saw a lot of neat functionality that I enjoyed playing with a lot. Take care. Bye. ♪ [music] ♪
Info
Channel: Google Maps Platform
Views: 9,564
Rating: undefined out of 5
Keywords: Getting started with using WebGLOverlayView with React, How to use WebGLOverlayView with React, WebGLOverlayView with React overview, how to combine Three.js with Google Maps Platform using the WebGLOverlayView, Three.js with Google Maps Platform using the WebGLOverlayView, what is three.js, WebGLOverlayView, three.js, Google Maps Platform, Geocasts, Leigh Halliday
Id: kxAwkT9M6rM
Channel Id: undefined
Length: 44min 55sec (2695 seconds)
Published: Thu Mar 23 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.