background
Recently, I am implementing a 3D sandbox game. The basic function is to build buildings in a 3D plane. You can add or edit buildings in the scene, and then there is a character model in the plane. between buildings.
When realizing the walking function of characters, we naturally think of taking the coordinates of the end point and the starting point, and then connecting them in a straight line to walk. This idea is really good when there are no buildings, but once it appears on the map After the building, you will find: Meow's character is wearing the mold!
At this point, the journey to find an obstacle avoidance solution begins.
Use the obstacle avoidance function that comes with Babylon.js
Since our project is developed using the framework, adheres to the principle of "can write it yourself and never write it yourself", our first idea is to use the obstacle avoidance function that comes with Babylon.js .
introduce
Briefly introduce the obstacle avoidance function that comes with Babylon.js. It is an extension function of the framework and needs to be used in conjunction with RecastJS dependencies.
Use RecastJS for Navigation Mesh (Navigation Mesh) generation. Then automatically find the way and avoid obstacles through the Crowd Agents module in RecastJS.
You may have a question here, what is a navigation mesh, here is a definition and introduction I saw in other blogs: "Navigation Mesh (Navigation Mesh), also commonly known as walking surface, is a kind of A polygon mesh data structure for navigating wayfinding in complex spaces, marking where to walk."
A navigation mesh is made up of many convex polygons. In vernacular terms, the map is cut into N parts, each of which is a convex polygon, We call a polygon a Poly .
Then when the character walks in the navigation grid, it will judge whether the starting point and the end point are in the same Poly, if so, directly connect the starting point and the end point into a straight line, which is the character's walking route; otherwise, you need to use The corresponding path-finding algorithm calculates the path, calculates which Poly the character needs to pass through, and then uses these Poly to calculate the specific path.
application
When I knew that Babylon.js had such a function, my heart was ecstatic, so I followed the example on the official website and pressed the keyboard, refreshed the page, added a building, clicked on the map, and looked forward to The character model automatically bypasses the building, resulting in:
What happened?
Experience tells me that the navigation mesh is not generated normally. Fortunately, RecastJS provides a variety of parameters so that I can adjust the generation rules of the navigation mesh.
So, after almost a whole day of parameter adjustment, I finally found that I was too naive, and the result did not change at all. Only when the added obstacle is very small, the character's obstacle avoidance function can successfully take effect. When the volume of the obstacle is slightly larger, the character will go straight through the building to the end.
At the time, my thought was: Okay, since the parameter adjustment is useless, then I will go to the source code of RecastJS to see how it generates the navigation mesh and what causes it to generate the navigation mesh abnormally.
So, I moved to github. I don't know if I don't search, but I was shocked and found that RecastJS was originally a library developed in C language. Later, I don't know who converted it into a Javascript version and sent it to npm. That is to say, We can't see Recast's Javascript version source code.
Well, well, using the obstacle avoidance function that comes with Babylon.js is completely blocked.
Self-built navigation grid and search algorithm
Since the built-in functions are useless, and there is no better solution in the Babylon.js community ecosystem, we can only rely on ourselves to create a set of navigation grids and search algorithms suitable for the current scene.
Let's take a look at the overall idea and process first:
- Get map data and generate appropriate navmesh
- Get the starting and ending points of the character's walk
- Find the shortest path from the start point to the end point in the navigation mesh using a search algorithm
- The operating characters follow the shortest path
Choose the right search algorithm
Let’s talk about the search algorithm first, and the search algorithm for pathfinding. The first thing that comes to mind should be the famous breadth-first search algorithm.
Breadth-first search
The breadth-first search algorithm is a very common pathfinding algorithm, which is widely used in various computer scenarios. For example, the drawing board graffiti function of Windows is implemented by the breadth-first algorithm.
We can simply divide the entire map into N 1X1 square grids. The basic principle of the breadth-first algorithm is very simple, that is, starting from the starting grid, you can move up, down, left and right each time. The green marked point in the image below is the start point, and the red marked point is the end point.
each round of exploration, the squares explored in this round will be marked as Frontier , which is the green square in the picture below.
Then the algorithm will start from these boundary squares and continue to the next round of exploration one by one, until the end square is found during the exploration process, the algorithm will stop exploring.
In each exploration, we need to record the of the , which is to form a structure similar to a linked list. After reaching the end point, we can find the corresponding shortest path according to the record of this origin.
In addition, the path grid explored in each round should be marked with before the next round of exploration starts to let the algorithm know that the grid has been traversed. Skips are not included in subsequent exploration rounds. The gray grids in the figure below are the grids that are marked and no longer explored.
In fact, the breadth-first algorithm is very simple to implement and has a wide range of application scenarios, but it also has obvious shortcomings, that is, it is stupid. In the worst case, it needs to traverse the entire map to find the target point, so it is easy to move too many times. cause performance issues.
A-Star algorithm
In order to solve the performance problem of the breadth-first algorithm, we introduced the heuristic search A-Star algorithm . Unlike the breadth-first algorithm, we do not explore all the boundaries in each round of search. Instead, will choose the direction with the lowest cost to explore , so it is with a certain directionality , and the direction it goes forward depends on the position of the lowest cost block in the current bounding block.
The cost is divided into two parts, one part is the current distance cost, or current cost (f-cost) , which indicates the number of paths you have traveled.
Another part of the cost is the estimated cost (g-cost) , which indicates how many steps it takes to get from the current block to the destination block. Since it is called the "estimated" cost, it is not an exact value of , this estimated value Mainly used to guide the algorithm to preferentially search for more promising paths.
Here are two commonly used estimated costs:
- Euler Distance : The straight-line distance from the current grid to the end point, expressed by a mathematical formula is Math.sqrt((x1 - x2)^2 + (y1 - y2)^2)
- Manhattan Distance (Manhattan Distance) : refers to the sum of the distance between the current grid and the end point in the vertical and horizontal directions, expressed by a mathematical formula is |x1 - x2| + |y1 - y2|, the calculation of Manhattan distance No prescription required, fast speed and high performance
When we know the current cost and estimated cost of a bounding box, we can get its total cost by adding these two values, namely:
Total cost = current cost + estimated cost
In each round of pathfinding, we search for the block with the lowest total cost in the current boundary for exploration, and record its origin and mark itself similar to the breadth-first algorithm. Until the end of the exploration, we can obtain the corresponding block through the A-Star algorithm. the shortest path.
Compared with the breadth-first algorithm, the A-Star algorithm has a certain directionality, so in general, it has a lot less useless exploration than the breadth-first algorithm, and the number of traversed map grids is also much less, so in general, the overall performance is would be better than the breadth-first algorithm.
At this point, we have a suitable search algorithm, the next step is to see how to generate our navigation map.
Build your own navigation mesh
Before we talk about how to build our own navigation mesh, we must understand a concept, that is, the models in the 3D scene are composed of triangles one after another, so when building the navigation mesh, we must also follow this principles to design.
How to generate navigation mesh in Recast
So we can briefly understand what steps are required to build a navigation mesh in the Recast mentioned above? In short, there are six steps in total.
- Scene model voxelization (Voxelization) , or "rasterization" (Rasterization). To put it simply, it is to convert triangular surface data into pixel information (also called voxel information), which can be understood as converting one triangular surface into one by one lattice information.
- filters out the walkable surface (Walkable Surface) , that is, the data of the walkable surface at the top of the voxel is calculated from the voxel information obtained in the first step.
- generates Region . After obtaining the walkable surface, we cut this surface into regions as large as possible, continuous, non-repetitive and without holes in the middle through some algorithms. These regions become Regions.
- generates Simplified Contours , calculates the edge information of each Region through the Region information obtained in the previous step, and then simplifies the edge contour through some simplification algorithms, we call this simplified contour (Simplified Contours).
- generates Poly Mesh . By dividing the simplified contour, we divide each simplified contour into multiple convex polygons. We can call each convex polygon a Poly, which is the basic unit in the pathfinding algorithm.
- generates the Detailed Mesh , and finally we triangulate each Poly and divide it into multiple triangles to generate the navigation mesh that our final search algorithm needs.
It is worth mentioning here that after the scene model generates Region in the third step, the three-dimensional scene has actually been simplified into a two-dimensional , which facilitates some subsequent calculations and operations. But at the same time, this step also causes the model to be unable to be completely perpendicular to the surface when moving, and can only remain perpendicular to the xOz coordinate plane.
How to generate the navigation mesh according to the actual situation
In our sandbox game this time, the map and buildings are actually quite simple. The map can be simply viewed as a 64*64 square plane, and the buildings can also be simplified into simple cubes.
Referring to the generation steps of the above Recast grid navigation, what is finally generated is a non-overlapping grid data.
Since our sandbox map is not particularly large, the way of generating our navigation mesh is actually very simple, which can be summarized in one sentence: directly uses the vertex data of the 64*64 square plane as the original navigation mesh data, Then exclude the vertex data that is in contact with the square projection of the building on the ground, and finally the mesh formed by the vertex data is the navigation mesh we need.
We will talk about this generation method in more detail later in the article, but here the reader has a basic concept.
Combination of A-Star Search Algorithm and Navigation Mesh
So far, we have basically understood the principle of the A-Star search algorithm and how the navigation mesh is generated. It is time to assemble them together.
First of all, when we talked about the A-Star search algorithm, we assumed that the map was composed of 1X1 square grids, but in reality, since the objects in the 3D scene are composed of triangular faces, , so our navigation The mesh is also composed of triangles, so some calculations in our A-Star algorithm should be adjusted accordingly.
When calculating the value of the total cost, as mentioned above, we will divide the total cost into two aspects: the current cost and the estimated cost. In terms of the current cost, we still calculate according to the number of paths that have been traveled.
The estimated cost is calculated using Euler distance. The calculation here will change from the calculation of the distance between square grids to the calculation of the distance between triangular faces. Therefore, we need to calculate the center point of each triangular face, and the distance between the grids. Will use the distance between the center points instead, and the value of the Euler distance is the distance from the midpoint of the boundary triangle to the midpoint of the end triangle.
Next, we need to process the map vertex data. filters out the vertices in the vertex data that intersect with the building projection to , and then finds the center point of each triangle formed by the filtered vertex data, and the triangle corresponding to this A list of neighboring triangles (neighbours) and their neighbor data (portals) .
Finally, we pass in the starting point and end point data. The A-Star algorithm will find the corresponding triangle face data of this point according to the starting point data and the ending point data, and then start from the starting point triangle and find the path according to the cost value corresponding to its adjacent triangles. , until the end triangle is found, return path data (paths) .
Finally, we can achieve the corresponding obstacle avoidance effect by making the characters move correspondingly along the returned path data.
Optimization in practical application
In fact, so far, we have basically implemented the pathfinding algorithm of the sandbox game, but during the test, we found that because the obtained path passes through too many triangle nodes, the character appears too much during the walking process. The inflection point is , and the paths between some triangles can obviously be represented by a straight line, but in fact the middle needs to pass through the midpoints of many triangles.
Therefore, we introduced the rope-pulling algorithm to solve the problem of too many path inflection points through the rope-pulling algorithm. We will not introduce the rope-pulling algorithm too much here. Those who are interested can use the article The funnel algorithm of rope-pulling algorithm to carry out To understanding.
After adding the rope-pulling algorithm, the path of the character's walking has also been optimized, and the final actual effect is basically the same as the ideal effect.
Subsequent items to be optimized
Our current pathfinding algorithm is actually not the optimal solution in my mind. Because in the generation process of this navigation grid, since we abstracted the entire map into the form of a grid, leads to too many nodes in the graph , which will be very inefficient to traverse when the map is large.
Therefore, we need to simplify the grid map into waypoints with fewer nodes, , to delete and merge some unnecessary points and faces in the navigation grid.
In addition, we can also add the concept of octree , and use the x, y, z axes to divide the space into 8 parts in the three-dimensional space to optimize the search efficiency.
In view of the length of the article, I will not expand these contents too much here. I hope that readers will continue to conduct in-depth research after reading with a studious attitude.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。