Find the clear (walkable, zero-cost) node closest to the given world * position. Spirals outward in expanding boxes until a clear node is * found or the search range is exhausted. Useful for snapping a click * or NPC spawn position to the nearest open tile. * * By defaul
(worldPos, searchRange = 10, rebuild = true)
| 322 | * @returns {PathFinderNode|null} |
| 323 | * @memberof PathFinding */ |
| 324 | getNearestClearNode(worldPos, searchRange = 10, rebuild = true) |
| 325 | { |
| 326 | ASSERT(isVector2(worldPos), 'worldPos must be a Vector2'); |
| 327 | if (rebuild) this.buildNodeData(); |
| 328 | |
| 329 | // Inline worldToTile to avoid a Vector2 allocation per call. |
| 330 | const ox = this.tileLayer ? this.tileLayer.pos.x : 0; |
| 331 | const oy = this.tileLayer ? this.tileLayer.pos.y : 0; |
| 332 | const centerX = floor(worldPos.x - ox); |
| 333 | const centerY = floor(worldPos.y - oy); |
| 334 | |
| 335 | for (let offset = 0; offset <= searchRange; ++offset) |
| 336 | { |
| 337 | let nearest = null; |
| 338 | let nearestDistSq = 0; |
| 339 | |
| 340 | for (let dy = -offset; dy <= offset; ++dy) |
| 341 | for (let dx = -offset; dx <= offset; ++dx) |
| 342 | { |
| 343 | // Only scan the perimeter of the current ring (skip the |
| 344 | // interior we've already searched in earlier iterations). |
| 345 | if (offset > 0 && abs(dx) !== offset && abs(dy) !== offset) |
| 346 | continue; |
| 347 | |
| 348 | const node = this.getNode(centerX + dx, centerY + dy); |
| 349 | if (!node || !node.isClear()) continue; |
| 350 | |
| 351 | const ddx = node.posWorld.x - worldPos.x; |
| 352 | const ddy = node.posWorld.y - worldPos.y; |
| 353 | const distSq = ddx * ddx + ddy * ddy; |
| 354 | if (!nearest || distSq < nearestDistSq) |
| 355 | { |
| 356 | nearest = node; |
| 357 | nearestDistSq = distSq; |
| 358 | } |
| 359 | } |
| 360 | if (nearest) return nearest; |
| 361 | } |
| 362 | return null; |
| 363 | } |
| 364 | |
| 365 | /** Smooth a node path by removing redundant turns and tightening corners |
| 366 | * where a grid-aligned diagonal is clear. Modifies the path in place. |
no test coverage detected