js A*寻路算法之3d简单化
1 /* SeekPath A*寻路 2 3 parameter: 4 option: Object{ 5 angle, timeout, maxHeight, size, lenX, lenY 6 } 7 8 heights: Array[Number] 9 10 attribute: 11 size: Number; //每个索引的大小 12 lenX: Number; //最大长度x (设置此属性时, 你需要重新.initMap(heights); .range 会被重置) 13 lenY: Number; //最大长度y (设置此属性时, 你需要重新.initMap(heights); .range 会被重置) 14 15 range: Box; //本次的搜索范围, 默认: 0,0,lenX,lenY 16 angle: Number; //8四方向 或 16八方向 默认 16 17 timeout: Number; //超时毫秒 默认 500 18 maxHeight: Number; //相邻可走的最大高 默认 6 19 20 //只读属性 21 success: Bool; //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false) 22 path: Array[x, y, z]; //存放.run()返回的路径 23 map: Map; //地图的缓存数据 24 25 method: 26 initMap(heights: Array[Number]): undefiend; //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数 27 run(x, y, x1, y1: Number): Array[x, y, z]; //参数索引坐标 28 29 demo: 30 const sp = new SeekPath({ 31 angle: 16, 32 timeout: 500, 33 maxHeight: 6, 34 size: 10, 35 lenX: 1000, 36 lenY: 1000, 37 }), 38 39 path = sp.run(0, 0, 1000, 1000); 40 41 console.log(sp); 42 43 */ 44 class SeekPath{ 45 46 static _open = [] 47 static _dots = [] 48 static dots = [] 49 static _sort = function (a, b){return a["f"] - b["f"];} 50 51 #map = null; 52 #path = []; 53 #success = true; 54 #halfX = 50; 55 #halfY = 50; 56 57 #size = 10; 58 #lenX = 10; 59 #lenY = 10; 60 61 constructor(option = {}, heights = null){ 62 this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向 63 this.timeout = option.timeout || 500; //超时毫秒 64 this.maxHeight = option.maxHeight || 6; 65 this.range = new Box(); 66 this.size = option.size || 10; 67 this.lenX = option.lenX || 10; 68 this.lenY = option.lenY || 10; 69 70 this.initMap(heights); 71 72 } 73 74 get map(){ 75 return this.#map; 76 } 77 78 get path(){ 79 return this.#path; 80 } 81 82 get success(){ 83 return this.#success; 84 } 85 86 get size(){ 87 return this.#size; 88 } 89 90 set size(v){ 91 this.#size = v; 92 v = v / 2; 93 this.#halfX = v * this.#lenX; 94 this.#halfY = v * this.#lenY; 95 } 96 97 get lenX(){ 98 return this.#lenX; 99 } 100 101 set lenX(v){ 102 this.#lenX = v; 103 v = this.#size / 2; 104 this.#halfX = v * this.#lenX; 105 this.#halfY = v * this.#lenY; 106 this.range.x = 0; 107 this.range.w = this.#lenX-1; 108 } 109 110 get lenY(){ 111 return this.#lenY; 112 } 113 114 set lenY(v){ 115 this.#lenY = v; 116 v = this.#size / 2; 117 this.#halfX = v * this.#lenX; 118 this.#halfY = v * this.#lenY; 119 this.range.y = 0; 120 this.range.h = this.#lenY-1; 121 } 122 123 toScene(n, v){ //n = "x|y" 124 //n = n === "y" ? "lenY" : "lenX"; 125 if(n === "y") return v * this.#size - this.#halfY; 126 return v * this.#size - this.#halfX; 127 128 } 129 130 toIndex(n, v){ 131 //n = n === "y" ? "lenY" : "lenX"; 132 if(n === "y") return Math.round((this.#halfY + v) / this.#size); 133 return Math.round((this.#halfX + v) / this.#size); 134 135 } 136 137 initMap(heights){ 138 heights = Array.isArray(heights) === true ? heights : null; 139 140 const lenX = this.lenX, lenY = this.lenY; 141 var getHeight = (ix, iy) => { 142 if(heights === null) return 0; 143 ix = heights[ix * lenY + iy]; 144 if(ix === undefined) return -99999999; 145 return ix; 146 }, 147 148 map = []//new Map(); 149 150 for(let x = 0, y, m; x < lenX; x++){ 151 m = []//new Map(); 152 for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y), g:0, h:0, f:0, p:null, id:""}); 153 map[x] = m;//map.set(x, m); 154 } 155 156 this.#map = map; 157 this._id = -1; 158 this._updateID(); 159 160 map = heights = getHeight = undefined; 161 162 } 163 164 setDots(x, y, a, r){ //获取周围的点 x,y, a:8|16, r:存放结果数组 165 r.length = 0; 166 const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1; 167 if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1); 168 else r.push(x, y_1, x, y1, x_1, y, x1, y); 169 170 } 171 172 _updateID(){ 173 this._id++; 174 this._openID = "o_"+this._id; 175 this._closeID = "c_"+this._id; 176 177 } 178 179 run(x, y, x1, y1){ 180 this.#path.length = 0; 181 if(this.#map === null || this.range.containsPoint(x, y) === false) return this.#path; 182 this._updateID(); 183 184 const _map = this.#map, 185 _sort = SeekPath._sort, 186 _open = SeekPath._open, 187 _dots = SeekPath._dots, 188 dots = SeekPath.dots, 189 time = Date.now(); 190 191 var _n = _map[x][y],//_map.get(x).get(y), 192 isDot = true, 193 suc = _n, 194 k, _k, _x, _y, mhd, g, h, f, _d; 195 196 _n.g = 0; 197 _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 198 _n.f = _n.h; 199 _n.p = null; 200 _n.id = this._openID; 201 _open.push(_n); 202 203 while(_open.length !== 0){ 204 if(Date.now() - time > this.timeout) break; 205 206 _open.sort(_sort); 207 _n = _open.shift(); 208 if(_n.x === x1 && _n.y === y1){ 209 suc = _n; 210 break; 211 } 212 213 if(suc.h > _n.h) suc = _n; 214 _n.id = this._closeID; 215 this.setDots(_n.x, _n.y, this.angle, _dots); 216 217 for(k = 0; k < this.angle; k += 2){ 218 219 _x = _dots[k]; _y = _dots[k+1]; 220 if(this.range.containsPoint(_x, _y) === false) continue; 221 222 _d = _map[_x][_y];//_map.get(_x).get(_y); 223 if(_d.id === this._closeID) continue; 224 225 mhd = Math["abs"](_n["x"] - _x) + Math["abs"](_n["y"] - _y); 226 g = _n["g"] + (mhd === 1 ? 10 : 14); 227 h = Math["abs"](x1 - _x) * 10 + Math["abs"](y1 - _y) * 10; 228 f = g + h; 229 230 if(_d.id !== this._openID){ 231 232 if(Math["abs"](_n.height - _d.height) < this.maxHeight){ 233 234 if(mhd !== 1 && this.angle === 16){ 235 236 this.setDots(_d.x, _d.y, 8, dots); //与 d 正对的4个点 237 238 for(_k = 0; _k < 8; _k += 2){ 239 _x = dots[_k]; _y = dots[_k+1]; 240 if(this.range.containsPoint(_x, _y) === false) continue; 241 242 if(Math["abs"](_n.x - _x) + Math["abs"](_n.y - _y) === 1){ 243 244 if(Math["abs"](_n.height - _map[_x][_y].height) >= this.maxHeight){ 245 isDot = false; 246 break; 247 } 248 249 } 250 251 } 252 253 } 254 255 if(isDot === true){ 256 _d.g = g; 257 _d.h = h; 258 _d.f = f; 259 _d.p = _n; 260 _d.id = this._openID; 261 _open.push(_d); 262 263 } 264 265 else isDot = true; 266 267 } 268 269 } 270 271 else if(g < _d.g){ 272 _d.g = g; 273 _d.f = g + _d.h; 274 _d.p = _n; 275 276 } 277 278 } 279 280 } 281 282 this.#success = suc === _n; 283 284 while(suc !== null){ 285 this.#path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"])); 286 suc = suc["p"]; 287 } 288 289 _open.length = _dots.length = dots.length = 0; 290 291 return this.#path; 292 } 293 294 }
API说明:
parameter:
option: Object{
angle, timeout, maxHeight, size, lenX, lenY
}
heights: Array[Number]
attribute:
size: Number; //每个索引的大小
lenX: Number; //最大长度x (设置此属性时, 你需要重新.initMap(heights); .range 会被重置)
lenY: Number; //最大长度y (设置此属性时, 你需要重新.initMap(heights); .range 会被重置)
range: Box; //本次的搜索范围, 默认: 0,0,lenX,lenY
angle: Number; //8四方向 或 16八方向 默认 16
timeout: Number; //超时毫秒 默认 500
maxHeight: Number; //相邻可走的最大高 默认 6
//只读属性
success: Bool; //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
path: Array[x, y, z]; //存放.run()返回的路径
map: Map; //地图的缓存数据
method:
initMap(heights: Array[Number]): undefiend; //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
run(x, y, x1, y1: Number): Array[x, y, z]; //参数索引坐标
demo: