mirror of
https://github.com/motajs/template.git
synced 2026-09-13 02:08:50 +08:00
refactor(02-02): precompute node costs and BFS from start position in graph build
This commit is contained in:
parent
7dd4f39e7e
commit
239ac0d790
@ -1,6 +1,5 @@
|
||||
import { InternalDirectionGroup, ITileLocator, logger } from '@motajs/common';
|
||||
import {
|
||||
ILayerLocation,
|
||||
IMapLayer,
|
||||
IMapState,
|
||||
IPassPredicate,
|
||||
@ -135,21 +134,6 @@ export class PathfindingFinder implements IPathfinder {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进入指定位置节点的损失,损失值为 NaN 或负数时告警并按损失 1 处理,
|
||||
* Infinity 为合法损失值
|
||||
* @param block 位置信息
|
||||
*/
|
||||
private getNodeCost(block: ILayerLocation): number {
|
||||
if (!this.cost) return 1;
|
||||
const value = this.cost(block);
|
||||
if (Number.isNaN(value) || value < 0) {
|
||||
logger.warn(174);
|
||||
return 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在有向图上执行最小损失搜索,终端节点仅可作为路径终点,不可作为中间节点
|
||||
* @param graph 寻路有向图
|
||||
@ -192,7 +176,7 @@ export class PathfindingFinder implements IPathfinder {
|
||||
const next = graph.nodes.get(edge.to);
|
||||
if (!next) continue;
|
||||
if (next.terminal && edge.to !== targetIndex) continue;
|
||||
const total = currDist + this.getNodeCost(next.block);
|
||||
const total = currDist + next.cost;
|
||||
const known = dist.get(edge.to);
|
||||
if (isNil(known) || total < known) {
|
||||
dist.set(edge.to, total);
|
||||
@ -241,9 +225,10 @@ export class PathfindingFinder implements IPathfinder {
|
||||
const builder = new PathfindingGraphBuilder();
|
||||
builder.useMapState(maps);
|
||||
builder.useMapLayer(layer);
|
||||
builder.useCostFunction(this.cost);
|
||||
builder.usePassPredicate(this.predicate);
|
||||
builder.useDirGroup(this.group);
|
||||
const graph = builder.build();
|
||||
const graph = builder.build(start);
|
||||
return this.search(graph, start, target);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// 测试寻路有向图构建:邻域方向组、单向门、终端节点分类与边界守卫
|
||||
// 测试寻路有向图构建:BFS 可达过滤、邻域方向组、单向门、终端节点分类、损失预计算与边界守卫
|
||||
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { FaceDirection } from '@user/data-common';
|
||||
import {
|
||||
@ -101,6 +101,15 @@ const SINK_TILE: TestTileDefinition = {
|
||||
eventPass: true
|
||||
};
|
||||
|
||||
/** 墙体图块:不可进入也不可离开 */
|
||||
const WALL_TILE: TestTileDefinition = {
|
||||
num: 6,
|
||||
id: 'wall',
|
||||
outPass: 0,
|
||||
inPass: 0,
|
||||
eventPass: true
|
||||
};
|
||||
|
||||
/** 撞击图块:四向可进可出但事件不通行,构成终端节点 */
|
||||
const HIT_TILE: TestTileDefinition = {
|
||||
num: 5,
|
||||
@ -112,6 +121,7 @@ const HIT_TILE: TestTileDefinition = {
|
||||
|
||||
const ALL_TILES: TestTileDefinition[] = [
|
||||
OPEN_TILE,
|
||||
WALL_TILE,
|
||||
ONEWAY_TILE,
|
||||
SINK_TILE,
|
||||
HIT_TILE
|
||||
@ -176,8 +186,8 @@ class FixturePredicate implements IPassPredicate {
|
||||
// 判断事件层
|
||||
const curr = event.getLocationData(currLoc.x, currLoc.y);
|
||||
const next = event.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const currRaw = curr?.static.raw();
|
||||
const nextRaw = next?.static.raw();
|
||||
const currRaw = curr?.static?.raw();
|
||||
const nextRaw = next?.static?.raw();
|
||||
if (currRaw) {
|
||||
canLeave = !!(leaveMask & currRaw.pass.outPass);
|
||||
}
|
||||
@ -191,8 +201,8 @@ class FixturePredicate implements IPassPredicate {
|
||||
if (layer === event) continue;
|
||||
const other = layer.getLocationData(currLoc.x, currLoc.y);
|
||||
const otherNext = layer.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const otherRaw = other?.static.raw();
|
||||
const otherNextRaw = otherNext?.static.raw();
|
||||
const otherRaw = other?.static?.raw();
|
||||
const otherNextRaw = otherNext?.static?.raw();
|
||||
if (otherRaw?.pass.onlyEvents) {
|
||||
canLeave = !!(leaveMask & otherRaw.pass.outPass);
|
||||
}
|
||||
@ -209,7 +219,7 @@ class FixturePredicate implements IPassPredicate {
|
||||
if (!event) return false;
|
||||
const { nextLoc } = handler;
|
||||
const next = event.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const nextRaw = next?.static.raw();
|
||||
const nextRaw = next?.static?.raw();
|
||||
if (!nextRaw) return false;
|
||||
return !nextRaw.eventPass;
|
||||
}
|
||||
@ -271,23 +281,23 @@ function createFixture(
|
||||
}
|
||||
|
||||
describe('pathfinding graph building', () => {
|
||||
// 验证未注入谓词时图节点齐备但所有边均不可行
|
||||
it('builds nodes without edges when no predicate is injected', () => {
|
||||
// 验证未注入谓词时无可通行边,BFS 仅包含起始位置自身且无损失告警
|
||||
it('includes only the start node when no predicate is injected', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.useMapLayer(map.getLayerByAlias('event'));
|
||||
const graph = builder.build();
|
||||
const graph = builder.build({ x: 1, y: 1 });
|
||||
|
||||
expect(graph.width).toBe(3);
|
||||
expect(graph.height).toBe(3);
|
||||
expect(graph.nodes.size).toBe(9);
|
||||
for (const node of graph.nodes.values()) {
|
||||
expect(node.edges).toHaveLength(0);
|
||||
expect(node.terminal).toBe(false);
|
||||
}
|
||||
expect(graph.nodes.size).toBe(1);
|
||||
const start = graph.nodes.get(1 * 3 + 1)!;
|
||||
expect(start.edges).toHaveLength(0);
|
||||
expect(start.terminal).toBe(false);
|
||||
expect(start.cost).toBe(1);
|
||||
});
|
||||
|
||||
// 验证注入谓词后中心节点邻域方向数与 DirectionMapper 四正交组一致为 4
|
||||
@ -304,7 +314,7 @@ describe('pathfinding graph building', () => {
|
||||
new modules.Dir8FaceHandler()
|
||||
);
|
||||
builder.usePassPredicate(predicate);
|
||||
const graph = builder.build();
|
||||
const graph = builder.build({ x: 1, y: 1 });
|
||||
|
||||
const center = graph.nodes.get(1 * 3 + 1)!;
|
||||
expect(center.edges).toHaveLength(expected.length);
|
||||
@ -333,7 +343,7 @@ describe('pathfinding graph building', () => {
|
||||
);
|
||||
builder.usePassPredicate(predicate);
|
||||
builder.useDirGroup(InternalDirectionGroup.Dir8);
|
||||
const graph = builder.build();
|
||||
const graph = builder.build({ x: 1, y: 1 });
|
||||
|
||||
const center = graph.nodes.get(1 * 3 + 1)!;
|
||||
expect(center.edges).toHaveLength(8);
|
||||
@ -347,7 +357,7 @@ describe('pathfinding graph building', () => {
|
||||
new modules.Dir8FaceHandler()
|
||||
);
|
||||
builder.usePassPredicate(predicate);
|
||||
const graph = builder.build();
|
||||
const graph = builder.build({ x: 0, y: 0 });
|
||||
|
||||
const source = graph.nodes.get(0)!;
|
||||
const sink = graph.nodes.get(1)!;
|
||||
@ -364,7 +374,7 @@ describe('pathfinding graph building', () => {
|
||||
new modules.Dir8FaceHandler()
|
||||
);
|
||||
builder.usePassPredicate(predicate);
|
||||
const graph = builder.build();
|
||||
const graph = builder.build({ x: 0, y: 0 });
|
||||
|
||||
const hit = graph.nodes.get(1)!;
|
||||
expect(hit.terminal).toBe(true);
|
||||
@ -381,9 +391,135 @@ describe('pathfinding graph building', () => {
|
||||
const builder = new modules.PathfindingGraphBuilder();
|
||||
builder.useMapLayer(null);
|
||||
|
||||
const result = modules.logger.catch(() => builder.build());
|
||||
const result = modules.logger.catch(() =>
|
||||
builder.build({ x: 0, y: 0 })
|
||||
);
|
||||
|
||||
expect(result.ret.nodes.size).toBe(0);
|
||||
expect(result.info.map(info => info.code)).toContain(173);
|
||||
});
|
||||
|
||||
// 验证起始位置越界时构建入口同样告警新码 173 并返回空图
|
||||
it('warns the registered code and returns an empty graph when start is out of map', () => {
|
||||
const { builder } = createFixture([1, 1, 1, 1, 1, 1, 1, 1, 1], 3, null);
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
builder.build({ x: 3, y: 0 })
|
||||
);
|
||||
|
||||
expect(result.ret.nodes.size).toBe(0);
|
||||
expect(result.ret.width).toBe(0);
|
||||
expect(result.info.map(info => info.code)).toContain(173);
|
||||
});
|
||||
|
||||
// 验证墙体隔断区域不进入有向图:图仅包含从起始位置沿可通行边可达的节点
|
||||
it('excludes walled-off regions from the graph', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 6, 1, 1, 6, 1, 1, 6, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.usePassPredicate(
|
||||
new FixturePredicate(map, new modules.Dir8FaceHandler())
|
||||
);
|
||||
const graph = builder.build({ x: 0, y: 0 });
|
||||
|
||||
expect(graph.nodes.size).toBe(3);
|
||||
expect(graph.nodes.has(0)).toBe(true);
|
||||
expect(graph.nodes.has(3)).toBe(true);
|
||||
expect(graph.nodes.has(6)).toBe(true);
|
||||
expect(graph.nodes.has(2)).toBe(false);
|
||||
expect(graph.nodes.has(5)).toBe(false);
|
||||
expect(graph.nodes.has(8)).toBe(false);
|
||||
});
|
||||
|
||||
// 验证节点损失在构建时预计算:自定义损失函数的取值直接出现在 node.cost 上
|
||||
it('precomputes node costs from the injected cost function at build time', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.usePassPredicate(
|
||||
new FixturePredicate(map, new modules.Dir8FaceHandler())
|
||||
);
|
||||
builder.useCostFunction(block =>
|
||||
block.locator.x === 1 && block.locator.y === 1 ? 7 : 3
|
||||
);
|
||||
const graph = builder.build({ x: 1, y: 1 });
|
||||
|
||||
expect(graph.nodes.get(1 * 3 + 1)!.cost).toBe(7);
|
||||
expect(graph.nodes.get(0)!.cost).toBe(3);
|
||||
expect(graph.nodes.get(2 * 3 + 2)!.cost).toBe(3);
|
||||
});
|
||||
|
||||
// 验证 Infinity 是合法损失值:构建时不告警,损失值原样保留在节点上
|
||||
it('allows Infinity as a legitimate node cost without warning', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.usePassPredicate(
|
||||
new FixturePredicate(map, new modules.Dir8FaceHandler())
|
||||
);
|
||||
builder.useCostFunction(block =>
|
||||
block.locator.x === 1 && block.locator.y === 1
|
||||
? Number.POSITIVE_INFINITY
|
||||
: 1
|
||||
);
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
builder.build({ x: 0, y: 0 })
|
||||
);
|
||||
|
||||
expect(result.info.map(info => info.code)).not.toContain(174);
|
||||
expect(result.ret.nodes.get(1 * 3 + 1)!.cost).toBe(
|
||||
Number.POSITIVE_INFINITY
|
||||
);
|
||||
});
|
||||
|
||||
// 验证 NaN 损失在构建时告警新码 174 且每个非法节点仅告警一次,并回退为损失 1
|
||||
it('warns once per invalid node per build and falls back to unit cost on NaN', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.usePassPredicate(
|
||||
new FixturePredicate(map, new modules.Dir8FaceHandler())
|
||||
);
|
||||
builder.useCostFunction(block =>
|
||||
block.locator.x === 1 && block.locator.y === 1 ? Number.NaN : 1
|
||||
);
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
builder.build({ x: 0, y: 0 })
|
||||
);
|
||||
|
||||
expect(result.info.filter(info => info.code === 174)).toHaveLength(1);
|
||||
expect(result.ret.nodes.get(1 * 3 + 1)!.cost).toBe(1);
|
||||
});
|
||||
|
||||
// 验证负数损失同样在构建时告警新码 174 并回退为损失 1
|
||||
it('warns and falls back to unit cost on negative node cost', () => {
|
||||
const { map, builder } = createFixture(
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
3,
|
||||
null
|
||||
);
|
||||
builder.usePassPredicate(
|
||||
new FixturePredicate(map, new modules.Dir8FaceHandler())
|
||||
);
|
||||
builder.useCostFunction(block =>
|
||||
block.locator.x === 1 && block.locator.y === 1 ? -2 : 1
|
||||
);
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
builder.build({ x: 0, y: 0 })
|
||||
);
|
||||
|
||||
expect(result.info.filter(info => info.code === 174)).toHaveLength(1);
|
||||
expect(result.ret.nodes.get(1 * 3 + 1)!.cost).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
IDirectionDescriptor,
|
||||
IDirectionMapper,
|
||||
InternalDirectionGroup,
|
||||
ITileLocator,
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
import { FaceDirection } from '@user/data-common';
|
||||
@ -18,7 +19,8 @@ import {
|
||||
IPathGraph,
|
||||
IPathGraphEdge,
|
||||
IPathGraphNode,
|
||||
IPathfindingGraphBuilder
|
||||
IPathfindingGraphBuilder,
|
||||
PathCostFunction
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@ -43,6 +45,8 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
private maps: IMapState | null = null;
|
||||
/** 绑定的地图图层,图节点来源 */
|
||||
private layer: IMapLayer | null = null;
|
||||
/** 注入的损失函数,未注入时每格损失 1 */
|
||||
private cost: PathCostFunction | null = null;
|
||||
/** 注入的通行性谓词,用于判定边的可行性与终端节点 */
|
||||
private predicate: IPassPredicate | null = null;
|
||||
/** 邻域方向组别,默认四正交方向 */
|
||||
@ -59,6 +63,10 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
useCostFunction(cost: PathCostFunction | null): void {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
usePassPredicate(predicate: IPassPredicate | null): void {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
@ -81,9 +89,24 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
build(): IPathGraph {
|
||||
/**
|
||||
* 获取进入指定位置节点的损失,损失值为 NaN 或负数时告警并按损失 1 处理,
|
||||
* Infinity 为合法损失值
|
||||
* @param block 位置信息
|
||||
*/
|
||||
private resolveCost(block: ILayerLocation): number {
|
||||
if (!this.cost) return 1;
|
||||
const value = this.cost(block);
|
||||
if (Number.isNaN(value) || value < 0) {
|
||||
logger.warn(174);
|
||||
return 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
build(start: ITileLocator): IPathGraph {
|
||||
const layer = this.layer;
|
||||
if (isNil(layer)) {
|
||||
if (isNil(layer) || !layer.inMap(start.x, start.y)) {
|
||||
logger.warn(173);
|
||||
return { width: 0, height: 0, nodes: new Map() };
|
||||
}
|
||||
@ -92,28 +115,21 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
const height = layer.height;
|
||||
const floorId = this.resolveFloorId();
|
||||
const state = layer.state;
|
||||
const blocks: (ILayerLocation | null)[] = new Array(
|
||||
width * height
|
||||
).fill(null);
|
||||
|
||||
// 收集图内全部图块作为图节点
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
if (!layer.inMap(x, y)) continue;
|
||||
const loc = layer.getLocationData(x, y);
|
||||
if (!loc) continue;
|
||||
blocks[y * width + x] = loc;
|
||||
}
|
||||
}
|
||||
const dirs: IDirectionDescriptor[] = [...this.mapper.map(this.group)];
|
||||
|
||||
const terminals: Set<number> = new Set();
|
||||
const adjacency: Map<number, IPathGraphEdge[]> = new Map();
|
||||
const dirs: IDirectionDescriptor[] = [...this.mapper.map(this.group)];
|
||||
const blocks: Map<number, ILayerLocation> = new Map();
|
||||
const startIndex = start.y * width + start.x;
|
||||
blocks.set(startIndex, layer.getLocationData(start.x, start.y)!);
|
||||
adjacency.set(startIndex, []);
|
||||
|
||||
// 逐节点判定邻域边可行性
|
||||
for (let index = 0; index < blocks.length; index++) {
|
||||
const block = blocks[index];
|
||||
if (!block) continue;
|
||||
// 以起始位置为中心 BFS,仅沿可通行有向边扩展,不可达区域不入图
|
||||
const queue: number[] = [startIndex];
|
||||
let head = 0;
|
||||
while (head < queue.length) {
|
||||
const index = queue[head++]!;
|
||||
const block = blocks.get(index)!;
|
||||
const x = index % width;
|
||||
const y = Math.floor(index / width);
|
||||
const edges: IPathGraphEdge[] = [];
|
||||
@ -123,8 +139,7 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
const nx = x + desc.x;
|
||||
const ny = y + desc.y;
|
||||
if (!layer.inMap(nx, ny)) continue;
|
||||
const next = blocks[ny * width + nx];
|
||||
if (!next) continue;
|
||||
const next = layer.getLocationData(nx, ny)!;
|
||||
const handler: IPassCheckHandler = {
|
||||
currLoc: block.locator,
|
||||
nextLoc: next.locator,
|
||||
@ -135,23 +150,28 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
||||
if (isNil(this.predicate) || !this.predicate.canPass(handler)) {
|
||||
continue;
|
||||
}
|
||||
const nextIndex = ny * width + nx;
|
||||
if (this.predicate.shouldHit(handler)) {
|
||||
terminals.add(ny * width + nx);
|
||||
terminals.add(nextIndex);
|
||||
}
|
||||
edges.push({ dir, to: nextIndex });
|
||||
if (!blocks.has(nextIndex)) {
|
||||
blocks.set(nextIndex, next);
|
||||
adjacency.set(nextIndex, []);
|
||||
queue.push(nextIndex);
|
||||
}
|
||||
edges.push({ dir, to: ny * width + nx });
|
||||
}
|
||||
adjacency.set(index, edges);
|
||||
}
|
||||
|
||||
const nodes: Map<number, IPathGraphNode> = new Map();
|
||||
for (let index = 0; index < blocks.length; index++) {
|
||||
const block = blocks[index];
|
||||
if (!block) continue;
|
||||
for (const [index, block] of blocks) {
|
||||
nodes.set(index, {
|
||||
index,
|
||||
x: index % width,
|
||||
y: Math.floor(index / width),
|
||||
block,
|
||||
cost: this.resolveCost(block),
|
||||
terminal: terminals.has(index),
|
||||
edges: adjacency.get(index) ?? []
|
||||
});
|
||||
|
||||
@ -145,8 +145,8 @@ class FixturePredicate implements IPassPredicate {
|
||||
let canEnter = true;
|
||||
const curr = event.getLocationData(currLoc.x, currLoc.y);
|
||||
const next = event.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const currRaw = curr?.static.raw();
|
||||
const nextRaw = next?.static.raw();
|
||||
const currRaw = curr?.static?.raw();
|
||||
const nextRaw = next?.static?.raw();
|
||||
if (currRaw) {
|
||||
canLeave = !!(leaveMask & currRaw.pass.outPass);
|
||||
}
|
||||
@ -289,7 +289,7 @@ function createPerformanceSystem(rows: number[], width: number) {
|
||||
builder.useMapState(maps);
|
||||
builder.useMapLayer(layer);
|
||||
builder.usePassPredicate(predicate);
|
||||
return builder.build();
|
||||
return builder.build({ x: 0, y: 0 });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -178,8 +178,8 @@ class FixturePredicate implements IPassPredicate {
|
||||
let canEnter = true;
|
||||
const curr = event.getLocationData(currLoc.x, currLoc.y);
|
||||
const next = event.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const currRaw = curr?.static.raw();
|
||||
const nextRaw = next?.static.raw();
|
||||
const currRaw = curr?.static?.raw();
|
||||
const nextRaw = next?.static?.raw();
|
||||
if (currRaw) {
|
||||
canLeave = !!(leaveMask & currRaw.pass.outPass);
|
||||
}
|
||||
@ -194,7 +194,7 @@ class FixturePredicate implements IPassPredicate {
|
||||
if (!event) return false;
|
||||
const { nextLoc } = handler;
|
||||
const next = event.getLocationData(nextLoc.x, nextLoc.y);
|
||||
const nextRaw = next?.static.raw();
|
||||
const nextRaw = next?.static?.raw();
|
||||
if (!nextRaw) return false;
|
||||
return !nextRaw.eventPass;
|
||||
}
|
||||
|
||||
@ -140,8 +140,10 @@ export interface IPathGraphNode {
|
||||
readonly x: number;
|
||||
/** 节点纵坐标 */
|
||||
readonly y: number;
|
||||
/** 节点对应的位置信息,用于损失计算 */
|
||||
/** 节点对应的位置信息 */
|
||||
readonly block: ILayerLocation;
|
||||
/** 进入该节点的损失,构建图时由损失函数计算 */
|
||||
readonly cost: number;
|
||||
/** 该节点是否仅可作为路径终点,不可作为中间节点 */
|
||||
readonly terminal: boolean;
|
||||
/** 该节点的全部出边 */
|
||||
@ -170,6 +172,12 @@ export interface IPathfindingGraphBuilder {
|
||||
*/
|
||||
useMapLayer(layer: IMapLayer | null): void;
|
||||
|
||||
/**
|
||||
* 设置构建图时使用的损失函数,未注入时每格损失 1
|
||||
* @param cost 损失函数
|
||||
*/
|
||||
useCostFunction(cost: PathCostFunction | null): void;
|
||||
|
||||
/**
|
||||
* 注入判定边可行性的通行性谓词
|
||||
* @param predicate 通行性谓词
|
||||
@ -183,9 +191,11 @@ export interface IPathfindingGraphBuilder {
|
||||
useDirGroup(group: number): void;
|
||||
|
||||
/**
|
||||
* 构建有向图。图层未绑定时告警并返回空图,不包含任何节点与边
|
||||
* 以起始位置为中心构建有向图:沿可通行有向边 BFS 扩展,
|
||||
* 仅包含从起始位置可达的节点。图层未绑定或起始位置越界时告警并返回空图
|
||||
* @param start BFS 起始位置
|
||||
*/
|
||||
build(): IPathGraph;
|
||||
build(start: ITileLocator): IPathGraph;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
Loading…
Reference in New Issue
Block a user