@esengine/pathfinding 13.2.0 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ecs.js CHANGED
@@ -1,26 +1,32 @@
1
1
  import {
2
- CatmullRomSmoother,
3
- CombinedSmoother,
4
- GridMap,
5
- IncrementalAStarPathfinder,
6
- LineOfSightSmoother,
7
- PathValidator
8
- } from "./chunk-VNC2YAAL.js";
9
- import {
10
- PathfindingState
11
- } from "./chunk-YKA3PWU3.js";
12
- import {
13
- DEFAULT_AGENT_PARAMS,
14
- DEFAULT_ORCA_CONFIG,
15
- createKDTree,
16
- createORCASolver
17
- } from "./chunk-JTZP55BJ.js";
2
+ CollisionResolverAdapter,
3
+ DEFAULT_FLOW_CONTROLLER_CONFIG,
4
+ DEFAULT_ORCA_PARAMS,
5
+ FlowController,
6
+ GridPathfinderAdapter,
7
+ IncrementalGridPathPlannerAdapter,
8
+ NavMeshPathPlannerAdapter,
9
+ ORCALocalAvoidanceAdapter,
10
+ PassPermission,
11
+ PathPlanState,
12
+ createAStarPlanner,
13
+ createDefaultCollisionResolver,
14
+ createFlowController,
15
+ createHPAPlanner,
16
+ createIncrementalAStarPlanner,
17
+ createJPSPlanner,
18
+ createNavMeshPathPlanner,
19
+ createORCAAvoidance,
20
+ isIncrementalPlanner
21
+ } from "./chunk-NIKT3PQC.js";
22
+ import "./chunk-YKA3PWU3.js";
23
+ import "./chunk-3VEX32JO.js";
18
24
  import {
19
25
  __name,
20
26
  __publicField
21
27
  } from "./chunk-T626JPC7.js";
22
28
 
23
- // src/ecs/PathfindingAgentComponent.ts
29
+ // src/ecs/NavigationAgentComponent.ts
24
30
  import { Component, ECSComponent, Serializable, Serialize, Property } from "@esengine/ecs-framework";
25
31
  function _ts_decorate(decorators, target, key, desc) {
26
32
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -33,225 +39,264 @@ function _ts_metadata(k, v) {
33
39
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
34
40
  }
35
41
  __name(_ts_metadata, "_ts_metadata");
36
- var _PathfindingAgentComponent = class _PathfindingAgentComponent extends Component {
42
+ var NavigationState = /* @__PURE__ */ (function(NavigationState2) {
43
+ NavigationState2["Idle"] = "idle";
44
+ NavigationState2["Navigating"] = "navigating";
45
+ NavigationState2["Arrived"] = "arrived";
46
+ NavigationState2["Blocked"] = "blocked";
47
+ NavigationState2["Unreachable"] = "unreachable";
48
+ return NavigationState2;
49
+ })({});
50
+ var _NavigationAgentComponent = class _NavigationAgentComponent extends Component {
37
51
  constructor() {
38
52
  super(...arguments);
39
53
  // =========================================================================
40
- // 位置属性 | Position Properties
54
+ // 核心物理属性 | Core Physical Properties
41
55
  // =========================================================================
42
56
  /**
43
- * @zh 当前位置 X 坐标
44
- * @en Current position X coordinate
57
+ * @zh 代理半径
58
+ * @en Agent radius
59
+ */
60
+ __publicField(this, "radius", 0.5);
61
+ /**
62
+ * @zh 最大速度
63
+ * @en Maximum speed
45
64
  */
46
- __publicField(this, "x", 0);
65
+ __publicField(this, "maxSpeed", 5);
47
66
  /**
48
- * @zh 当前位置 Y 坐标
49
- * @en Current position Y coordinate
67
+ * @zh 加速度(用于平滑移动)
68
+ * @en Acceleration (for smooth movement)
50
69
  */
51
- __publicField(this, "y", 0);
70
+ __publicField(this, "acceleration", 10);
52
71
  // =========================================================================
53
- // 目标属性 | Target Properties
72
+ // 寻路配置 | Pathfinding Configuration
54
73
  // =========================================================================
55
74
  /**
56
- * @zh 目标位置 X 坐标
57
- * @en Target position X coordinate
75
+ * @zh 路径点到达阈值
76
+ * @en Waypoint arrival threshold
58
77
  */
59
- __publicField(this, "targetX", 0);
78
+ __publicField(this, "waypointThreshold", 0.5);
60
79
  /**
61
- * @zh 目标位置 Y 坐标
62
- * @en Target position Y coordinate
80
+ * @zh 目标到达阈值
81
+ * @en Destination arrival threshold
63
82
  */
64
- __publicField(this, "targetY", 0);
83
+ __publicField(this, "arrivalThreshold", 0.3);
65
84
  /**
66
- * @zh 是否有新的寻路请求待处理
67
- * @en Whether there is a new path request pending
85
+ * @zh 路径重新计算间隔(秒)
86
+ * @en Path recalculation interval (seconds)
68
87
  */
69
- __publicField(this, "hasRequest", false);
88
+ __publicField(this, "repathInterval", 0.5);
70
89
  // =========================================================================
71
- // 配置属性 | Configuration Properties
90
+ // 配置选项 | Configuration Options
72
91
  // =========================================================================
73
92
  /**
74
- * @zh 寻路优先级(数值越小优先级越高)
75
- * @en Pathfinding priority (lower number = higher priority)
93
+ * @zh 是否启用导航
94
+ * @en Whether navigation is enabled
76
95
  */
77
- __publicField(this, "priority", 50);
96
+ __publicField(this, "enabled", true);
78
97
  /**
79
- * @zh 每帧最大迭代次数
80
- * @en Maximum iterations per frame
98
+ * @zh 是否自动重新计算被阻挡的路径
99
+ * @en Whether to auto repath when blocked
81
100
  */
82
- __publicField(this, "maxIterationsPerFrame", 100);
101
+ __publicField(this, "autoRepath", true);
83
102
  /**
84
- * @zh 是否启用动态重规划
85
- * @en Whether dynamic replanning is enabled
103
+ * @zh 是否启用平滑转向
104
+ * @en Whether to enable smooth steering
86
105
  */
87
- __publicField(this, "enableDynamicReplan", true);
106
+ __publicField(this, "smoothSteering", true);
107
+ // =========================================================================
108
+ // 运行时状态 | Runtime State (Non-serialized)
109
+ // =========================================================================
88
110
  /**
89
- * @zh 向前探测距离(用于障碍物检测)
90
- * @en Lookahead distance for obstacle detection
111
+ * @zh 当前位置
112
+ * @en Current position
91
113
  */
92
- __publicField(this, "lookaheadDistance", 5);
114
+ __publicField(this, "position", {
115
+ x: 0,
116
+ y: 0
117
+ });
93
118
  /**
94
- * @zh 路径验证间隔(帧数)
95
- * @en Path validation interval (in frames)
119
+ * @zh 当前速度
120
+ * @en Current velocity
96
121
  */
97
- __publicField(this, "validationInterval", 10);
98
- // =========================================================================
99
- // 运行时状态(不序列化)| Runtime State (not serialized)
100
- // =========================================================================
122
+ __publicField(this, "velocity", {
123
+ x: 0,
124
+ y: 0
125
+ });
101
126
  /**
102
- * @zh 当前寻路状态
103
- * @en Current pathfinding state
127
+ * @zh 目标位置
128
+ * @en Destination position
104
129
  */
105
- __publicField(this, "state", PathfindingState.Idle);
130
+ __publicField(this, "destination", null);
106
131
  /**
107
- * @zh 当前请求 ID
108
- * @en Current request ID
132
+ * @zh 当前导航状态
133
+ * @en Current navigation state
109
134
  */
110
- __publicField(this, "currentRequestId", -1);
135
+ __publicField(this, "state", "idle");
111
136
  /**
112
- * @zh 当前路径点列表
113
- * @en Current path waypoints
137
+ * @zh 当前路径
138
+ * @en Current path
114
139
  */
115
140
  __publicField(this, "path", []);
116
141
  /**
117
- * @zh 当前路径索引
118
- * @en Current path index
142
+ * @zh 当前路径点索引
143
+ * @en Current waypoint index
119
144
  */
120
- __publicField(this, "pathIndex", 0);
145
+ __publicField(this, "currentWaypointIndex", 0);
121
146
  /**
122
- * @zh 路径总代价
123
- * @en Total path cost
147
+ * @zh 上次重新计算路径的时间
148
+ * @en Last repath time
124
149
  */
125
- __publicField(this, "pathCost", 0);
150
+ __publicField(this, "lastRepathTime", 0);
151
+ // =========================================================================
152
+ // 增量寻路状态(时间切片)| Incremental Pathfinding State (Time Slicing)
153
+ // =========================================================================
126
154
  /**
127
- * @zh 寻路进度 (0-1)
128
- * @en Pathfinding progress (0-1)
155
+ * @zh 当前增量寻路请求 ID
156
+ * @en Current incremental pathfinding request ID
129
157
  */
130
- __publicField(this, "progress", 0);
158
+ __publicField(this, "currentRequestId", -1);
131
159
  /**
132
- * @zh 上次验证的帧号
133
- * @en Last validation frame number
160
+ * @zh 寻路进度 (0-1)
161
+ * @en Pathfinding progress (0-1)
134
162
  */
135
- __publicField(this, "lastValidationFrame", 0);
163
+ __publicField(this, "pathProgress", 0);
136
164
  /**
137
- * @zh 寻路完成回调
138
- * @en Pathfinding complete callback
165
+ * @zh 优先级(数字越小优先级越高)
166
+ * @en Priority (lower number = higher priority)
139
167
  */
140
- __publicField(this, "onPathComplete");
168
+ __publicField(this, "priority", 50);
141
169
  /**
142
- * @zh 寻路进度回调
143
- * @en Pathfinding progress callback
170
+ * @zh 是否正在等待路径计算完成
171
+ * @en Whether waiting for path computation to complete
144
172
  */
145
- __publicField(this, "onPathProgress");
173
+ __publicField(this, "isComputingPath", false);
146
174
  }
147
175
  // =========================================================================
148
176
  // 公共方法 | Public Methods
149
177
  // =========================================================================
150
178
  /**
151
- * @zh 请求寻路到目标位置
152
- * @en Request path to target position
179
+ * @zh 设置位置
180
+ * @en Set position
153
181
  *
154
- * @param targetX - @zh 目标 X 坐标 @en Target X coordinate
155
- * @param targetY - @zh 目标 Y 坐标 @en Target Y coordinate
182
+ * @param x - @zh X 坐标 @en X coordinate
183
+ * @param y - @zh Y 坐标 @en Y coordinate
156
184
  */
157
- requestPathTo(targetX, targetY) {
158
- this.targetX = targetX;
159
- this.targetY = targetY;
160
- this.hasRequest = true;
161
- this.state = PathfindingState.Idle;
162
- this.progress = 0;
185
+ setPosition(x, y) {
186
+ this.position = {
187
+ x,
188
+ y
189
+ };
163
190
  }
164
191
  /**
165
- * @zh 取消当前寻路
166
- * @en Cancel current pathfinding
192
+ * @zh 设置目标位置
193
+ * @en Set destination
194
+ *
195
+ * @param x - @zh 目标 X 坐标 @en Destination X coordinate
196
+ * @param y - @zh 目标 Y 坐标 @en Destination Y coordinate
167
197
  */
168
- cancelPath() {
169
- this.hasRequest = false;
170
- this.state = PathfindingState.Cancelled;
198
+ setDestination(x, y) {
199
+ this.destination = {
200
+ x,
201
+ y
202
+ };
203
+ this.state = "navigating";
171
204
  this.path = [];
172
- this.pathIndex = 0;
173
- this.progress = 0;
174
- this.currentRequestId = -1;
205
+ this.currentWaypointIndex = 0;
206
+ this.lastRepathTime = 0;
175
207
  }
176
208
  /**
177
- * @zh 获取下一个路径点
178
- * @en Get next waypoint
179
- *
180
- * @returns @zh 下一个路径点或 null @en Next waypoint or null
209
+ * @zh 停止导航
210
+ * @en Stop navigation
181
211
  */
182
- getNextWaypoint() {
183
- if (this.pathIndex < this.path.length) {
184
- return this.path[this.pathIndex];
185
- }
186
- return null;
212
+ stop() {
213
+ this.destination = null;
214
+ this.state = "idle";
215
+ this.path = [];
216
+ this.currentWaypointIndex = 0;
217
+ this.velocity = {
218
+ x: 0,
219
+ y: 0
220
+ };
187
221
  }
188
222
  /**
189
- * @zh 前进到下一个路径点
190
- * @en Advance to next waypoint
223
+ * @zh 获取当前路径点
224
+ * @en Get current waypoint
225
+ *
226
+ * @returns @zh 当前路径点,如果没有则返回 null @en Current waypoint, or null if none
191
227
  */
192
- advanceWaypoint() {
193
- if (this.pathIndex < this.path.length) {
194
- this.pathIndex++;
228
+ getCurrentWaypoint() {
229
+ if (this.currentWaypointIndex < this.path.length) {
230
+ return this.path[this.currentWaypointIndex];
195
231
  }
232
+ return null;
196
233
  }
197
234
  /**
198
- * @zh 检查是否到达路径终点
199
- * @en Check if reached path end
235
+ * @zh 获取到目标的距离
236
+ * @en Get distance to destination
200
237
  *
201
- * @returns @zh 是否到达终点 @en Whether reached end
238
+ * @returns @zh 到目标的距离,如果没有目标则返回 Infinity @en Distance to destination, or Infinity if no destination
202
239
  */
203
- isPathComplete() {
204
- return this.pathIndex >= this.path.length;
240
+ getDistanceToDestination() {
241
+ if (!this.destination) return Infinity;
242
+ const dx = this.destination.x - this.position.x;
243
+ const dy = this.destination.y - this.position.y;
244
+ return Math.sqrt(dx * dx + dy * dy);
205
245
  }
206
246
  /**
207
- * @zh 检查是否正在寻路
208
- * @en Check if pathfinding is in progress
247
+ * @zh 获取当前速度大小
248
+ * @en Get current speed
209
249
  *
210
- * @returns @zh 是否正在寻路 @en Whether pathfinding is in progress
250
+ * @returns @zh 当前速度大小 @en Current speed magnitude
211
251
  */
212
- isSearching() {
213
- return this.state === PathfindingState.InProgress;
252
+ getCurrentSpeed() {
253
+ return Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
214
254
  }
215
255
  /**
216
- * @zh 检查是否有有效路径
217
- * @en Check if has valid path
256
+ * @zh 检查是否已到达目标
257
+ * @en Check if arrived at destination
218
258
  *
219
- * @returns @zh 是否有有效路径 @en Whether has valid path
259
+ * @returns @zh 是否已到达 @en Whether arrived
220
260
  */
221
- hasValidPath() {
222
- return this.state === PathfindingState.Completed && this.path.length > 0;
261
+ hasArrived() {
262
+ return this.state === "arrived";
223
263
  }
224
264
  /**
225
- * @zh 获取剩余路径点数量
226
- * @en Get remaining waypoint count
265
+ * @zh 检查路径是否被阻挡
266
+ * @en Check if path is blocked
227
267
  *
228
- * @returns @zh 剩余路径点数量 @en Remaining waypoint count
268
+ * @returns @zh 是否被阻挡 @en Whether blocked
229
269
  */
230
- getRemainingWaypointCount() {
231
- return Math.max(0, this.path.length - this.pathIndex);
270
+ isBlocked() {
271
+ return this.state === "blocked";
232
272
  }
233
273
  /**
234
- * @zh 获取当前路径的总长度
235
- * @en Get total path length
274
+ * @zh 检查目标是否无法到达
275
+ * @en Check if destination is unreachable
236
276
  *
237
- * @returns @zh 路径总长度 @en Total path length
277
+ * @returns @zh 是否无法到达 @en Whether unreachable
238
278
  */
239
- getPathLength() {
240
- return this.path.length;
279
+ isUnreachable() {
280
+ return this.state === "unreachable";
241
281
  }
242
282
  /**
243
283
  * @zh 重置组件状态
244
284
  * @en Reset component state
245
285
  */
246
286
  reset() {
247
- this.state = PathfindingState.Idle;
248
- this.currentRequestId = -1;
287
+ this.position = {
288
+ x: 0,
289
+ y: 0
290
+ };
291
+ this.velocity = {
292
+ x: 0,
293
+ y: 0
294
+ };
295
+ this.destination = null;
296
+ this.state = "idle";
249
297
  this.path = [];
250
- this.pathIndex = 0;
251
- this.pathCost = 0;
252
- this.progress = 0;
253
- this.hasRequest = false;
254
- this.lastValidationFrame = 0;
298
+ this.currentWaypointIndex = 0;
299
+ this.lastRepathTime = 0;
255
300
  }
256
301
  /**
257
302
  * @zh 组件从实体移除时调用
@@ -259,102 +304,104 @@ var _PathfindingAgentComponent = class _PathfindingAgentComponent extends Compon
259
304
  */
260
305
  onRemovedFromEntity() {
261
306
  this.reset();
262
- this.onPathComplete = void 0;
263
- this.onPathProgress = void 0;
264
307
  }
265
308
  };
266
- __name(_PathfindingAgentComponent, "PathfindingAgentComponent");
267
- var PathfindingAgentComponent = _PathfindingAgentComponent;
309
+ __name(_NavigationAgentComponent, "NavigationAgentComponent");
310
+ var NavigationAgentComponent = _NavigationAgentComponent;
268
311
  _ts_decorate([
269
312
  Serialize(),
270
313
  Property({
271
314
  type: "number",
272
- label: "Position X"
315
+ label: "Radius",
316
+ min: 0.1,
317
+ max: 10
273
318
  }),
274
319
  _ts_metadata("design:type", Number)
275
- ], PathfindingAgentComponent.prototype, "x", void 0);
320
+ ], NavigationAgentComponent.prototype, "radius", void 0);
276
321
  _ts_decorate([
277
322
  Serialize(),
278
323
  Property({
279
324
  type: "number",
280
- label: "Position Y"
325
+ label: "Max Speed",
326
+ min: 0.1,
327
+ max: 100
281
328
  }),
282
329
  _ts_metadata("design:type", Number)
283
- ], PathfindingAgentComponent.prototype, "y", void 0);
330
+ ], NavigationAgentComponent.prototype, "maxSpeed", void 0);
284
331
  _ts_decorate([
285
332
  Serialize(),
286
333
  Property({
287
334
  type: "number",
288
- label: "Target X"
335
+ label: "Acceleration",
336
+ min: 0.1,
337
+ max: 100
289
338
  }),
290
339
  _ts_metadata("design:type", Number)
291
- ], PathfindingAgentComponent.prototype, "targetX", void 0);
340
+ ], NavigationAgentComponent.prototype, "acceleration", void 0);
292
341
  _ts_decorate([
293
342
  Serialize(),
294
343
  Property({
295
344
  type: "number",
296
- label: "Target Y"
345
+ label: "Waypoint Threshold",
346
+ min: 0.1,
347
+ max: 10
297
348
  }),
298
349
  _ts_metadata("design:type", Number)
299
- ], PathfindingAgentComponent.prototype, "targetY", void 0);
350
+ ], NavigationAgentComponent.prototype, "waypointThreshold", void 0);
300
351
  _ts_decorate([
301
352
  Serialize(),
302
353
  Property({
303
354
  type: "number",
304
- label: "Priority",
305
- min: 0,
306
- max: 100
355
+ label: "Arrival Threshold",
356
+ min: 0.1,
357
+ max: 10
307
358
  }),
308
359
  _ts_metadata("design:type", Number)
309
- ], PathfindingAgentComponent.prototype, "priority", void 0);
360
+ ], NavigationAgentComponent.prototype, "arrivalThreshold", void 0);
310
361
  _ts_decorate([
311
362
  Serialize(),
312
363
  Property({
313
364
  type: "number",
314
- label: "Max Iterations/Frame",
315
- min: 10,
316
- max: 1e3
365
+ label: "Repath Interval",
366
+ min: 0.1,
367
+ max: 10
317
368
  }),
318
369
  _ts_metadata("design:type", Number)
319
- ], PathfindingAgentComponent.prototype, "maxIterationsPerFrame", void 0);
370
+ ], NavigationAgentComponent.prototype, "repathInterval", void 0);
320
371
  _ts_decorate([
321
372
  Serialize(),
322
373
  Property({
323
374
  type: "boolean",
324
- label: "Dynamic Replan"
375
+ label: "Enabled"
325
376
  }),
326
377
  _ts_metadata("design:type", Boolean)
327
- ], PathfindingAgentComponent.prototype, "enableDynamicReplan", void 0);
378
+ ], NavigationAgentComponent.prototype, "enabled", void 0);
328
379
  _ts_decorate([
329
380
  Serialize(),
330
381
  Property({
331
- type: "number",
332
- label: "Lookahead Distance",
333
- min: 1,
334
- max: 20
382
+ type: "boolean",
383
+ label: "Auto Repath"
335
384
  }),
336
- _ts_metadata("design:type", Number)
337
- ], PathfindingAgentComponent.prototype, "lookaheadDistance", void 0);
385
+ _ts_metadata("design:type", Boolean)
386
+ ], NavigationAgentComponent.prototype, "autoRepath", void 0);
338
387
  _ts_decorate([
339
388
  Serialize(),
340
389
  Property({
341
- type: "number",
342
- label: "Validation Interval",
343
- min: 1,
344
- max: 60
390
+ type: "boolean",
391
+ label: "Smooth Steering"
345
392
  }),
346
- _ts_metadata("design:type", Number)
347
- ], PathfindingAgentComponent.prototype, "validationInterval", void 0);
348
- PathfindingAgentComponent = _ts_decorate([
349
- ECSComponent("PathfindingAgent"),
393
+ _ts_metadata("design:type", Boolean)
394
+ ], NavigationAgentComponent.prototype, "smoothSteering", void 0);
395
+ NavigationAgentComponent = _ts_decorate([
396
+ ECSComponent("NavigationAgent"),
350
397
  Serializable({
351
398
  version: 1,
352
- typeId: "PathfindingAgent"
399
+ typeId: "NavigationAgent"
353
400
  })
354
- ], PathfindingAgentComponent);
401
+ ], NavigationAgentComponent);
355
402
 
