blog: 使用导航网格实现寻路
All checks were successful
🚀 Build and deploy by ftp / 🎉 Deploy (push) Successful in 11m37s
All checks were successful
🚀 Build and deploy by ftp / 🎉 Deploy (push) Successful in 11m37s
This commit is contained in:
parent
f3a25f7a46
commit
44174c5f36
@ -1 +1,41 @@
|
|||||||
{"arch-linux":3,"环境搭建":3,"vps":3,"linux":1,"pve":2,"xray":2,"acme":1,"acmesh":1,"docker":3,"docker-compose":1,"内网穿透":1,"github-actions":1,"cicd":1,"de":1,"debian":1,"blog":1,"markdown":1,"nextjs":1,"tailwind-css":1,"verdaccio":1,"self-hosted":3,"caddy":2,"registry":1,"nodejs":1,"zerotier":1,"tailscale":1,"sd-wan":1,"nat":1,"frp":1,"react":1,"baas":1,"appwrite":1,"nhost":1,"supabase":1,"sni":1,"tls":1,"reverse-proxy":1,"反向代理":1,"vless":1}
|
{
|
||||||
|
"github-actions": 1,
|
||||||
|
"cicd": 1,
|
||||||
|
"docker": 3,
|
||||||
|
"react": 1,
|
||||||
|
"baas": 1,
|
||||||
|
"self-hosted": 3,
|
||||||
|
"appwrite": 1,
|
||||||
|
"nhost": 1,
|
||||||
|
"supabase": 1,
|
||||||
|
"pve": 2,
|
||||||
|
"de": 1,
|
||||||
|
"环境搭建": 3,
|
||||||
|
"debian": 1,
|
||||||
|
"arch-linux": 3,
|
||||||
|
"vps": 3,
|
||||||
|
"zerotier": 1,
|
||||||
|
"tailscale": 1,
|
||||||
|
"sd-wan": 1,
|
||||||
|
"nat": 1,
|
||||||
|
"frp": 1,
|
||||||
|
"verdaccio": 1,
|
||||||
|
"caddy": 2,
|
||||||
|
"registry": 1,
|
||||||
|
"nodejs": 1,
|
||||||
|
"sni": 1,
|
||||||
|
"tls": 1,
|
||||||
|
"reverse-proxy": 1,
|
||||||
|
"反向代理": 1,
|
||||||
|
"xray": 2,
|
||||||
|
"vless": 1,
|
||||||
|
"blog": 1,
|
||||||
|
"markdown": 1,
|
||||||
|
"nextjs": 1,
|
||||||
|
"tailwind-css": 1,
|
||||||
|
"acme": 1,
|
||||||
|
"acmesh": 1,
|
||||||
|
"docker-compose": 1,
|
||||||
|
"内网穿透": 1,
|
||||||
|
"linux": 1
|
||||||
|
}
|
||||||
|
84
data/blog/using-navigation-mesh-for-pathfinding.mdx
Normal file
84
data/blog/using-navigation-mesh-for-pathfinding.mdx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
title: 使用导航网格实现寻路
|
||||||
|
date: '2023-09-12'
|
||||||
|
tags: ['Three.js', 'Navigation Mesh', '3D', 'Game', 'Path Finding']
|
||||||
|
draft: false
|
||||||
|
summary: 本文结合 three-pathfinding 项目的源代码,简要分析如何使用导航网格实现寻路功能。 three-pathfinding 是一个 Three.js 导航网格寻路库。
|
||||||
|
images: ['https://s3.ivanli.cc/ivan-public/uPic/2023/mZ9HNo.jpeg']
|
||||||
|
---
|
||||||
|
|
||||||
|
本文结合 [donmccurdy/three-pathfinding](https://github.com/donmccurdy/three-pathfinding) 项目的源代码,简要分析如何使用导航网格实现寻路功能。 three-pathfinding 是一个基于 [PatrolJS](https://github.com/nickjanssen/PatrolJS) 实现的 Three.js 导航网格寻路库。
|
||||||
|
|
||||||
|
在 Three.js 中使用导航网格实现寻路功能,主要用到以下三个部分:
|
||||||
|
|
||||||
|
- 导航网格(Navmesh, Navigation Mesh):寻路用的地图
|
||||||
|
- A\*(A Star)搜索算法:用于寻路
|
||||||
|
- 漏斗算法(Funnel Algorithm): 用于在二维平面找到绕过障碍物的最短路径
|
||||||
|
|
||||||
|
## 导航网格
|
||||||
|
|
||||||
|
导航网格由若干个可供角色行走的、相邻的凸多边形组成,在我们的用例中,是三角形。导航网格的作用是寻路算法提供所需的顶点数据的,本身并没有任何算法。导航网格可以通过 [UPBGE](https://tl.ivanli.cc/m/28) 生成,生成后就是一个 Mesh 物体,所以顶点数据可以直接通过 GLTF 等格式分发。
|
||||||
|
|
||||||
|
## A Star 搜索
|
||||||
|
|
||||||
|
网络上有很多文章介绍这个算法:
|
||||||
|
|
||||||
|
- [A Star Algorithm 总结与实现 | Cheng Wei's Blog](https://shiori.ivanli.cc/bookmark/47/archive/)
|
||||||
|
|
||||||
|
本文结合实际应用再简要地说明下。
|
||||||
|
|
||||||
|
首先,我们除了会给算法传入三角形的定点数据,还会传了起点和终点,这里分为三个情况:
|
||||||
|
|
||||||
|
#### 起点和终点顶点都在同一个三角形之中
|
||||||
|
|
||||||
|
不需要执行 A Star 搜索算法,直接将两点用直线连接就是目标路径。
|
||||||
|
|
||||||
|
#### 起点或终点不在任意一个的三角形之中
|
||||||
|
|
||||||
|
要么通过其他算法将原始的起点和终点在三角形中找到最接近的点,要么放弃这次寻路。因为这是不可处理的意外情况。
|
||||||
|
|
||||||
|
#### 起点和终点在不同的三角形之中
|
||||||
|
|
||||||
|
这样就能正式执行 A Star 搜索算法了。
|
||||||
|
|
||||||
|
### 数据准备
|
||||||
|
|
||||||
|
[Builder.\_buildNavigationMesh()](https://github.com/donmccurdy/three-pathfinding/blob/538b57b65e78b37fe033ff845a09659ebed426dd/src/Builder.js#L150) 方法将从 NavMesh 提取所有三角形的顶点,并通过三角形的三个顶点的 x 分量作为 ID,找到了每个三角形相邻的三角形数组。
|
||||||
|
|
||||||
|
[Build_buildPolygonGroups()](https://github.com/donmccurdy/three-pathfinding/blob/538b57b65e78b37fe033ff845a09659ebed426dd/src/Builder.js#L103) 方法将从上一个方法中返回三角形及其相邻的三角形作为一个参数,返回了若干组三角形,每组内的三角形都能相互联通,也就是在这组三角形上的任意两点都能找到连接的路径。
|
||||||
|
|
||||||
|
最后每个三角形会被结构化成 Node,长这样:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: number, // ID
|
||||||
|
neighbours: nb[], // 相邻的三角形的 id 数组(Node.id 数组)
|
||||||
|
vertexIds: vertexIds[], // 顶点 ID,使用三个顶点的 X 分量组成
|
||||||
|
centroid: Vector3, // 重心
|
||||||
|
portals: [number, number][] // 与其他三角形公用的边的顶点的索引数组,
|
||||||
|
// 一般情况下是两个,即一个边与另一个三角形相邻,但是也不排除会三个边都相邻
|
||||||
|
// 如果三边都相邻,则是 [number, number, number]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开始使用 A Star 搜索路径
|
||||||
|
|
||||||
|
A Star 算法怎么跑的本文就不赘述了。
|
||||||
|
|
||||||
|
执行 [Pathfinding.findPath()](https://github.com/donmccurdy/three-pathfinding/blob/abc331195143d7ea1242debed4b52500bda8b7fe/src/Pathfinding.js#L106) 时,需要传入 zoneID 和 groupID。通过前面对数据准备的分析,我们知道同组的三角形是相通的,只要保证起点和终点都在这组三角形的面上,正常情况下 findPath 就能求出路径了。
|
||||||
|
|
||||||
|
A Star 算法本质上是在若干个点之间求出一组点,连接这些点就是导航路径。这里使用三角形的重心作为这个点。
|
||||||
|
|
||||||
|
通过 A Star 算法搜索出路径后, 会获得一组有序的 Node。
|
||||||
|
|
||||||
|
## “拉绳”
|
||||||
|
|
||||||
|
A Star 算法寻得的路径是比较粗粒度的路径,接下来使用漏斗算法来拉出一条最短路径。
|
||||||
|
|
||||||
|
[Channel.js](https://github.com/donmccurdy/three-pathfinding/blob/364fdc5e6c41c6f3835d881edd00565c45ab0401/src/Channel.js) 里便是使用漏斗算法来获取最短路径。值得注意的一点是,这个算法适合平面,并不适合有高度落差联通的导航网格。
|
||||||
|
|
||||||
|
漏斗算法参考这篇文章:[图解NavMesh寻路中的漏斗算法 - PointerSMQ - 博客园](https://shiori.ivanli.cc/bookmark/48/archive/)
|
||||||
|
|
||||||
|
![](https://s3.ivanli.cc/ivan-public/uPic/2023/JJjp9f.png ' =666x428')
|
||||||
|
|
||||||
|
漏斗算法能够让最终路径在绕过障碍物的同时,保证路径最短。从上面的文章中,可以总结一个核心逻辑,每次生成的路径如果超过左边界或右边界,就会增加一个节点,并从此处构造新的漏斗。直到到达终点。
|
8909
pnpm-lock.yaml
Normal file
8909
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user