356
- // src/ecs/PathfindingMapComponent.ts
357
- import { Component as Component2, ECSComponent as ECSComponent2, Serializable as Serializable2, Serialize as Serialize2, Property as Property2 } from "@esengine/ecs-framework";
403
+ // src/ecs/NavigationSystem.ts
404
+ import { EntitySystem, Matcher, ECSSystem } from "@esengine/ecs-framework";
358
405
  function _ts_decorate2(decorators, target, key, desc) {
359
406
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
360
407
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -366,1357 +413,914 @@ function _ts_metadata2(k, v) {
366
413
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
367
414
  }
368
415
  __name(_ts_metadata2, "_ts_metadata");
369
- var _PathfindingMapComponent = class _PathfindingMapComponent extends Component2 {
370
- constructor() {
371
- super(...arguments);
372
- // =========================================================================
373
- // 地图配置 | Map Configuration
374
- // =========================================================================
375
- /**
376
- * @zh 地图类型
377
- * @en Map type
378
- */
379
- __publicField(this, "mapType", "grid");
380
- /**
381
- * @zh 网格宽度(仅 grid 类型)
382
- * @en Grid width (grid type only)
383
- */
384
- __publicField(this, "width", 100);
385
- /**
386
- * @zh 网格高度(仅 grid 类型)
387
- * @en Grid height (grid type only)
388
- */
389
- __publicField(this, "height", 100);
390
- /**
391
- * @zh 是否允许对角移动
392
- * @en Whether diagonal movement is allowed
393
- */
394
- __publicField(this, "allowDiagonal", true);
395
- /**
396
- * @zh 是否避免穿角
397
- * @en Whether to avoid corner cutting
398
- */
399
- __publicField(this, "avoidCorners", true);
400
- // =========================================================================
401
- // 系统配置 | System Configuration
402
- // =========================================================================
403
- /**
404
- * @zh 每帧处理的最大代理数
405
- * @en Maximum agents processed per frame
406
- */
407
- __publicField(this, "maxAgentsPerFrame", 10);
408
- /**
409
- * @zh 每帧总迭代次数预算
410
- * @en Total iterations budget per frame
411
- */
412
- __publicField(this, "iterationsBudget", 1e3);
413
- /**
414
- * @zh 是否启用路径平滑
415
- * @en Whether path smoothing is enabled
416
- */
417
- __publicField(this, "enableSmoothing", true);
418
- /**
419
- * @zh 路径平滑类型
420
- * @en Path smoothing type
421
- */
422
- __publicField(this, "smoothingType", "los");
423
- // =========================================================================
424
- // 缓存配置 | Cache Configuration
425
- // =========================================================================
426
- /**
427
- * @zh 是否启用路径缓存
428
- * @en Whether path caching is enabled
429
- */
430
- __publicField(this, "enableCache", true);
431
- /**
432
- * @zh 缓存最大条目数
433
- * @en Maximum cache entries
434
- */
435
- __publicField(this, "cacheMaxEntries", 1e3);
436
- /**
437
- * @zh 缓存过期时间(毫秒),0 表示不过期
438
- * @en Cache TTL in milliseconds, 0 means no expiration
439
- */
440
- __publicField(this, "cacheTtlMs", 5e3);
441
- // =========================================================================
442
- // 调试配置 | Debug Configuration
443
- // =========================================================================
444
- /**
445
- * @zh 是否显示调试信息
446
- * @en Whether to show debug info
447
- */
448
- __publicField(this, "debugMode", false);
449
- /**
450
- * @zh 是否显示网格
451
- * @en Whether to show grid
452
- */
453
- __publicField(this, "showGrid", false);
454
- /**
455
- * @zh 是否显示路径
456
- * @en Whether to show paths
457
- */
458
- __publicField(this, "showPaths", false);
459
- // =========================================================================
460
- // 运行时实例(不序列化)| Runtime Instances (not serialized)
461
- // =========================================================================
462
- /**
463
- * @zh 地图实例
464
- * @en Map instance
465
- */
466
- __publicField(this, "map", null);
467
- /**
468
- * @zh 增量寻路器实例
469
- * @en Incremental pathfinder instance
470
- */
471
- __publicField(this, "pathfinder", null);
472
- /**
473
- * @zh 路径平滑器实例
474
- * @en Path smoother instance
475
- */
476
- __publicField(this, "smoother", null);
477
- /**
478
- * @zh 是否已初始化
479
- * @en Whether initialized
480
- */
481
- __publicField(this, "initialized", false);
416
+ var DEFAULT_CONFIG = {
417
+ timeStep: 1 / 60,
418
+ enablePathPlanning: true,
419
+ enableFlowControl: true,
420
+ enableLocalAvoidance: true,
421
+ enableCollisionResolution: true,
422
+ enableAgentCollisionResolution: true,
423
+ enableTimeSlicing: false,
424
+ iterationsBudget: 1e3,
425
+ maxAgentsPerFrame: 10,
426
+ maxIterationsPerAgent: 200
427
+ };
428
+ var _NavigationSystem = class _NavigationSystem extends EntitySystem {
429
+ constructor(config = {}) {
430
+ super(Matcher.all(NavigationAgentComponent));
431
+ __publicField(this, "config");
432
+ __publicField(this, "pathPlanner", null);
433
+ __publicField(this, "flowController", null);
434
+ __publicField(this, "localAvoidance", null);
435
+ __publicField(this, "collisionResolver", null);
436
+ /**
437
+ * @zh 静态障碍物(墙壁、建筑等)- 由 PathPlanner 和 CollisionResolver 处理
438
+ * @en Static obstacles (walls, buildings) - handled by PathPlanner and CollisionResolver
439
+ */
440
+ __publicField(this, "staticObstacles", []);
441
+ /**
442
+ * @zh 动态障碍物(移动物体等)- 由 ORCA 和 CollisionResolver 处理
443
+ * @en Dynamic obstacles (moving objects) - handled by ORCA and CollisionResolver
444
+ */
445
+ __publicField(this, "dynamicObstacles", []);
446
+ __publicField(this, "currentTime", 0);
447
+ __publicField(this, "agentEnterTimes", /* @__PURE__ */ new Map());
482
448
  // =========================================================================
483
- // 统计信息 | Statistics
449
+ // 时间切片状态 | Time Slicing State
484
450
  // =========================================================================
485
451
  /**
486
- * @zh 当前活跃请求数
487
- * @en Current active request count
488
- */
489
- __publicField(this, "activeRequests", 0);
490
- /**
491
- * @zh 本帧使用的迭代次数
492
- * @en Iterations used this frame
493
- */
494
- __publicField(this, "iterationsUsedThisFrame", 0);
495
- /**
496
- * @zh 本帧处理的代理数
497
- * @en Agents processed this frame
452
+ * @zh 是否为增量寻路器
453
+ * @en Whether the path planner is incremental
498
454
  */
499
- __publicField(this, "agentsProcessedThisFrame", 0);
455
+ __publicField(this, "isIncrementalPlanner", false);
500
456
  /**
501
- * @zh 缓存命中次数
502
- * @en Cache hit count
457
+ * @zh 等待寻路的代理队列(按优先级排序)
458
+ * @en Queue of agents waiting for pathfinding (sorted by priority)
503
459
  */
504
- __publicField(this, "cacheHits", 0);
505
- /**
506
- * @zh 缓存未命中次数
507
- * @en Cache miss count
508
- */
509
- __publicField(this, "cacheMisses", 0);
460
+ __publicField(this, "pendingPathRequests", /* @__PURE__ */ new Set());
461
+ this.config = {
462
+ ...DEFAULT_CONFIG,
463
+ ...config
464
+ };
510
465
  }
511
466
  // =========================================================================
512
- // 公共方法 | Public Methods
467
+ // 算法设置 | Algorithm Setters
513
468
  // =========================================================================
514
469
  /**
515
- * @zh 设置网格单元格是否可通行
516
- * @en Set grid cell walkability
470
+ * @zh 设置路径规划器
471
+ * @en Set path planner
517
472
  *
518
- * @param x - @zh X 坐标 @en X coordinate
519
- * @param y - @zh Y 坐标 @en Y coordinate
520
- * @param walkable - @zh 是否可通行 @en Is walkable
473
+ * @param planner - @zh 路径规划器,传入 null 禁用路径规划 @en Path planner, pass null to disable
474
+ *
475
+ * @zh 如果传入 IIncrementalPathPlanner 且启用了时间切片,会自动使用增量寻路
476
+ * @en If passing IIncrementalPathPlanner and time slicing is enabled, will automatically use incremental pathfinding
477
+ *
478
+ * @example
479
+ * ```typescript
480
+ * navSystem.setPathPlanner(createNavMeshPathPlanner(navMesh));
481
+ * navSystem.setPathPlanner(createAStarPlanner(gridMap));
482
+ * navSystem.setPathPlanner(createIncrementalAStarPlanner(gridMap)); // 支持时间切片
483
+ * navSystem.setPathPlanner(null); // 禁用
484
+ * ```
521
485
  */
522
- setWalkable(x, y, walkable) {
523
- if (this.map && "setWalkable" in this.map) {
524
- this.map.setWalkable(x, y, walkable);
525
- if (this.pathfinder) {
526
- this.pathfinder.notifyObstacleChange(x - 1, y - 1, x + 1, y + 1);
527
- }
528
- }
486
+ setPathPlanner(planner) {
487
+ this.pathPlanner?.dispose();
488
+ this.pathPlanner = planner;
489
+ this.isIncrementalPlanner = planner !== null && isIncrementalPlanner(planner);
490
+ this.pendingPathRequests.clear();
529
491
  }
530
492
  /**
531
- * @zh 设置矩形区域是否可通行
532
- * @en Set rectangular area walkability
533
- *
534
- * @param x - @zh 起始 X @en Start X
535
- * @param y - @zh 起始 Y @en Start Y
536
- * @param width - @zh 宽度 @en Width
537
- * @param height - @zh 高度 @en Height
538
- * @param walkable - @zh 是否可通行 @en Is walkable
493
+ * @zh 获取当前路径规划器
494
+ * @en Get current path planner
539
495
  */
540
- setRectWalkable(x, y, rectWidth, rectHeight, walkable) {
541
- if (this.map && "setRectWalkable" in this.map) {
542
- this.map.setRectWalkable(x, y, rectWidth, rectHeight, walkable);
543
- if (this.pathfinder) {
544
- this.pathfinder.notifyObstacleChange(x - 1, y - 1, x + rectWidth + 1, y + rectHeight + 1);
545
- }
546
- }
496
+ getPathPlanner() {
497
+ return this.pathPlanner;
547
498
  }
548
499
  /**
549
- * @zh 检查位置是否可通行
550
- * @en Check if position is walkable
500
+ * @zh 设置流量控制器
501
+ * @en Set flow controller
551
502
  *
552
- * @param x - @zh X 坐标 @en X coordinate
553
- * @param y - @zh Y 坐标 @en Y coordinate
554
- * @returns @zh 是否可通行 @en Is walkable
503
+ * @param controller - @zh 流量控制器,传入 null 禁用流量控制 @en Flow controller, pass null to disable
504
+ *
505
+ * @example
506
+ * ```typescript
507
+ * navSystem.setFlowController(createFlowController());
508
+ * navSystem.setFlowController(null); // 禁用
509
+ * ```
555
510
  */
556
- isWalkable(x, y) {
557
- return this.map?.isWalkable(x, y) ?? false;
511
+ setFlowController(controller) {
512
+ this.flowController?.dispose();
513
+ this.flowController = controller;
558
514
  }
559
515
  /**
560
- * @zh 重置统计信息
561
- * @en Reset statistics
516
+ * @zh 获取当前流量控制器
517
+ * @en Get current flow controller
562
518
  */
563
- resetStats() {
564
- this.iterationsUsedThisFrame = 0;
565
- this.agentsProcessedThisFrame = 0;
519
+ getFlowController() {
520
+ return this.flowController;
566
521
  }
567
522
  /**
568
- * @zh 获取剩余迭代预算
569
- * @en Get remaining iteration budget
523
+ * @zh 设置局部避让算法
524
+ * @en Set local avoidance algorithm
570
525
  *
571
- * @returns @zh 剩余预算 @en Remaining budget
526
+ * @param avoidance - @zh 局部避让算法,传入 null 禁用避让 @en Local avoidance, pass null to disable
527
+ *
528
+ * @example
529
+ * ```typescript
530
+ * navSystem.setLocalAvoidance(createORCAAvoidance());
531
+ * navSystem.setLocalAvoidance(null); // 禁用
532
+ * ```
572
533
  */
573
- getRemainingBudget() {
574
- return Math.max(0, this.iterationsBudget - this.iterationsUsedThisFrame);
534
+ setLocalAvoidance(avoidance) {
535
+ this.localAvoidance?.dispose();
536
+ this.localAvoidance = avoidance;
575
537
  }
576
538
  /**
577
- * @zh 获取缓存统计信息
578
- * @en Get cache statistics
579
- *
580
- * @returns @zh 缓存统计 @en Cache statistics
539
+ * @zh 获取当前局部避让算法
540
+ * @en Get current local avoidance algorithm
581
541
  */
582
- getCacheStats() {
583
- const pathfinderWithCache = this.pathfinder;
584
- if (pathfinderWithCache?.getCacheStats) {
585
- const stats = pathfinderWithCache.getCacheStats();
586
- this.cacheHits = stats.hits;
587
- this.cacheMisses = stats.misses;
588
- return {
589
- enabled: this.enableCache,
590
- hits: stats.hits,
591
- misses: stats.misses,
592
- hitRate: stats.hitRate
593
- };
594
- }
595
- return {
596
- enabled: this.enableCache,
597
- hits: this.cacheHits,
598
- misses: this.cacheMisses,
599
- hitRate: this.cacheHits + this.cacheMisses > 0 ? this.cacheHits / (this.cacheHits + this.cacheMisses) : 0
600
- };
542
+ getLocalAvoidance() {
543
+ return this.localAvoidance;
601
544
  }
602
545
  /**
603
- * @zh 组件从实体移除时调用
604
- * @en Called when component is removed from entity
546
+ * @zh 设置碰撞解决器
547
+ * @en Set collision resolver
548
+ *
549
+ * @param resolver - @zh 碰撞解决器,传入 null 禁用碰撞解决 @en Collision resolver, pass null to disable
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * navSystem.setCollisionResolver(createDefaultCollisionResolver());
554
+ * navSystem.setCollisionResolver(null); // 禁用
555
+ * ```
605
556
  */
606
- onRemovedFromEntity() {
607
- if (this.pathfinder) {
608
- this.pathfinder.clear();
609
- }
610
- this.map = null;
611
- this.pathfinder = null;
612
- this.smoother = null;
613
- this.initialized = false;
614
- }
615
- };
616
- __name(_PathfindingMapComponent, "PathfindingMapComponent");
617
- var PathfindingMapComponent = _PathfindingMapComponent;
618
- _ts_decorate2([
619
- Serialize2(),
620
- Property2({
621
- type: "enum",
622
- label: "Map Type",
623
- options: [
624
- {
625
- value: "grid",
626
- label: "Grid"
627
- },
628
- {
629
- value: "navmesh",
630
- label: "NavMesh"
631
- }
632
- ]
633
- }),
634
- _ts_metadata2("design:type", typeof PathfindingMapType === "undefined" ? Object : PathfindingMapType)
635
- ], PathfindingMapComponent.prototype, "mapType", void 0);
636
- _ts_decorate2([
637
- Serialize2(),
638
- Property2({
639
- type: "number",
640
- label: "Map Width",
641
- min: 1,
642
- max: 1e4
643
- }),
644
- _ts_metadata2("design:type", Number)
645
- ], PathfindingMapComponent.prototype, "width", void 0);
646
- _ts_decorate2([
647
- Serialize2(),
648
- Property2({
649
- type: "number",
650
- label: "Map Height",
651
- min: 1,
652
- max: 1e4
653
- }),
654
- _ts_metadata2("design:type", Number)
655
- ], PathfindingMapComponent.prototype, "height", void 0);
656
- _ts_decorate2([
657
- Serialize2(),
658
- Property2({
659
- type: "boolean",
660
- label: "Allow Diagonal"
661
- }),
662
- _ts_metadata2("design:type", Boolean)
663
- ], PathfindingMapComponent.prototype, "allowDiagonal", void 0);
664
- _ts_decorate2([
665
- Serialize2(),
666
- Property2({
667
- type: "boolean",
668
- label: "Avoid Corners"
669
- }),
670
- _ts_metadata2("design:type", Boolean)
671
- ], PathfindingMapComponent.prototype, "avoidCorners", void 0);
672
- _ts_decorate2([
673
- Serialize2(),
674
- Property2({
675
- type: "number",
676
- label: "Max Agents/Frame",
677
- min: 1,
678
- max: 100
679
- }),
680
- _ts_metadata2("design:type", Number)
681
- ], PathfindingMapComponent.prototype, "maxAgentsPerFrame", void 0);
682
- _ts_decorate2([
683
- Serialize2(),
684
- Property2({
685
- type: "number",
686
- label: "Iterations Budget",
687
- min: 100,
688
- max: 1e4
689
- }),
690
- _ts_metadata2("design:type", Number)
691
- ], PathfindingMapComponent.prototype, "iterationsBudget", void 0);
692
- _ts_decorate2([
693
- Serialize2(),
694
- Property2({
695
- type: "boolean",
696
- label: "Enable Smoothing"
697
- }),
698
- _ts_metadata2("design:type", Boolean)
699
- ], PathfindingMapComponent.prototype, "enableSmoothing", void 0);
700
- _ts_decorate2([
701
- Serialize2(),
702
- Property2({
703
- type: "enum",
704
- label: "Smoothing Type",
705
- options: [
706
- {
707
- value: "los",
708
- label: "Line of Sight"
709
- },
710
- {
711
- value: "catmullrom",
712
- label: "Catmull-Rom"
713
- },
714
- {
715
- value: "combined",
716
- label: "Combined"
717
- }
718
- ]
719
- }),
720
- _ts_metadata2("design:type", String)
721
- ], PathfindingMapComponent.prototype, "smoothingType", void 0);
722
- _ts_decorate2([
723
- Serialize2(),
724
- Property2({
725
- type: "boolean",
726
- label: "Enable Cache"
727
- }),
728
- _ts_metadata2("design:type", Boolean)
729
- ], PathfindingMapComponent.prototype, "enableCache", void 0);
730
- _ts_decorate2([
731
- Serialize2(),
732
- Property2({
733
- type: "number",
734
- label: "Cache Size",
735
- min: 100,
736
- max: 1e4
737
- }),
738
- _ts_metadata2("design:type", Number)
739
- ], PathfindingMapComponent.prototype, "cacheMaxEntries", void 0);
740
- _ts_decorate2([
741
- Serialize2(),
742
- Property2({
743
- type: "number",
744
- label: "Cache TTL (ms)",
745
- min: 0,
746
- max: 6e4
747
- }),
748
- _ts_metadata2("design:type", Number)
749
- ], PathfindingMapComponent.prototype, "cacheTtlMs", void 0);
750
- _ts_decorate2([
751
- Serialize2(),
752
- Property2({
753
- type: "boolean",
754
- label: "Debug Mode"
755
- }),
756
- _ts_metadata2("design:type", Boolean)
757
- ], PathfindingMapComponent.prototype, "debugMode", void 0);
758
- _ts_decorate2([
759
- Serialize2(),
760
- Property2({
761
- type: "boolean",
762
- label: "Show Grid"
763
- }),
764
- _ts_metadata2("design:type", Boolean)
765
- ], PathfindingMapComponent.prototype, "showGrid", void 0);
766
- _ts_decorate2([
767
- Serialize2(),
768
- Property2({
769
- type: "boolean",
770
- label: "Show Paths"
771
- }),
772
- _ts_metadata2("design:type", Boolean)
773
- ], PathfindingMapComponent.prototype, "showPaths", void 0);
774
- PathfindingMapComponent = _ts_decorate2([
775
- ECSComponent2("PathfindingMap"),
776
- Serializable2({
777
- version: 1,
778
- typeId: "PathfindingMap"
779
- })
780
- ], PathfindingMapComponent);
781
-
782
- // src/ecs/PathfindingSystem.ts
783
- import { EntitySystem, Matcher, ECSSystem } from "@esengine/ecs-framework";
784
- function _ts_decorate3(decorators, target, key, desc) {
785
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
786
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
787
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
788
- return c > 3 && r && Object.defineProperty(target, key, r), r;
789
- }
790
- __name(_ts_decorate3, "_ts_decorate");
791
- function _ts_metadata3(k, v) {
792
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
793
- }
794
- __name(_ts_metadata3, "_ts_metadata");
795
- var _PathfindingSystem = class _PathfindingSystem extends EntitySystem {
796
- constructor() {
797
- super(Matcher.all(PathfindingAgentComponent));
798
- __publicField(this, "mapEntity", null);
799
- __publicField(this, "mapComponent", null);
800
- __publicField(this, "pathValidator");
801
- __publicField(this, "agentQueue", []);
802
- __publicField(this, "frameCounter", 0);
803
- this.pathValidator = new PathValidator();
557
+ setCollisionResolver(resolver) {
558
+ this.collisionResolver?.dispose();
559
+ this.collisionResolver = resolver;
804
560
  }
805
561
  /**
806
- * @zh 系统初始化
807
- * @en System initialization
562
+ * @zh 获取当前碰撞解决器
563
+ * @en Get current collision resolver
808
564
  */
809
- onInitialize() {
810
- this.findMapEntity();
811
- this.initializeMap();
565
+ getCollisionResolver() {
566
+ return this.collisionResolver;
812
567
  }
568
+ // =========================================================================
569
+ // 障碍物管理 | Obstacle Management
570
+ // =========================================================================
813
571
  /**
814
- * @zh 系统激活时调用
815
- * @en Called when system is enabled
572
+ * @zh 添加静态障碍物(墙壁、建筑等)
573
+ * @en Add static obstacle (walls, buildings, etc.)
574
+ *
575
+ * @zh 静态障碍物由 PathPlanner 规划路径时考虑,CollisionResolver 防止穿透
576
+ * @zh ORCA 不会处理静态障碍物,因为路径规划已经绑开了它们
577
+ * @en Static obstacles are considered by PathPlanner for routing, CollisionResolver for penetration prevention
578
+ * @en ORCA does NOT process static obstacles since path planning already avoids them
579
+ *
580
+ * @param obstacle - @zh 障碍物数据 @en Obstacle data
816
581
  */
817
- onEnable() {
818
- this.findMapEntity();
582
+ addStaticObstacle(obstacle) {
583
+ this.staticObstacles.push(obstacle);
819
584
  }
820
585
  /**
821
- * @zh 处理实体
822
- * @en Process entities
586
+ * @zh 添加动态障碍物(移动物体、临时障碍等)
587
+ * @en Add dynamic obstacle (moving objects, temporary obstacles, etc.)
588
+ *
589
+ * @zh 动态障碍物由 ORCA 进行局部避让,CollisionResolver 防止穿透
590
+ * @en Dynamic obstacles are handled by ORCA for local avoidance, CollisionResolver for penetration prevention
591
+ *
592
+ * @param obstacle - @zh 障碍物数据 @en Obstacle data
823
593
  */
824
- process(entities) {
825
- if (!this.mapComponent?.pathfinder) {
826
- this.findMapEntity();
827
- if (!this.mapComponent?.pathfinder) {
828
- return;
829
- }
830
- }
831
- this.frameCounter++;
832
- this.mapComponent.resetStats();
833
- this.buildAgentQueue(entities);
834
- this.processAgentsWithBudget();
835
- this.validatePaths(entities);
594
+ addDynamicObstacle(obstacle) {
595
+ this.dynamicObstacles.push(obstacle);
836
596
  }
837
- // =========================================================================
838
- // 私有方法 | Private Methods
839
- // =========================================================================
840
597
  /**
841
- * @zh 查找地图实体
842
- * @en Find map entity
598
+ * @zh 移除所有静态障碍物
599
+ * @en Remove all static obstacles
843
600
  */
844
- findMapEntity() {
845
- if (!this.scene) return;
846
- const entities = this.scene.entities.findEntitiesWithComponent(PathfindingMapComponent);
847
- if (entities.length > 0) {
848
- const entity = entities[0];
849
- const mapComp = entity.getComponent(PathfindingMapComponent);
850
- if (mapComp) {
851
- this.mapEntity = entity;
852
- this.mapComponent = mapComp;
853
- if (!mapComp.initialized) {
854
- this.initializeMap();
855
- }
856
- }
857
- }
601
+ clearStaticObstacles() {
602
+ this.staticObstacles = [];
858
603
  }
859
604
  /**
860
- * @zh 初始化地图
861
- * @en Initialize map
605
+ * @zh 移除所有动态障碍物
606
+ * @en Remove all dynamic obstacles
862
607
  */
863
- initializeMap() {
864
- if (!this.mapComponent) return;
865
- if (this.mapComponent.initialized) return;
866
- if (!this.mapComponent.map) {
867
- if (this.mapComponent.mapType === "grid") {
868
- this.mapComponent.map = new GridMap(this.mapComponent.width, this.mapComponent.height, {
869
- allowDiagonal: this.mapComponent.allowDiagonal,
870
- avoidCorners: this.mapComponent.avoidCorners
871
- });
872
- }
608
+ clearDynamicObstacles() {
609
+ this.dynamicObstacles = [];
610
+ }
611
+ /**
612
+ * @zh 移除所有障碍物(静态和动态)
613
+ * @en Remove all obstacles (static and dynamic)
614
+ */
615
+ clearObstacles() {
616
+ this.staticObstacles = [];
617
+ this.dynamicObstacles = [];
618
+ }
619
+ /**
620
+ * @zh 获取静态障碍物列表
621
+ * @en Get static obstacles list
622
+ */
623
+ getStaticObstacles() {
624
+ return this.staticObstacles;
625
+ }
626
+ /**
627
+ * @zh 获取动态障碍物列表
628
+ * @en Get dynamic obstacles list
629
+ */
630
+ getDynamicObstacles() {
631
+ return this.dynamicObstacles;
632
+ }
633
+ /**
634
+ * @zh 获取所有障碍物列表(静态+动态)
635
+ * @en Get all obstacles list (static + dynamic)
636
+ */
637
+ getObstacles() {
638
+ return [
639
+ ...this.staticObstacles,
640
+ ...this.dynamicObstacles
641
+ ];
642
+ }
643
+ /**
644
+ * @zh 获取所有障碍物用于碰撞检测
645
+ * @en Get all obstacles for collision detection
646
+ */
647
+ getAllObstaclesForCollision() {
648
+ return [
649
+ ...this.staticObstacles,
650
+ ...this.dynamicObstacles
651
+ ];
652
+ }
653
+ /**
654
+ * @zh 设置静态障碍物列表
655
+ * @en Set static obstacles list
656
+ *
657
+ * @param obstacles - @zh 障碍物列表 @en Obstacles list
658
+ */
659
+ setStaticObstacles(obstacles) {
660
+ this.staticObstacles = [
661
+ ...obstacles
662
+ ];
663
+ }
664
+ /**
665
+ * @zh 设置动态障碍物列表
666
+ * @en Set dynamic obstacles list
667
+ *
668
+ * @param obstacles - @zh 障碍物列表 @en Obstacles list
669
+ */
670
+ setDynamicObstacles(obstacles) {
671
+ this.dynamicObstacles = [
672
+ ...obstacles
673
+ ];
674
+ }
675
+ // =========================================================================
676
+ // 系统生命周期 | System Lifecycle
677
+ // =========================================================================
678
+ /**
679
+ * @zh 系统销毁时调用
680
+ * @en Called when system is destroyed
681
+ */
682
+ onDestroy() {
683
+ this.pathPlanner?.dispose();
684
+ this.flowController?.dispose();
685
+ this.localAvoidance?.dispose();
686
+ this.collisionResolver?.dispose();
687
+ this.pathPlanner = null;
688
+ this.flowController = null;
689
+ this.localAvoidance = null;
690
+ this.collisionResolver = null;
691
+ this.staticObstacles = [];
692
+ this.dynamicObstacles = [];
693
+ this.agentEnterTimes.clear();
694
+ this.pendingPathRequests.clear();
695
+ this.isIncrementalPlanner = false;
696
+ }
697
+ // =========================================================================
698
+ // 处理管线 | Processing Pipeline
699
+ // =========================================================================
700
+ /**
701
+ * @zh 处理实体
702
+ * @en Process entities
703
+ */
704
+ process(entities) {
705
+ if (entities.length === 0) return;
706
+ const deltaTime = this.config.timeStep;
707
+ this.currentTime += deltaTime;
708
+ const agentDataMap = /* @__PURE__ */ new Map();
709
+ const flowAgentDataList = [];
710
+ const entityMap = /* @__PURE__ */ new Map();
711
+ for (const entity of entities) {
712
+ entityMap.set(entity.id, entity);
873
713
  }
874
- if (!this.mapComponent.pathfinder && this.mapComponent.map) {
875
- this.mapComponent.pathfinder = new IncrementalAStarPathfinder(this.mapComponent.map, {
876
- enableCache: this.mapComponent.enableCache,
877
- cacheConfig: {
878
- maxEntries: this.mapComponent.cacheMaxEntries,
879
- ttlMs: this.mapComponent.cacheTtlMs
714
+ if (this.config.enablePathPlanning && this.pathPlanner) {
715
+ if (this.config.enableTimeSlicing && this.isIncrementalPlanner) {
716
+ const agentList = [];
717
+ for (const entity of entities) {
718
+ const agent = entity.getComponent(NavigationAgentComponent);
719
+ if (agent.enabled) {
720
+ agentList.push({
721
+ entityId: entity.id,
722
+ agent
723
+ });
724
+ }
725
+ }
726
+ this.processIncrementalPathPlanning(agentList, entityMap);
727
+ } else {
728
+ for (const entity of entities) {
729
+ const agent = entity.getComponent(NavigationAgentComponent);
730
+ if (!agent.enabled) continue;
731
+ this.processPathPlanning(agent, deltaTime);
880
732
  }
881
- });
882
- }
883
- if (!this.mapComponent.smoother && this.mapComponent.enableSmoothing) {
884
- switch (this.mapComponent.smoothingType) {
885
- case "catmullrom":
886
- this.mapComponent.smoother = new CatmullRomSmoother();
887
- break;
888
- case "combined":
889
- this.mapComponent.smoother = new CombinedSmoother();
890
- break;
891
- case "los":
892
- default:
893
- this.mapComponent.smoother = new LineOfSightSmoother();
894
- break;
895
733
  }
896
734
  }
897
- this.mapComponent.initialized = true;
898
- }
899
- /**
900
- * @zh 构建代理优先级队列
901
- * @en Build agent priority queue
902
- */
903
- buildAgentQueue(entities) {
904
- this.agentQueue.length = 0;
905
735
  for (const entity of entities) {
906
- const agent = entity.getComponent(PathfindingAgentComponent);
907
- if (!agent) continue;
908
- if (!agent.hasRequest && (agent.state === PathfindingState.Idle || agent.state === PathfindingState.Completed || agent.state === PathfindingState.Cancelled)) {
909
- continue;
736
+ const agent = entity.getComponent(NavigationAgentComponent);
737
+ if (!agent.enabled) continue;
738
+ if (!this.agentEnterTimes.has(entity.id)) {
739
+ this.agentEnterTimes.set(entity.id, this.currentTime);
910
740
  }
911
- this.agentQueue.push({
912
- entity,
913
- component: agent
741
+ const preferredVelocity = this.calculatePreferredVelocity(agent);
742
+ const agentData = this.buildAgentData(entity, agent, preferredVelocity);
743
+ agentDataMap.set(entity.id, agentData);
744
+ flowAgentDataList.push({
745
+ id: entity.id,
746
+ position: {
747
+ x: agent.position.x,
748
+ y: agent.position.y
749
+ },
750
+ destination: agent.destination,
751
+ currentWaypoint: agent.getCurrentWaypoint(),
752
+ radius: agent.radius,
753
+ priority: 50,
754
+ enterTime: this.agentEnterTimes.get(entity.id)
914
755
  });
915
756
  }
916
- this.agentQueue.sort((a, b) => a.component.priority - b.component.priority);
757
+ if (this.config.enableFlowControl && this.flowController) {
758
+ this.flowController.update(flowAgentDataList, deltaTime);
759
+ }
760
+ if (this.config.enableLocalAvoidance && this.localAvoidance && agentDataMap.size > 0) {
761
+ const proceedingAgents = [];
762
+ const proceedingEntityIds = /* @__PURE__ */ new Set();
763
+ for (const entity of entities) {
764
+ const agent = entity.getComponent(NavigationAgentComponent);
765
+ if (!agent.enabled) continue;
766
+ const flowResult = this.flowController?.getFlowControl(entity.id);
767
+ const permission = flowResult?.permission ?? PassPermission.Proceed;
768
+ if (permission === PassPermission.Wait) {
769
+ this.handleWaitingAgent(entity, agent, flowResult.waitPosition, deltaTime);
770
+ } else {
771
+ const agentData = agentDataMap.get(entity.id);
772
+ if (agentData) {
773
+ const speedMult = flowResult?.speedMultiplier ?? 1;
774
+ if (speedMult < 1) {
775
+ const modifiedAgentData = {
776
+ ...agentData,
777
+ preferredVelocity: {
778
+ x: agentData.preferredVelocity.x * speedMult,
779
+ y: agentData.preferredVelocity.y * speedMult
780
+ }
781
+ };
782
+ proceedingAgents.push(modifiedAgentData);
783
+ } else {
784
+ proceedingAgents.push(agentData);
785
+ }
786
+ proceedingEntityIds.add(entity.id);
787
+ }
788
+ }
789
+ }
790
+ if (proceedingAgents.length > 0) {
791
+ const avoidanceResults = this.localAvoidance.computeBatchAvoidance(proceedingAgents, this.dynamicObstacles, deltaTime);
792
+ for (const entity of entities) {
793
+ if (!proceedingEntityIds.has(entity.id)) continue;
794
+ const agent = entity.getComponent(NavigationAgentComponent);
795
+ if (!agent.enabled) continue;
796
+ const result = avoidanceResults.get(entity.id);
797
+ if (result) {
798
+ this.applyAvoidanceResult(entity, agent, result.velocity, deltaTime);
799
+ } else {
800
+ const agentData = agentDataMap.get(entity.id);
801
+ if (agentData) {
802
+ this.applyAvoidanceResult(entity, agent, agentData.preferredVelocity, deltaTime);
803
+ }
804
+ }
805
+ }
806
+ }
807
+ } else {
808
+ for (const entity of entities) {
809
+ const agent = entity.getComponent(NavigationAgentComponent);
810
+ if (!agent.enabled) continue;
811
+ const flowResult = this.flowController?.getFlowControl(entity.id);
812
+ const permission = flowResult?.permission ?? PassPermission.Proceed;
813
+ if (permission === PassPermission.Wait && this.config.enableFlowControl && this.flowController) {
814
+ this.handleWaitingAgent(entity, agent, flowResult.waitPosition, deltaTime);
815
+ } else {
816
+ const agentData = agentDataMap.get(entity.id);
817
+ if (agentData) {
818
+ let velocity = agentData.preferredVelocity;
819
+ const speedMult = flowResult?.speedMultiplier ?? 1;
820
+ if (speedMult < 1) {
821
+ velocity = {
822
+ x: velocity.x * speedMult,
823
+ y: velocity.y * speedMult
824
+ };
825
+ }
826
+ this.applyAvoidanceResult(entity, agent, velocity, deltaTime);
827
+ }
828
+ }
829
+ }
830
+ }
831
+ if (this.config.enableAgentCollisionResolution && this.collisionResolver) {
832
+ this.resolveAgentCollisions(entities);
833
+ }
834
+ this.cleanupEnterTimes(entities);
917
835
  }
918
836
  /**
919
- * @zh 使用预算处理代理
920
- * @en Process agents with budget
837
+ * @zh 处理等待中的代理
838
+ * @en Handle waiting agent
921
839
  */
922
- processAgentsWithBudget() {
923
- const pathfinder = this.mapComponent.pathfinder;
924
- const maxAgents = this.mapComponent.maxAgentsPerFrame;
925
- let remainingBudget = this.mapComponent.iterationsBudget;
926
- let agentsProcessed = 0;
927
- for (const { component: agent } of this.agentQueue) {
928
- if (agentsProcessed >= maxAgents || remainingBudget <= 0) {
929
- break;
930
- }
931
- if (agent.hasRequest && agent.state === PathfindingState.Idle) {
932
- this.startNewRequest(agent, pathfinder);
933
- }
934
- if (agent.state === PathfindingState.InProgress) {
935
- const iterations = Math.min(agent.maxIterationsPerFrame, remainingBudget);
936
- const progress = pathfinder.step(agent.currentRequestId, iterations);
937
- this.updateAgentFromProgress(agent, progress, pathfinder);
938
- remainingBudget -= progress.nodesSearched;
939
- this.mapComponent.iterationsUsedThisFrame += progress.nodesSearched;
840
+ handleWaitingAgent(entity, agent, waitPosition, deltaTime) {
841
+ if (waitPosition) {
842
+ const dx = waitPosition.x - agent.position.x;
843
+ const dy = waitPosition.y - agent.position.y;
844
+ const dist = Math.sqrt(dx * dx + dy * dy);
845
+ if (dist > agent.arrivalThreshold) {
846
+ const speed = Math.min(agent.maxSpeed * 0.5, dist);
847
+ const velocity = {
848
+ x: dx / dist * speed,
849
+ y: dy / dist * speed
850
+ };
851
+ this.applyAvoidanceResult(entity, agent, velocity, deltaTime);
852
+ } else {
853
+ agent.velocity = {
854
+ x: 0,
855
+ y: 0
856
+ };
940
857
  }
941
- agentsProcessed++;
858
+ } else {
859
+ agent.velocity = {
860
+ x: 0,
861
+ y: 0
862
+ };
942
863
  }
943
- this.mapComponent.agentsProcessedThisFrame = agentsProcessed;
944
864
  }
945
865
  /**
946
- * @zh 启动新的寻路请求
947
- * @en Start new pathfinding request
866
+ * @zh 清理已移除代理的进入时间记录
867
+ * @en Cleanup enter times for removed agents
948
868
  */
949
- startNewRequest(agent, pathfinder) {
950
- if (agent.currentRequestId >= 0) {
951
- pathfinder.cancel(agent.currentRequestId);
952
- pathfinder.cleanup(agent.currentRequestId);
869
+ cleanupEnterTimes(entities) {
870
+ const activeIds = new Set(entities.map((e) => e.id));
871
+ for (const id of this.agentEnterTimes.keys()) {
872
+ if (!activeIds.has(id)) {
873
+ this.agentEnterTimes.delete(id);
874
+ }
953
875
  }
954
- const request = pathfinder.requestPath(agent.x, agent.y, agent.targetX, agent.targetY, {
955
- priority: agent.priority
956
- });
957
- agent.currentRequestId = request.id;
958
- agent.state = PathfindingState.InProgress;
959
- agent.hasRequest = false;
960
- agent.progress = 0;
961
- agent.path = [];
962
- agent.pathIndex = 0;
963
- this.mapComponent.activeRequests++;
964
876
  }
965
877
  /**
966
- * @zh 从进度更新代理状态
967
- * @en Update agent state from progress
878
+ * @zh 处理路径规划(同步模式)
879
+ * @en Process path planning (synchronous mode)
968
880
  */
969
- updateAgentFromProgress(agent, progress, pathfinder) {
970
- agent.state = progress.state;
971
- agent.progress = progress.estimatedProgress;
972
- agent.onPathProgress?.(progress.estimatedProgress);
973
- if (progress.state === PathfindingState.Completed) {
974
- const result = pathfinder.getResult(agent.currentRequestId);
975
- if (result && result.found) {
976
- const smoother = this.mapComponent?.smoother;
977
- const map = this.mapComponent?.map;
978
- if (smoother && map && this.mapComponent?.enableSmoothing) {
979
- agent.path = smoother.smooth(result.path, map);
980
- } else {
981
- agent.path = [
982
- ...result.path
983
- ];
984
- }
985
- agent.pathIndex = 0;
986
- agent.pathCost = result.cost;
987
- agent.onPathComplete?.(true, agent.path);
881
+ processPathPlanning(agent, deltaTime) {
882
+ if (!agent.destination || agent.state === NavigationState.Arrived) {
883
+ return;
884
+ }
885
+ const needsRepath = agent.path.length === 0 || agent.autoRepath && this.currentTime - agent.lastRepathTime > agent.repathInterval;
886
+ if (needsRepath && this.pathPlanner) {
887
+ const result = this.pathPlanner.findPath(agent.position, agent.destination, {
888
+ agentRadius: agent.radius
889
+ });
890
+ if (result.found) {
891
+ agent.path = result.path.map((p) => ({
892
+ x: p.x,
893
+ y: p.y
894
+ }));
895
+ agent.currentWaypointIndex = 0;
896
+ agent.state = NavigationState.Navigating;
988
897
  } else {
898
+ agent.state = NavigationState.Unreachable;
989
899
  agent.path = [];
990
- agent.state = PathfindingState.Failed;
991
- agent.onPathComplete?.(false, []);
992
900
  }
993
- pathfinder.cleanup(agent.currentRequestId);
994
- this.mapComponent.activeRequests--;
995
- } else if (progress.state === PathfindingState.Failed) {
996
- agent.path = [];
997
- agent.onPathComplete?.(false, []);
998
- pathfinder.cleanup(agent.currentRequestId);
999
- this.mapComponent.activeRequests--;
901
+ agent.lastRepathTime = this.currentTime;
1000
902
  }
903
+ this.advanceWaypoint(agent);
1001
904
  }
1002
905
  /**
1003
- * @zh 周期性验证路径有效性
1004
- * @en Periodically validate path validity
906
+ * @zh 处理增量路径规划(时间切片模式)
907
+ * @en Process incremental path planning (time slicing mode)
908
+ *
909
+ * @param agents - @zh 代理列表 @en Agent list
910
+ * @param entityMap - @zh 实体 ID 到代理的映射 @en Entity ID to agent mapping
1005
911
  */
1006
- validatePaths(entities) {
1007
- const map = this.mapComponent?.map;
1008
- if (!map) return;
1009
- for (const entity of entities) {
1010
- const agent = entity.getComponent(PathfindingAgentComponent);
1011
- if (!agent || !agent.enableDynamicReplan) continue;
1012
- if (agent.path.length === 0 || agent.isPathComplete()) continue;
1013
- if (this.frameCounter - agent.lastValidationFrame < agent.validationInterval) {
912
+ processIncrementalPathPlanning(agents, entityMap) {
913
+ if (!this.pathPlanner || !this.isIncrementalPlanner) return;
914
+ const planner = this.pathPlanner;
915
+ let remainingBudget = this.config.iterationsBudget;
916
+ let processedCount = 0;
917
+ for (const { entityId, agent } of agents) {
918
+ if (!agent.destination || agent.state === NavigationState.Arrived) {
919
+ if (agent.currentRequestId >= 0) {
920
+ planner.cleanup(agent.currentRequestId);
921
+ agent.currentRequestId = -1;
922
+ agent.isComputingPath = false;
923
+ }
1014
924
  continue;
1015
925
  }
1016
- agent.lastValidationFrame = this.frameCounter;
1017
- const checkEnd = Math.min(agent.pathIndex + agent.lookaheadDistance, agent.path.length);
1018
- const result = this.pathValidator.validatePath(agent.path, agent.pathIndex, checkEnd, map);
1019
- if (!result.valid) {
1020
- agent.x = agent.path[agent.pathIndex]?.x ?? agent.x;
1021
- agent.y = agent.path[agent.pathIndex]?.y ?? agent.y;
1022
- agent.requestPathTo(agent.targetX, agent.targetY);
926
+ const needsRepath = !agent.isComputingPath && agent.currentRequestId < 0 && (agent.path.length === 0 || agent.autoRepath && this.currentTime - agent.lastRepathTime > agent.repathInterval);
927
+ if (needsRepath) {
928
+ this.pendingPathRequests.add(entityId);
1023
929
  }
1024
930
  }
1025
- }
1026
- };
1027
- __name(_PathfindingSystem, "PathfindingSystem");
1028
- var PathfindingSystem = _PathfindingSystem;
1029
- PathfindingSystem = _ts_decorate3([
1030
- ECSSystem("Pathfinding", {
1031
- updateOrder: 0
1032
- }),
1033
- _ts_metadata3("design:type", Function),
1034
- _ts_metadata3("design:paramtypes", [])
1035
- ], PathfindingSystem);
1036
-
1037
- // src/ecs/AvoidanceAgentComponent.ts
1038
- import { Component as Component3, ECSComponent as ECSComponent3, Serializable as Serializable3, Serialize as Serialize3, Property as Property3 } from "@esengine/ecs-framework";
1039
- function _ts_decorate4(decorators, target, key, desc) {
1040
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1041
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1042
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1043
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1044
- }
1045
- __name(_ts_decorate4, "_ts_decorate");
1046
- function _ts_metadata4(k, v) {
1047
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1048
- }
1049
- __name(_ts_metadata4, "_ts_metadata");
1050
- var _AvoidanceAgentComponent = class _AvoidanceAgentComponent extends Component3 {
1051
- constructor() {
1052
- super(...arguments);
1053
- // =========================================================================
1054
- // 物理属性 | Physical Properties
1055
- // =========================================================================
1056
- /**
1057
- * @zh 代理半径
1058
- * @en Agent radius
1059
- *
1060
- * @zh 用于碰撞检测和避让计算
1061
- * @en Used for collision detection and avoidance computation
1062
- */
1063
- __publicField(this, "radius", DEFAULT_AGENT_PARAMS.radius);
1064
- /**
1065
- * @zh 最大速度
1066
- * @en Maximum speed
1067
- */
1068
- __publicField(this, "maxSpeed", DEFAULT_AGENT_PARAMS.maxSpeed);
1069
- // =========================================================================
1070
- // ORCA 参数 | ORCA Parameters
1071
- // =========================================================================
1072
- /**
1073
- * @zh 邻居检测距离
1074
- * @en Neighbor detection distance
1075
- *
1076
- * @zh 只考虑此范围内的其他代理
1077
- * @en Only considers other agents within this range
1078
- */
1079
- __publicField(this, "neighborDist", DEFAULT_AGENT_PARAMS.neighborDist);
1080
- /**
1081
- * @zh 最大邻居数量
1082
- * @en Maximum number of neighbors to consider
1083
- *
1084
- * @zh 限制计算量,优先考虑最近的邻居
1085
- * @en Limits computation, prioritizes closest neighbors
1086
- */
1087
- __publicField(this, "maxNeighbors", DEFAULT_AGENT_PARAMS.maxNeighbors);
1088
- /**
1089
- * @zh 代理避让时间视野(秒)
1090
- * @en Time horizon for agent avoidance (seconds)
1091
- *
1092
- * @zh 更大的值会让代理更早开始避让,但可能导致过于保守
1093
- * @en Larger values make agents start avoiding earlier, but may be too conservative
1094
- */
1095
- __publicField(this, "timeHorizon", DEFAULT_AGENT_PARAMS.timeHorizon);
1096
- /**
1097
- * @zh 障碍物避让时间视野(秒)
1098
- * @en Time horizon for obstacle avoidance (seconds)
1099
- */
1100
- __publicField(this, "timeHorizonObst", DEFAULT_AGENT_PARAMS.timeHorizonObst);
1101
- // =========================================================================
1102
- // 位置和速度(运行时状态)| Position & Velocity (Runtime State)
1103
- // =========================================================================
1104
- /**
1105
- * @zh 当前位置 X
1106
- * @en Current position X
1107
- *
1108
- * @zh 如果实体有 Transform 组件,系统会自动同步
1109
- * @en If entity has Transform component, system will sync automatically
1110
- */
1111
- __publicField(this, "positionX", 0);
1112
- /**
1113
- * @zh 当前位置 Y
1114
- * @en Current position Y
1115
- */
1116
- __publicField(this, "positionY", 0);
1117
- /**
1118
- * @zh 当前速度 X
1119
- * @en Current velocity X
1120
- */
1121
- __publicField(this, "velocityX", 0);
1122
- /**
1123
- * @zh 当前速度 Y
1124
- * @en Current velocity Y
1125
- */
1126
- __publicField(this, "velocityY", 0);
1127
- /**
1128
- * @zh 首选速度 X(通常指向目标方向)
1129
- * @en Preferred velocity X (usually towards target)
1130
- */
1131
- __publicField(this, "preferredVelocityX", 0);
1132
- /**
1133
- * @zh 首选速度 Y
1134
- * @en Preferred velocity Y
1135
- */
1136
- __publicField(this, "preferredVelocityY", 0);
1137
- /**
1138
- * @zh ORCA 计算的新速度 X
1139
- * @en New velocity X computed by ORCA
1140
- */
1141
- __publicField(this, "newVelocityX", 0);
1142
- /**
1143
- * @zh ORCA 计算的新速度 Y
1144
- * @en New velocity Y computed by ORCA
1145
- */
1146
- __publicField(this, "newVelocityY", 0);
1147
- // =========================================================================
1148
- // 配置选项 | Configuration Options
1149
- // =========================================================================
1150
- /**
1151
- * @zh 是否启用避让
1152
- * @en Whether avoidance is enabled
1153
- */
1154
- __publicField(this, "enabled", true);
1155
- /**
1156
- * @zh 是否自动应用新速度
1157
- * @en Whether to automatically apply new velocity
1158
- *
1159
- * @zh 如果为 true,系统会在计算后自动将 newVelocity 赋值给 velocity
1160
- * @en If true, system will automatically assign newVelocity to velocity after computation
1161
- */
1162
- __publicField(this, "autoApplyVelocity", true);
1163
- }
1164
- // =========================================================================
1165
- // 公共方法 | Public Methods
1166
- // =========================================================================
1167
- /**
1168
- * @zh 设置位置
1169
- * @en Set position
1170
- */
1171
- setPosition(x, y) {
1172
- this.positionX = x;
1173
- this.positionY = y;
1174
- }
1175
- /**
1176
- * @zh 设置当前速度
1177
- * @en Set current velocity
1178
- */
1179
- setVelocity(x, y) {
1180
- this.velocityX = x;
1181
- this.velocityY = y;
931
+ const pendingArray = Array.from(this.pendingPathRequests);
932
+ pendingArray.sort((a, b) => {
933
+ const agentA = agents.find((x) => x.entityId === a);
934
+ const agentB = agents.find((x) => x.entityId === b);
935
+ return (agentA?.agent.priority ?? 50) - (agentB?.agent.priority ?? 50);
936
+ });
937
+ for (const entityId of pendingArray) {
938
+ if (processedCount >= this.config.maxAgentsPerFrame) break;
939
+ const entry = agents.find((x) => x.entityId === entityId);
940
+ const destination = entry?.agent.destination;
941
+ if (!entry || !destination) {
942
+ this.pendingPathRequests.delete(entityId);
943
+ continue;
944
+ }
945
+ const { agent } = entry;
946
+ const request = planner.requestPath(agent.position, destination, {
947
+ agentRadius: agent.radius
948
+ });
949
+ agent.currentRequestId = request.id;
950
+ agent.isComputingPath = true;
951
+ agent.pathProgress = 0;
952
+ this.pendingPathRequests.delete(entityId);
953
+ processedCount++;
954
+ }
955
+ const activeAgents = agents.filter((x) => x.agent.isComputingPath && x.agent.currentRequestId >= 0);
956
+ activeAgents.sort((a, b) => a.agent.priority - b.agent.priority);
957
+ for (const { agent } of activeAgents) {
958
+ if (remainingBudget <= 0) break;
959
+ const iterations = Math.min(remainingBudget, this.config.maxIterationsPerAgent);
960
+ const progress = planner.step(agent.currentRequestId, iterations);
961
+ remainingBudget -= progress.nodesSearched;
962
+ agent.pathProgress = progress.estimatedProgress;
963
+ if (progress.state === PathPlanState.Completed) {
964
+ const result = planner.getResult(agent.currentRequestId);
965
+ planner.cleanup(agent.currentRequestId);
966
+ if (result && result.found) {
967
+ agent.path = result.path.map((p) => ({
968
+ x: p.x,
969
+ y: p.y
970
+ }));
971
+ agent.currentWaypointIndex = 0;
972
+ agent.state = NavigationState.Navigating;
973
+ } else {
974
+ agent.state = NavigationState.Unreachable;
975
+ agent.path = [];
976
+ }
977
+ agent.currentRequestId = -1;
978
+ agent.isComputingPath = false;
979
+ agent.pathProgress = 0;
980
+ agent.lastRepathTime = this.currentTime;
981
+ } else if (progress.state === PathPlanState.Failed || progress.state === PathPlanState.Cancelled) {
982
+ planner.cleanup(agent.currentRequestId);
983
+ agent.state = NavigationState.Unreachable;
984
+ agent.path = [];
985
+ agent.currentRequestId = -1;
986
+ agent.isComputingPath = false;
987
+ agent.pathProgress = 0;
988
+ agent.lastRepathTime = this.currentTime;
989
+ }
990
+ }
991
+ for (const { agent } of agents) {
992
+ this.advanceWaypoint(agent);
993
+ }
1182
994
  }
1183
995
  /**
1184
- * @zh 设置首选速度
1185
- * @en Set preferred velocity
996
+ * @zh 推进路径点
997
+ * @en Advance waypoint
1186
998
  */
1187
- setPreferredVelocity(x, y) {
1188
- this.preferredVelocityX = x;
1189
- this.preferredVelocityY = y;
999
+ advanceWaypoint(agent) {
1000
+ while (agent.currentWaypointIndex < agent.path.length) {
1001
+ const waypoint = agent.path[agent.currentWaypointIndex];
1002
+ const dx = waypoint.x - agent.position.x;
1003
+ const dy = waypoint.y - agent.position.y;
1004
+ const dist = Math.sqrt(dx * dx + dy * dy);
1005
+ if (dist < agent.waypointThreshold) {
1006
+ agent.currentWaypointIndex++;
1007
+ } else {
1008
+ break;
1009
+ }
1010
+ }
1190
1011
  }
1191
1012
  /**
1192
- * @zh 设置首选速度朝向目标
1193
- * @en Set preferred velocity towards target
1194
- *
1195
- * @param targetX - @zh 目标 X @en Target X
1196
- * @param targetY - @zh 目标 Y @en Target Y
1197
- * @param currentX - @zh 当前 X(可选,默认使用 positionX)@en Current X (optional, defaults to positionX)
1198
- * @param currentY - @zh 当前 Y(可选,默认使用 positionY)@en Current Y (optional, defaults to positionY)
1013
+ * @zh 计算首选速度
1014
+ * @en Calculate preferred velocity
1199
1015
  */
1200
- setPreferredVelocityTowards(targetX, targetY, currentX, currentY) {
1201
- const x = currentX ?? this.positionX;
1202
- const y = currentY ?? this.positionY;
1203
- const dx = targetX - x;
1204
- const dy = targetY - y;
1205
- const dist = Math.sqrt(dx * dx + dy * dy);
1206
- if (dist > 1e-4) {
1207
- const speed = Math.min(this.maxSpeed, dist);
1208
- this.preferredVelocityX = dx / dist * speed;
1209
- this.preferredVelocityY = dy / dist * speed;
1016
+ calculatePreferredVelocity(agent) {
1017
+ if (!agent.destination) {
1018
+ return {
1019
+ x: 0,
1020
+ y: 0
1021
+ };
1022
+ }
1023
+ let targetX, targetY;
1024
+ let isLastWaypoint = false;
1025
+ if (agent.currentWaypointIndex < agent.path.length) {
1026
+ const waypoint = agent.path[agent.currentWaypointIndex];
1027
+ targetX = waypoint.x;
1028
+ targetY = waypoint.y;
1029
+ isLastWaypoint = agent.currentWaypointIndex === agent.path.length - 1;
1210
1030
  } else {
1211
- this.preferredVelocityX = 0;
1212
- this.preferredVelocityY = 0;
1031
+ targetX = agent.destination.x;
1032
+ targetY = agent.destination.y;
1033
+ isLastWaypoint = true;
1213
1034
  }
1035
+ const dx = targetX - agent.position.x;
1036
+ const dy = targetY - agent.position.y;
1037
+ const dist = Math.sqrt(dx * dx + dy * dy);
1038
+ if (dist < 1e-4) {
1039
+ return {
1040
+ x: 0,
1041
+ y: 0
1042
+ };
1043
+ }
1044
+ const speed = isLastWaypoint ? Math.min(agent.maxSpeed, dist) : agent.maxSpeed;
1045
+ return {
1046
+ x: dx / dist * speed,
1047
+ y: dy / dist * speed
1048
+ };
1214
1049
  }
1215
1050
  /**
1216
- * @zh 应用 ORCA 计算的新速度
1217
- * @en Apply new velocity computed by ORCA
1218
- */
1219
- applyNewVelocity() {
1220
- this.velocityX = this.newVelocityX;
1221
- this.velocityY = this.newVelocityY;
1222
- }
1223
- /**
1224
- * @zh 获取新速度的长度
1225
- * @en Get length of new velocity
1051
+ * @zh 构建代理数据
1052
+ * @en Build agent data
1226
1053
  */
1227
- getNewSpeed() {
1228
- return Math.sqrt(this.newVelocityX * this.newVelocityX + this.newVelocityY * this.newVelocityY);
1054
+ buildAgentData(entity, agent, preferredVelocity) {
1055
+ return {
1056
+ id: entity.id,
1057
+ position: {
1058
+ x: agent.position.x,
1059
+ y: agent.position.y
1060
+ },
1061
+ velocity: {
1062
+ x: agent.velocity.x,
1063
+ y: agent.velocity.y
1064
+ },
1065
+ preferredVelocity,
1066
+ radius: agent.radius,
1067
+ maxSpeed: agent.maxSpeed
1068
+ };
1229
1069
  }
1230
1070
  /**
1231
- * @zh 获取当前速度的长度
1232
- * @en Get length of current velocity
1071
+ * @zh 应用避让结果
1072
+ * @en Apply avoidance result
1233
1073
  */
1234
- getCurrentSpeed() {
1235
- return Math.sqrt(this.velocityX * this.velocityX + this.velocityY * this.velocityY);
1074
+ applyAvoidanceResult(entity, agent, newVelocity, deltaTime) {
1075
+ const allObstacles = this.getAllObstaclesForCollision();
1076
+ if (this.config.enableCollisionResolution && this.collisionResolver && allObstacles.length > 0) {
1077
+ newVelocity = this.collisionResolver.validateVelocity(agent.position, newVelocity, agent.radius, allObstacles, deltaTime);
1078
+ }
1079
+ if (agent.smoothSteering) {
1080
+ newVelocity = this.applySmoothSteering(agent, newVelocity, deltaTime);
1081
+ }
1082
+ agent.velocity = {
1083
+ x: newVelocity.x,
1084
+ y: newVelocity.y
1085
+ };
1086
+ let newPosition = {
1087
+ x: agent.position.x + newVelocity.x * deltaTime,
1088
+ y: agent.position.y + newVelocity.y * deltaTime
1089
+ };
1090
+ if (this.config.enableCollisionResolution && this.collisionResolver && allObstacles.length > 0) {
1091
+ newPosition = this.collisionResolver.resolveCollision(newPosition, agent.radius, allObstacles);
1092
+ }
1093
+ agent.position = newPosition;
1094
+ this.checkArrival(agent);
1236
1095
  }
1237
1096
  /**
1238
- * @zh 停止代理
1239
- * @en Stop the agent
1097
+ * @zh 应用平滑转向
1098
+ * @en Apply smooth steering
1240
1099
  */
1241
- stop() {
1242
- this.velocityX = 0;
1243
- this.velocityY = 0;
1244
- this.preferredVelocityX = 0;
1245
- this.preferredVelocityY = 0;
1246
- this.newVelocityX = 0;
1247
- this.newVelocityY = 0;
1100
+ applySmoothSteering(agent, targetVelocity, deltaTime) {
1101
+ const maxChange = agent.acceleration * deltaTime;
1102
+ const dvx = targetVelocity.x - agent.velocity.x;
1103
+ const dvy = targetVelocity.y - agent.velocity.y;
1104
+ const changeMag = Math.sqrt(dvx * dvx + dvy * dvy);
1105
+ if (changeMag <= maxChange) {
1106
+ return targetVelocity;
1107
+ }
1108
+ const factor = maxChange / changeMag;
1109
+ const newVel = {
1110
+ x: agent.velocity.x + dvx * factor,
1111
+ y: agent.velocity.y + dvy * factor
1112
+ };
1113
+ const targetSpeed = Math.sqrt(targetVelocity.x * targetVelocity.x + targetVelocity.y * targetVelocity.y);
1114
+ const newSpeed = Math.sqrt(newVel.x * newVel.x + newVel.y * newVel.y);
1115
+ if (newSpeed > 1e-4 && targetSpeed > 1e-4) {
1116
+ const scale = targetSpeed / newSpeed;
1117
+ newVel.x *= scale;
1118
+ newVel.y *= scale;
1119
+ }
1120
+ return newVel;
1248
1121
  }
1249
1122
  /**
1250
- * @zh 重置组件状态
1251
- * @en Reset component state
1123
+ * @zh 检查是否到达目标
1124
+ * @en Check if arrived at destination
1252
1125
  */
1253
- reset() {
1254
- this.positionX = 0;
1255
- this.positionY = 0;
1256
- this.velocityX = 0;
1257
- this.velocityY = 0;
1258
- this.preferredVelocityX = 0;
1259
- this.preferredVelocityY = 0;
1260
- this.newVelocityX = 0;
1261
- this.newVelocityY = 0;
1126
+ checkArrival(agent) {
1127
+ if (!agent.destination) return;
1128
+ const dx = agent.destination.x - agent.position.x;
1129
+ const dy = agent.destination.y - agent.position.y;
1130
+ const dist = Math.sqrt(dx * dx + dy * dy);
1131
+ if (dist < agent.arrivalThreshold) {
1132
+ agent.state = NavigationState.Arrived;
1133
+ agent.velocity = {
1134
+ x: 0,
1135
+ y: 0
1136
+ };
1137
+ }
1262
1138
  }
1263
1139
  /**
1264
- * @zh 组件从实体移除时调用
1265
- * @en Called when component is removed from entity
1140
+ * @zh 解决代理间碰撞
1141
+ * @en Resolve agent-agent collisions
1266
1142
  */
1267
- onRemovedFromEntity() {
1268
- this.reset();
1143
+ resolveAgentCollisions(entities) {
1144
+ if (!this.collisionResolver) return;
1145
+ const corrections = /* @__PURE__ */ new Map();
1146
+ const activeEntities = entities.filter((e) => {
1147
+ const agent = e.getComponent(NavigationAgentComponent);
1148
+ return agent.enabled;
1149
+ });
1150
+ for (let i = 0; i < activeEntities.length; i++) {
1151
+ for (let j = i + 1; j < activeEntities.length; j++) {
1152
+ const entityA = activeEntities[i];
1153
+ const entityB = activeEntities[j];
1154
+ const agentA = entityA.getComponent(NavigationAgentComponent);
1155
+ const agentB = entityB.getComponent(NavigationAgentComponent);
1156
+ const collision = this.collisionResolver.detectAgentCollision(agentA.position, agentA.radius, agentB.position, agentB.radius);
1157
+ if (collision.collided) {
1158
+ const halfPush = (collision.penetration + 0.01) * 0.5;
1159
+ const corrA = corrections.get(entityA.id) ?? {
1160
+ x: 0,
1161
+ y: 0
1162
+ };
1163
+ corrA.x += collision.normal.x * halfPush;
1164
+ corrA.y += collision.normal.y * halfPush;
1165
+ corrections.set(entityA.id, corrA);
1166
+ const corrB = corrections.get(entityB.id) ?? {
1167
+ x: 0,
1168
+ y: 0
1169
+ };
1170
+ corrB.x -= collision.normal.x * halfPush;
1171
+ corrB.y -= collision.normal.y * halfPush;
1172
+ corrections.set(entityB.id, corrB);
1173
+ }
1174
+ }
1175
+ }
1176
+ const allObstacles = this.getAllObstaclesForCollision();
1177
+ for (const [entityId, correction] of corrections) {
1178
+ const entity = activeEntities.find((e) => e.id === entityId);
1179
+ if (!entity) continue;
1180
+ const agent = entity.getComponent(NavigationAgentComponent);
1181
+ let newPosition = {
1182
+ x: agent.position.x + correction.x,
1183
+ y: agent.position.y + correction.y
1184
+ };
1185
+ if (allObstacles.length > 0) {
1186
+ newPosition = this.collisionResolver.resolveCollision(newPosition, agent.radius, allObstacles);
1187
+ }
1188
+ agent.position = newPosition;
1189
+ }
1269
1190
  }
1270
1191
  };
1271
- __name(_AvoidanceAgentComponent, "AvoidanceAgentComponent");
1272
- var AvoidanceAgentComponent = _AvoidanceAgentComponent;
1273
- _ts_decorate4([
1274
- Serialize3(),
1275
- Property3({
1276
- type: "number",
1277
- label: "Radius",
1278
- min: 0.1,
1279
- max: 10
1192
+ __name(_NavigationSystem, "NavigationSystem");
1193
+ var NavigationSystem = _NavigationSystem;
1194
+ NavigationSystem = _ts_decorate2([
1195
+ ECSSystem("Navigation", {
1196
+ updateOrder: 45
1280
1197
  }),
1281
- _ts_metadata4("design:type", Number)
1282
- ], AvoidanceAgentComponent.prototype, "radius", void 0);
1283
- _ts_decorate4([
1284
- Serialize3(),
1285
- Property3({
1286
- type: "number",
1287
- label: "Max Speed",
1288
- min: 0.1,
1289
- max: 100
1290
- }),
1291
- _ts_metadata4("design:type", Number)
1292
- ], AvoidanceAgentComponent.prototype, "maxSpeed", void 0);
1293
- _ts_decorate4([
1294
- Serialize3(),
1295
- Property3({
1296
- type: "number",
1297
- label: "Neighbor Dist",
1298
- min: 1,
1299
- max: 100
1300
- }),
1301
- _ts_metadata4("design:type", Number)
1302
- ], AvoidanceAgentComponent.prototype, "neighborDist", void 0);
1303
- _ts_decorate4([
1304
- Serialize3(),
1305
- Property3({
1306
- type: "number",
1307
- label: "Max Neighbors",
1308
- min: 1,
1309
- max: 50
1310
- }),
1311
- _ts_metadata4("design:type", Number)
1312
- ], AvoidanceAgentComponent.prototype, "maxNeighbors", void 0);
1313
- _ts_decorate4([
1314
- Serialize3(),
1315
- Property3({
1316
- type: "number",
1317
- label: "Time Horizon",
1318
- min: 0.1,
1319
- max: 10
1320
- }),
1321
- _ts_metadata4("design:type", Number)
1322
- ], AvoidanceAgentComponent.prototype, "timeHorizon", void 0);
1323
- _ts_decorate4([
1324
- Serialize3(),
1325
- Property3({
1326
- type: "number",
1327
- label: "Time Horizon Obst",
1328
- min: 0.1,
1329
- max: 10
1330
- }),
1331
- _ts_metadata4("design:type", Number)
1332
- ], AvoidanceAgentComponent.prototype, "timeHorizonObst", void 0);
1333
- _ts_decorate4([
1334
- Serialize3(),
1335
- Property3({
1336
- type: "boolean",
1337
- label: "Enabled"
1338
- }),
1339
- _ts_metadata4("design:type", Boolean)
1340
- ], AvoidanceAgentComponent.prototype, "enabled", void 0);
1341
- _ts_decorate4([
1342
- Serialize3(),
1343
- Property3({
1344
- type: "boolean",
1345
- label: "Auto Apply"
1346
- }),
1347
- _ts_metadata4("design:type", Boolean)
1348
- ], AvoidanceAgentComponent.prototype, "autoApplyVelocity", void 0);
1349
- AvoidanceAgentComponent = _ts_decorate4([
1350
- ECSComponent3("AvoidanceAgent"),
1351
- Serializable3({
1352
- version: 1,
1353
- typeId: "AvoidanceAgent"
1354
- })
1355
- ], AvoidanceAgentComponent);
1198
+ _ts_metadata2("design:type", Function),
1199
+ _ts_metadata2("design:paramtypes", [
1200
+ typeof INavigationSystemConfig === "undefined" ? Object : INavigationSystemConfig
1201
+ ])
1202
+ ], NavigationSystem);
1356
1203
 
1357
- // src/ecs/AvoidanceWorldComponent.ts
1358
- import { Component as Component4, ECSComponent as ECSComponent4, Serializable as Serializable4, Serialize as Serialize4, Property as Property4 } from "@esengine/ecs-framework";
1359
- function _ts_decorate5(decorators, target, key, desc) {
1204
+ // src/ecs/ORCAConfigComponent.ts
1205
+ import { Component as Component2, ECSComponent as ECSComponent2, Serializable as Serializable2, Serialize as Serialize2, Property as Property2 } from "@esengine/ecs-framework";
1206
+ function _ts_decorate3(decorators, target, key, desc) {
1360
1207
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1361
1208
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1362
1209
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1363
1210
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1364
1211
  }
1365
- __name(_ts_decorate5, "_ts_decorate");
1366
- function _ts_metadata5(k, v) {
1212
+ __name(_ts_decorate3, "_ts_decorate");
1213
+ function _ts_metadata3(k, v) {
1367
1214
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1368
1215
  }
1369
- __name(_ts_metadata5, "_ts_metadata");
1370
- var _AvoidanceWorldComponent = class _AvoidanceWorldComponent extends Component4 {
1216
+ __name(_ts_metadata3, "_ts_metadata");
1217
+ var _ORCAConfigComponent = class _ORCAConfigComponent extends Component2 {
1371
1218
  constructor() {
1372
1219
  super(...arguments);
1373
- // =========================================================================
1374
- // ORCA 配置 | ORCA Configuration
1375
- // =========================================================================
1376
- /**
1377
- * @zh 默认时间视野(代理)
1378
- * @en Default time horizon for agents
1379
- */
1380
- __publicField(this, "defaultTimeHorizon", DEFAULT_ORCA_CONFIG.defaultTimeHorizon);
1381
- /**
1382
- * @zh 默认时间视野(障碍物)
1383
- * @en Default time horizon for obstacles
1384
- */
1385
- __publicField(this, "defaultTimeHorizonObst", DEFAULT_ORCA_CONFIG.defaultTimeHorizonObst);
1386
- /**
1387
- * @zh 时间步长
1388
- * @en Time step
1389
- */
1390
- __publicField(this, "timeStep", DEFAULT_ORCA_CONFIG.timeStep);
1391
- // =========================================================================
1392
- // 运行时实例(不序列化)| Runtime Instances (not serialized)
1393
- // =========================================================================
1394
- /**
1395
- * @zh ORCA 求解器实例
1396
- * @en ORCA solver instance
1397
- */
1398
- __publicField(this, "solver", null);
1399
1220
  /**
1400
- * @zh KD-Tree 实例
1401
- * @en KD-Tree instance
1402
- */
1403
- __publicField(this, "kdTree", null);
1404
- /**
1405
- * @zh 静态障碍物列表
1406
- * @en List of static obstacles
1407
- */
1408
- __publicField(this, "obstacles", []);
1409
- /**
1410
- * @zh 是否已初始化
1411
- * @en Whether initialized
1221
+ * @zh 邻居检测距离
1222
+ * @en Neighbor detection distance
1223
+ *
1224
+ * @zh 代理检测邻居的最大距离,更大的值意味着更早开始避让但也更消耗性能
1225
+ * @en Maximum distance for detecting neighbors, larger value means earlier avoidance but more performance cost
1412
1226
  */
1413
- __publicField(this, "initialized", false);
1414
- // =========================================================================
1415
- // 统计信息 | Statistics
1416
- // =========================================================================
1227
+ __publicField(this, "neighborDist", 15);
1417
1228
  /**
1418
- * @zh 当前代理数量
1419
- * @en Current agent count
1229
+ * @zh 最大邻居数量
1230
+ * @en Maximum number of neighbors
1231
+ *
1232
+ * @zh 计算避让时考虑的最大邻居数量,更多邻居意味着更精确但也更消耗性能
1233
+ * @en Maximum neighbors considered for avoidance, more neighbors means more accurate but slower
1420
1234
  */
1421
- __publicField(this, "agentCount", 0);
1235
+ __publicField(this, "maxNeighbors", 10);
1422
1236
  /**
1423
- * @zh 本帧处理的代理数
1424
- * @en Agents processed this frame
1237
+ * @zh 代理避让时间视野
1238
+ * @en Time horizon for agent avoidance
1239
+ *
1240
+ * @zh 预测其他代理未来位置的时间范围,更长意味着更平滑但可能过度避让
1241
+ * @en Time range for predicting other agents' future positions, longer means smoother but may over-avoid
1425
1242
  */
1426
- __publicField(this, "agentsProcessedThisFrame", 0);
1243
+ __publicField(this, "timeHorizon", 2);
1427
1244
  /**
1428
- * @zh 本帧 ORCA 计算耗时(毫秒)
1429
- * @en ORCA computation time this frame (ms)
1245
+ * @zh 障碍物避让时间视野
1246
+ * @en Time horizon for obstacle avoidance
1247
+ *
1248
+ * @zh 预测与障碍物碰撞的时间范围,通常比代理视野短
1249
+ * @en Time range for predicting obstacle collisions, usually shorter than agent horizon
1430
1250
  */
1431
- __publicField(this, "computeTimeMs", 0);
1432
- }
1433
- // =========================================================================
1434
- // 公共方法 | Public Methods
1435
- // =========================================================================
1436
- /**
1437
- * @zh 获取 ORCA 配置
1438
- * @en Get ORCA configuration
1439
- */
1440
- getConfig() {
1441
- return {
1442
- defaultTimeHorizon: this.defaultTimeHorizon,
1443
- defaultTimeHorizonObst: this.defaultTimeHorizonObst,
1444
- timeStep: this.timeStep
1445
- };
1446
- }
1447
- /**
1448
- * @zh 添加静态障碍物
1449
- * @en Add static obstacle
1450
- *
1451
- * @param obstacle - @zh 障碍物(顶点列表,逆时针顺序)@en Obstacle (vertex list, counter-clockwise)
1452
- */
1453
- addObstacle(obstacle) {
1454
- this.obstacles.push(obstacle);
1455
- }
1456
- /**
1457
- * @zh 添加矩形障碍物
1458
- * @en Add rectangular obstacle
1459
- *
1460
- * @param x - @zh 左下角 X @en Bottom-left X
1461
- * @param y - @zh 左下角 Y @en Bottom-left Y
1462
- * @param width - @zh 宽度 @en Width
1463
- * @param height - @zh 高度 @en Height
1464
- */
1465
- addRectObstacle(x, y, width, height) {
1466
- this.obstacles.push({
1467
- vertices: [
1468
- {
1469
- x,
1470
- y
1471
- },
1472
- {
1473
- x: x + width,
1474
- y
1475
- },
1476
- {
1477
- x: x + width,
1478
- y: y + height
1479
- },
1480
- {
1481
- x,
1482
- y: y + height
1483
- }
1484
- ]
1485
- });
1486
- }
1487
- /**
1488
- * @zh 移除所有障碍物
1489
- * @en Remove all obstacles
1490
- */
1491
- clearObstacles() {
1492
- this.obstacles = [];
1493
- }
1494
- /**
1495
- * @zh 重置统计信息
1496
- * @en Reset statistics
1497
- */
1498
- resetStats() {
1499
- this.agentsProcessedThisFrame = 0;
1500
- this.computeTimeMs = 0;
1501
- }
1502
- /**
1503
- * @zh 组件从实体移除时调用
1504
- * @en Called when component is removed from entity
1505
- */
1506
- onRemovedFromEntity() {
1507
- this.solver = null;
1508
- this.kdTree = null;
1509
- this.obstacles = [];
1510
- this.initialized = false;
1251
+ __publicField(this, "timeHorizonObst", 1);
1511
1252
  }
1512
1253
  };
1513
- __name(_AvoidanceWorldComponent, "AvoidanceWorldComponent");
1514
- var AvoidanceWorldComponent = _AvoidanceWorldComponent;
1515
- _ts_decorate5([
1516
- Serialize4(),
1517
- Property4({
1254
+ __name(_ORCAConfigComponent, "ORCAConfigComponent");
1255
+ var ORCAConfigComponent = _ORCAConfigComponent;
1256
+ _ts_decorate3([
1257
+ Serialize2(),
1258
+ Property2({
1259
+ type: "number",
1260
+ label: "Neighbor Dist",
1261
+ min: 1,
1262
+ max: 100
1263
+ }),
1264
+ _ts_metadata3("design:type", Number)
1265
+ ], ORCAConfigComponent.prototype, "neighborDist", void 0);
1266
+ _ts_decorate3([
1267
+ Serialize2(),
1268
+ Property2({
1269
+ type: "number",
1270
+ label: "Max Neighbors",
1271
+ min: 1,
1272
+ max: 50
1273
+ }),
1274
+ _ts_metadata3("design:type", Number)
1275
+ ], ORCAConfigComponent.prototype, "maxNeighbors", void 0);
1276
+ _ts_decorate3([
1277
+ Serialize2(),
1278
+ Property2({
1518
1279
  type: "number",
1519
1280
  label: "Time Horizon",
1520
1281
  min: 0.1,
1521
1282
  max: 10
1522
1283
  }),
1523
- _ts_metadata5("design:type", Number)
1524
- ], AvoidanceWorldComponent.prototype, "defaultTimeHorizon", void 0);
1525
- _ts_decorate5([
1526
- Serialize4(),
1527
- Property4({
1284
+ _ts_metadata3("design:type", Number)
1285
+ ], ORCAConfigComponent.prototype, "timeHorizon", void 0);
1286
+ _ts_decorate3([
1287
+ Serialize2(),
1288
+ Property2({
1528
1289
  type: "number",
1529
1290
  label: "Time Horizon Obst",
1530
1291
  min: 0.1,
1531
1292
  max: 10
1532
1293
  }),
1533
- _ts_metadata5("design:type", Number)
1534
- ], AvoidanceWorldComponent.prototype, "defaultTimeHorizonObst", void 0);
1535
- _ts_decorate5([
1536
- Serialize4(),
1537
- Property4({
1538
- type: "number",
1539
- label: "Time Step",
1540
- min: 1e-3,
1541
- max: 0.1
1542
- }),
1543
- _ts_metadata5("design:type", Number)
1544
- ], AvoidanceWorldComponent.prototype, "timeStep", void 0);
1545
- AvoidanceWorldComponent = _ts_decorate5([
1546
- ECSComponent4("AvoidanceWorld"),
1547
- Serializable4({
1294
+ _ts_metadata3("design:type", Number)
1295
+ ], ORCAConfigComponent.prototype, "timeHorizonObst", void 0);
1296
+ ORCAConfigComponent = _ts_decorate3([
1297
+ ECSComponent2("ORCAConfig"),
1298
+ Serializable2({
1548
1299
  version: 1,
1549
- typeId: "AvoidanceWorld"
1300
+ typeId: "ORCAConfig"
1550
1301
  })
1551
- ], AvoidanceWorldComponent);
1552
-
1553
- // src/ecs/LocalAvoidanceSystem.ts
1554
- import { EntitySystem as EntitySystem2, Matcher as Matcher2, ECSSystem as ECSSystem2 } from "@esengine/ecs-framework";
1555
- function _ts_decorate6(decorators, target, key, desc) {
1556
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1557
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1558
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1559
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1560
- }
1561
- __name(_ts_decorate6, "_ts_decorate");
1562
- function _ts_metadata6(k, v) {
1563
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1564
- }
1565
- __name(_ts_metadata6, "_ts_metadata");
1566
- var _LocalAvoidanceSystem = class _LocalAvoidanceSystem extends EntitySystem2 {
1567
- constructor() {
1568
- super(Matcher2.all(AvoidanceAgentComponent));
1569
- __publicField(this, "worldEntity", null);
1570
- __publicField(this, "worldComponent", null);
1571
- __publicField(this, "solver", null);
1572
- __publicField(this, "kdTree", null);
1573
- }
1574
- /**
1575
- * @zh 系统初始化
1576
- * @en System initialization
1577
- */
1578
- onInitialize() {
1579
- this.findWorldEntity();
1580
- this.initializeSolver();
1581
- }
1582
- /**
1583
- * @zh 系统激活时调用
1584
- * @en Called when system is enabled
1585
- */
1586
- onEnable() {
1587
- this.findWorldEntity();
1588
- }
1589
- /**
1590
- * @zh 处理实体
1591
- * @en Process entities
1592
- */
1593
- process(entities) {
1594
- if (entities.length === 0) return;
1595
- if (!this.solver) {
1596
- this.initializeSolver();
1597
- }
1598
- const startTime = performance.now();
1599
- const agents = this.collectAgentData(entities);
1600
- this.kdTree.build(agents);
1601
- const obstacles = this.worldComponent?.obstacles ?? [];
1602
- const deltaTime = this.worldComponent?.timeStep ?? 1 / 60;
1603
- for (let i = 0; i < entities.length; i++) {
1604
- const entity = entities[i];
1605
- const component = entity.getComponent(AvoidanceAgentComponent);
1606
- if (!component.enabled) continue;
1607
- const agent = agents[i];
1608
- const neighborResults = this.kdTree.queryNeighbors(agent.position, agent.neighborDist, agent.maxNeighbors, agent.id);
1609
- const neighbors = neighborResults.map((r) => r.agent);
1610
- const newVelocity = this.solver.computeNewVelocity(agent, neighbors, obstacles, deltaTime);
1611
- component.newVelocityX = newVelocity.x;
1612
- component.newVelocityY = newVelocity.y;
1613
- if (component.autoApplyVelocity) {
1614
- component.applyNewVelocity();
1615
- }
1616
- }
1617
- const endTime = performance.now();
1618
- if (this.worldComponent) {
1619
- this.worldComponent.agentCount = entities.length;
1620
- this.worldComponent.agentsProcessedThisFrame = entities.length;
1621
- this.worldComponent.computeTimeMs = endTime - startTime;
1622
- }
1623
- }
1624
- // =========================================================================
1625
- // 私有方法 | Private Methods
1626
- // =========================================================================
1627
- /**
1628
- * @zh 查找世界实体
1629
- * @en Find world entity
1630
- */
1631
- findWorldEntity() {
1632
- if (!this.scene) return;
1633
- const entities = this.scene.entities.findEntitiesWithComponent(AvoidanceWorldComponent);
1634
- if (entities.length > 0) {
1635
- const entity = entities[0];
1636
- const worldComp = entity.getComponent(AvoidanceWorldComponent);
1637
- if (worldComp) {
1638
- this.worldEntity = entity;
1639
- this.worldComponent = worldComp;
1640
- if (this.solver && !worldComp.solver) {
1641
- worldComp.solver = this.solver;
1642
- }
1643
- if (this.kdTree && !worldComp.kdTree) {
1644
- worldComp.kdTree = this.kdTree;
1645
- }
1646
- worldComp.initialized = true;
1647
- }
1648
- }
1649
- }
1650
- /**
1651
- * @zh 初始化求解器
1652
- * @en Initialize solver
1653
- */
1654
- initializeSolver() {
1655
- const config = this.worldComponent?.getConfig();
1656
- this.solver = createORCASolver(config);
1657
- this.kdTree = createKDTree();
1658
- if (this.worldComponent) {
1659
- this.worldComponent.solver = this.solver;
1660
- this.worldComponent.kdTree = this.kdTree;
1661
- }
1662
- }
1663
- /**
1664
- * @zh 收集代理数据
1665
- * @en Collect agent data
1666
- */
1667
- collectAgentData(entities) {
1668
- const agents = [];
1669
- for (const entity of entities) {
1670
- const avoidance = entity.getComponent(AvoidanceAgentComponent);
1671
- const pathAgent = entity.getComponent(PathfindingAgentComponent);
1672
- if (pathAgent) {
1673
- avoidance.positionX = pathAgent.x;
1674
- avoidance.positionY = pathAgent.y;
1675
- const waypoint = pathAgent.getNextWaypoint();
1676
- if (waypoint && avoidance.preferredVelocityX === 0 && avoidance.preferredVelocityY === 0) {
1677
- avoidance.setPreferredVelocityTowards(waypoint.x, waypoint.y);
1678
- }
1679
- }
1680
- agents.push({
1681
- id: entity.id,
1682
- position: {
1683
- x: avoidance.positionX,
1684
- y: avoidance.positionY
1685
- },
1686
- velocity: {
1687
- x: avoidance.velocityX,
1688
- y: avoidance.velocityY
1689
- },
1690
- preferredVelocity: {
1691
- x: avoidance.preferredVelocityX,
1692
- y: avoidance.preferredVelocityY
1693
- },
1694
- radius: avoidance.radius,
1695
- maxSpeed: avoidance.maxSpeed,
1696
- neighborDist: avoidance.neighborDist,
1697
- maxNeighbors: avoidance.maxNeighbors,
1698
- timeHorizon: avoidance.timeHorizon,
1699
- timeHorizonObst: avoidance.timeHorizonObst
1700
- });
1701
- }
1702
- return agents;
1703
- }
1704
- };
1705
- __name(_LocalAvoidanceSystem, "LocalAvoidanceSystem");
1706
- var LocalAvoidanceSystem = _LocalAvoidanceSystem;
1707
- LocalAvoidanceSystem = _ts_decorate6([
1708
- ECSSystem2("LocalAvoidance", {
1709
- updateOrder: 50
1710
- }),
1711
- _ts_metadata6("design:type", Function),
1712
- _ts_metadata6("design:paramtypes", [])
1713
- ], LocalAvoidanceSystem);
1302
+ ], ORCAConfigComponent);
1714
1303
  export {
1715
- AvoidanceAgentComponent,
1716
- AvoidanceWorldComponent,
1717
- LocalAvoidanceSystem,
1718
- PathfindingAgentComponent,
1719
- PathfindingMapComponent,
1720
- PathfindingSystem
1304
+ CollisionResolverAdapter,
1305
+ DEFAULT_FLOW_CONTROLLER_CONFIG,
1306
+ DEFAULT_ORCA_PARAMS,
1307
+ FlowController,
1308
+ GridPathfinderAdapter,
1309
+ IncrementalGridPathPlannerAdapter,
1310
+ NavMeshPathPlannerAdapter,
1311
+ NavigationAgentComponent,
1312
+ NavigationState,
1313
+ NavigationSystem,
1314
+ ORCAConfigComponent,
1315
+ ORCALocalAvoidanceAdapter,
1316
+ PassPermission,
1317
+ createAStarPlanner,
1318
+ createDefaultCollisionResolver,
1319
+ createFlowController,
1320
+ createHPAPlanner,
1321
+ createIncrementalAStarPlanner,
1322
+ createJPSPlanner,
1323
+ createNavMeshPathPlanner,
1324
+ createORCAAvoidance
1721
1325
  };
1722
1326
  //# sourceMappingURL=ecs.js.map