@esengine/pathfinding 13.1.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/{KDTree-2rs2EXvm.d.ts → CollisionResolver-CSgWsegP.d.ts} +122 -86
- package/dist/FlowController-Dc3nuLq5.d.ts +2751 -0
- package/dist/KDTree-BRpn7O8K.d.ts +216 -0
- package/dist/avoidance.d.ts +26 -4
- package/dist/avoidance.js +10 -2
- package/dist/{chunk-JTZP55BJ.js → chunk-3VEX32JO.js} +385 -9
- package/dist/chunk-3VEX32JO.js.map +1 -0
- package/dist/chunk-H5EFZBBT.js +1 -0
- package/dist/chunk-NIKT3PQC.js +3811 -0
- package/dist/chunk-NIKT3PQC.js.map +1 -0
- package/dist/ecs.d.ts +440 -647
- package/dist/ecs.js +1003 -1399
- package/dist/ecs.js.map +1 -1
- package/dist/index.d.ts +153 -611
- package/dist/index.js +1360 -1202
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/IIncrementalPathfinding-3qs7e_pO.d.ts +0 -450
- package/dist/LinearProgram-DyD3pI6v.d.ts +0 -56
- package/dist/chunk-JTZP55BJ.js.map +0 -1
- package/dist/chunk-KEYTX37K.js +0 -1
- package/dist/chunk-VNC2YAAL.js +0 -1650
- package/dist/chunk-VNC2YAAL.js.map +0 -1
- /package/dist/{chunk-KEYTX37K.js.map → chunk-H5EFZBBT.js.map} +0 -0
package/dist/ecs.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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/
|
|
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
|
|
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
|
-
//
|
|
54
|
+
// 核心物理属性 | Core Physical Properties
|
|
41
55
|
// =========================================================================
|
|
42
56
|
/**
|
|
43
|
-
* @zh
|
|
44
|
-
* @en
|
|
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, "
|
|
65
|
+
__publicField(this, "maxSpeed", 5);
|
|
47
66
|
/**
|
|
48
|
-
* @zh
|
|
49
|
-
* @en
|
|
67
|
+
* @zh 加速度(用于平滑移动)
|
|
68
|
+
* @en Acceleration (for smooth movement)
|
|
50
69
|
*/
|
|
51
|
-
__publicField(this, "
|
|
70
|
+
__publicField(this, "acceleration", 10);
|
|
52
71
|
// =========================================================================
|
|
53
|
-
//
|
|
72
|
+
// 寻路配置 | Pathfinding Configuration
|
|
54
73
|
// =========================================================================
|
|
55
74
|
/**
|
|
56
|
-
* @zh
|
|
57
|
-
* @en
|
|
75
|
+
* @zh 路径点到达阈值
|
|
76
|
+
* @en Waypoint arrival threshold
|
|
58
77
|
*/
|
|
59
|
-
__publicField(this, "
|
|
78
|
+
__publicField(this, "waypointThreshold", 0.5);
|
|
60
79
|
/**
|
|
61
|
-
* @zh
|
|
62
|
-
* @en
|
|
80
|
+
* @zh 目标到达阈值
|
|
81
|
+
* @en Destination arrival threshold
|
|
63
82
|
*/
|
|
64
|
-
__publicField(this, "
|
|
83
|
+
__publicField(this, "arrivalThreshold", 0.3);
|
|
65
84
|
/**
|
|
66
|
-
* @zh
|
|
67
|
-
* @en
|
|
85
|
+
* @zh 路径重新计算间隔(秒)
|
|
86
|
+
* @en Path recalculation interval (seconds)
|
|
68
87
|
*/
|
|
69
|
-
__publicField(this, "
|
|
88
|
+
__publicField(this, "repathInterval", 0.5);
|
|
70
89
|
// =========================================================================
|
|
71
|
-
//
|
|
90
|
+
// 配置选项 | Configuration Options
|
|
72
91
|
// =========================================================================
|
|
73
92
|
/**
|
|
74
|
-
* @zh
|
|
75
|
-
* @en
|
|
93
|
+
* @zh 是否启用导航
|
|
94
|
+
* @en Whether navigation is enabled
|
|
76
95
|
*/
|
|
77
|
-
__publicField(this, "
|
|
96
|
+
__publicField(this, "enabled", true);
|
|
78
97
|
/**
|
|
79
|
-
* @zh
|
|
80
|
-
* @en
|
|
98
|
+
* @zh 是否自动重新计算被阻挡的路径
|
|
99
|
+
* @en Whether to auto repath when blocked
|
|
81
100
|
*/
|
|
82
|
-
__publicField(this, "
|
|
101
|
+
__publicField(this, "autoRepath", true);
|
|
83
102
|
/**
|
|
84
|
-
* @zh
|
|
85
|
-
* @en Whether
|
|
103
|
+
* @zh 是否启用平滑转向
|
|
104
|
+
* @en Whether to enable smooth steering
|
|
86
105
|
*/
|
|
87
|
-
__publicField(this, "
|
|
106
|
+
__publicField(this, "smoothSteering", true);
|
|
107
|
+
// =========================================================================
|
|
108
|
+
// 运行时状态 | Runtime State (Non-serialized)
|
|
109
|
+
// =========================================================================
|
|
88
110
|
/**
|
|
89
|
-
* @zh
|
|
90
|
-
* @en
|
|
111
|
+
* @zh 当前位置
|
|
112
|
+
* @en Current position
|
|
91
113
|
*/
|
|
92
|
-
__publicField(this, "
|
|
114
|
+
__publicField(this, "position", {
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0
|
|
117
|
+
});
|
|
93
118
|
/**
|
|
94
|
-
* @zh
|
|
95
|
-
* @en
|
|
119
|
+
* @zh 当前速度
|
|
120
|
+
* @en Current velocity
|
|
96
121
|
*/
|
|
97
|
-
__publicField(this, "
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
__publicField(this, "velocity", {
|
|
123
|
+
x: 0,
|
|
124
|
+
y: 0
|
|
125
|
+
});
|
|
101
126
|
/**
|
|
102
|
-
* @zh
|
|
103
|
-
* @en
|
|
127
|
+
* @zh 目标位置
|
|
128
|
+
* @en Destination position
|
|
104
129
|
*/
|
|
105
|
-
__publicField(this, "
|
|
130
|
+
__publicField(this, "destination", null);
|
|
106
131
|
/**
|
|
107
|
-
* @zh
|
|
108
|
-
* @en Current
|
|
132
|
+
* @zh 当前导航状态
|
|
133
|
+
* @en Current navigation state
|
|
109
134
|
*/
|
|
110
|
-
__publicField(this, "
|
|
135
|
+
__publicField(this, "state", "idle");
|
|
111
136
|
/**
|
|
112
|
-
* @zh
|
|
113
|
-
* @en Current path
|
|
137
|
+
* @zh 当前路径
|
|
138
|
+
* @en Current path
|
|
114
139
|
*/
|
|
115
140
|
__publicField(this, "path", []);
|
|
116
141
|
/**
|
|
117
|
-
* @zh
|
|
118
|
-
* @en Current
|
|
142
|
+
* @zh 当前路径点索引
|
|
143
|
+
* @en Current waypoint index
|
|
119
144
|
*/
|
|
120
|
-
__publicField(this, "
|
|
145
|
+
__publicField(this, "currentWaypointIndex", 0);
|
|
121
146
|
/**
|
|
122
|
-
* @zh
|
|
123
|
-
* @en
|
|
147
|
+
* @zh 上次重新计算路径的时间
|
|
148
|
+
* @en Last repath time
|
|
124
149
|
*/
|
|
125
|
-
__publicField(this, "
|
|
150
|
+
__publicField(this, "lastRepathTime", 0);
|
|
151
|
+
// =========================================================================
|
|
152
|
+
// 增量寻路状态(时间切片)| Incremental Pathfinding State (Time Slicing)
|
|
153
|
+
// =========================================================================
|
|
126
154
|
/**
|
|
127
|
-
* @zh
|
|
128
|
-
* @en
|
|
155
|
+
* @zh 当前增量寻路请求 ID
|
|
156
|
+
* @en Current incremental pathfinding request ID
|
|
129
157
|
*/
|
|
130
|
-
__publicField(this, "
|
|
158
|
+
__publicField(this, "currentRequestId", -1);
|
|
131
159
|
/**
|
|
132
|
-
* @zh
|
|
133
|
-
* @en
|
|
160
|
+
* @zh 寻路进度 (0-1)
|
|
161
|
+
* @en Pathfinding progress (0-1)
|
|
134
162
|
*/
|
|
135
|
-
__publicField(this, "
|
|
163
|
+
__publicField(this, "pathProgress", 0);
|
|
136
164
|
/**
|
|
137
|
-
* @zh
|
|
138
|
-
* @en
|
|
165
|
+
* @zh 优先级(数字越小优先级越高)
|
|
166
|
+
* @en Priority (lower number = higher priority)
|
|
139
167
|
*/
|
|
140
|
-
__publicField(this, "
|
|
168
|
+
__publicField(this, "priority", 50);
|
|
141
169
|
/**
|
|
142
|
-
* @zh
|
|
143
|
-
* @en
|
|
170
|
+
* @zh 是否正在等待路径计算完成
|
|
171
|
+
* @en Whether waiting for path computation to complete
|
|
144
172
|
*/
|
|
145
|
-
__publicField(this, "
|
|
173
|
+
__publicField(this, "isComputingPath", false);
|
|
146
174
|
}
|
|
147
175
|
// =========================================================================
|
|
148
176
|
// 公共方法 | Public Methods
|
|
149
177
|
// =========================================================================
|
|
150
178
|
/**
|
|
151
|
-
* @zh
|
|
152
|
-
* @en
|
|
179
|
+
* @zh 设置位置
|
|
180
|
+
* @en Set position
|
|
153
181
|
*
|
|
154
|
-
* @param
|
|
155
|
-
* @param
|
|
182
|
+
* @param x - @zh X 坐标 @en X coordinate
|
|
183
|
+
* @param y - @zh Y 坐标 @en Y coordinate
|
|
156
184
|
*/
|
|
157
|
-
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.progress = 0;
|
|
185
|
+
setPosition(x, y) {
|
|
186
|
+
this.position = {
|
|
187
|
+
x,
|
|
188
|
+
y
|
|
189
|
+
};
|
|
163
190
|
}
|
|
164
191
|
/**
|
|
165
|
-
* @zh
|
|
166
|
-
* @en
|
|
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
|
-
|
|
169
|
-
this.
|
|
170
|
-
|
|
198
|
+
setDestination(x, y) {
|
|
199
|
+
this.destination = {
|
|
200
|
+
x,
|
|
201
|
+
y
|
|
202
|
+
};
|
|
203
|
+
this.state = "navigating";
|
|
171
204
|
this.path = [];
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
174
|
-
this.currentRequestId = -1;
|
|
205
|
+
this.currentWaypointIndex = 0;
|
|
206
|
+
this.lastRepathTime = 0;
|
|
175
207
|
}
|
|
176
208
|
/**
|
|
177
|
-
* @zh
|
|
178
|
-
* @en
|
|
179
|
-
*
|
|
180
|
-
* @returns @zh 下一个路径点或 null @en Next waypoint or null
|
|
209
|
+
* @zh 停止导航
|
|
210
|
+
* @en Stop navigation
|
|
181
211
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
223
|
+
* @zh 获取当前路径点
|
|
224
|
+
* @en Get current waypoint
|
|
225
|
+
*
|
|
226
|
+
* @returns @zh 当前路径点,如果没有则返回 null @en Current waypoint, or null if none
|
|
191
227
|
*/
|
|
192
|
-
|
|
193
|
-
if (this.
|
|
194
|
-
this.
|
|
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
|
|
235
|
+
* @zh 获取到目标的距离
|
|
236
|
+
* @en Get distance to destination
|
|
200
237
|
*
|
|
201
|
-
* @returns @zh
|
|
238
|
+
* @returns @zh 到目标的距离,如果没有目标则返回 Infinity @en Distance to destination, or Infinity if no destination
|
|
202
239
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
247
|
+
* @zh 获取当前速度大小
|
|
248
|
+
* @en Get current speed
|
|
209
249
|
*
|
|
210
|
-
* @returns @zh
|
|
250
|
+
* @returns @zh 当前速度大小 @en Current speed magnitude
|
|
211
251
|
*/
|
|
212
|
-
|
|
213
|
-
return this.
|
|
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
|
|
256
|
+
* @zh 检查是否已到达目标
|
|
257
|
+
* @en Check if arrived at destination
|
|
218
258
|
*
|
|
219
|
-
* @returns @zh
|
|
259
|
+
* @returns @zh 是否已到达 @en Whether arrived
|
|
220
260
|
*/
|
|
221
|
-
|
|
222
|
-
return this.state ===
|
|
261
|
+
hasArrived() {
|
|
262
|
+
return this.state === "arrived";
|
|
223
263
|
}
|
|
224
264
|
/**
|
|
225
|
-
* @zh
|
|
226
|
-
* @en
|
|
265
|
+
* @zh 检查路径是否被阻挡
|
|
266
|
+
* @en Check if path is blocked
|
|
227
267
|
*
|
|
228
|
-
* @returns @zh
|
|
268
|
+
* @returns @zh 是否被阻挡 @en Whether blocked
|
|
229
269
|
*/
|
|
230
|
-
|
|
231
|
-
return
|
|
270
|
+
isBlocked() {
|
|
271
|
+
return this.state === "blocked";
|
|
232
272
|
}
|
|
233
273
|
/**
|
|
234
|
-
* @zh
|
|
235
|
-
* @en
|
|
274
|
+
* @zh 检查目标是否无法到达
|
|
275
|
+
* @en Check if destination is unreachable
|
|
236
276
|
*
|
|
237
|
-
* @returns @zh
|
|
277
|
+
* @returns @zh 是否无法到达 @en Whether unreachable
|
|
238
278
|
*/
|
|
239
|
-
|
|
240
|
-
return this.
|
|
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.
|
|
248
|
-
|
|
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.
|
|
251
|
-
this.
|
|
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(
|
|
267
|
-
var
|
|
309
|
+
__name(_NavigationAgentComponent, "NavigationAgentComponent");
|
|
310
|
+
var NavigationAgentComponent = _NavigationAgentComponent;
|
|
268
311
|
_ts_decorate([
|
|
269
312
|
Serialize(),
|
|
270
313
|
Property({
|
|
271
314
|
type: "number",
|
|
272
|
-
label: "
|
|
315
|
+
label: "Radius",
|
|
316
|
+
min: 0.1,
|
|
317
|
+
max: 10
|
|
273
318
|
}),
|
|
274
319
|
_ts_metadata("design:type", Number)
|
|
275
|
-
],
|
|
320
|
+
], NavigationAgentComponent.prototype, "radius", void 0);
|
|
276
321
|
_ts_decorate([
|
|
277
322
|
Serialize(),
|
|
278
323
|
Property({
|
|
279
324
|
type: "number",
|
|
280
|
-
label: "
|
|
325
|
+
label: "Max Speed",
|
|
326
|
+
min: 0.1,
|
|
327
|
+
max: 100
|
|
281
328
|
}),
|
|
282
329
|
_ts_metadata("design:type", Number)
|
|
283
|
-
],
|
|
330
|
+
], NavigationAgentComponent.prototype, "maxSpeed", void 0);
|
|
284
331
|
_ts_decorate([
|
|
285
332
|
Serialize(),
|
|
286
333
|
Property({
|
|
287
334
|
type: "number",
|
|
288
|
-
label: "
|
|
335
|
+
label: "Acceleration",
|
|
336
|
+
min: 0.1,
|
|
337
|
+
max: 100
|
|
289
338
|
}),
|
|
290
339
|
_ts_metadata("design:type", Number)
|
|
291
|
-
],
|
|
340
|
+
], NavigationAgentComponent.prototype, "acceleration", void 0);
|
|
292
341
|
_ts_decorate([
|
|
293
342
|
Serialize(),
|
|
294
343
|
Property({
|
|
295
344
|
type: "number",
|
|
296
|
-
label: "
|
|
345
|
+
label: "Waypoint Threshold",
|
|
346
|
+
min: 0.1,
|
|
347
|
+
max: 10
|
|
297
348
|
}),
|
|
298
349
|
_ts_metadata("design:type", Number)
|
|
299
|
-
],
|
|
350
|
+
], NavigationAgentComponent.prototype, "waypointThreshold", void 0);
|
|
300
351
|
_ts_decorate([
|
|
301
352
|
Serialize(),
|
|
302
353
|
Property({
|
|
303
354
|
type: "number",
|
|
304
|
-
label: "
|
|
305
|
-
min: 0,
|
|
306
|
-
max:
|
|
355
|
+
label: "Arrival Threshold",
|
|
356
|
+
min: 0.1,
|
|
357
|
+
max: 10
|
|
307
358
|
}),
|
|
308
359
|
_ts_metadata("design:type", Number)
|
|
309
|
-
],
|
|
360
|
+
], NavigationAgentComponent.prototype, "arrivalThreshold", void 0);
|
|
310
361
|
_ts_decorate([
|
|
311
362
|
Serialize(),
|
|
312
363
|
Property({
|
|
313
364
|
type: "number",
|
|
314
|
-
label: "
|
|
315
|
-
min:
|
|
316
|
-
max:
|
|
365
|
+
label: "Repath Interval",
|
|
366
|
+
min: 0.1,
|
|
367
|
+
max: 10
|
|
317
368
|
}),
|
|
318
369
|
_ts_metadata("design:type", Number)
|
|
319
|
-
],
|
|
370
|
+
], NavigationAgentComponent.prototype, "repathInterval", void 0);
|
|
320
371
|
_ts_decorate([
|
|
321
372
|
Serialize(),
|
|
322
373
|
Property({
|
|
323
374
|
type: "boolean",
|
|
324
|
-
label: "
|
|
375
|
+
label: "Enabled"
|
|
325
376
|
}),
|
|
326
377
|
_ts_metadata("design:type", Boolean)
|
|
327
|
-
],
|
|
378
|
+
], NavigationAgentComponent.prototype, "enabled", void 0);
|
|
328
379
|
_ts_decorate([
|
|
329
380
|
Serialize(),
|
|
330
381
|
Property({
|
|
331
|
-
type: "
|
|
332
|
-
label: "
|
|
333
|
-
min: 1,
|
|
334
|
-
max: 20
|
|
382
|
+
type: "boolean",
|
|
383
|
+
label: "Auto Repath"
|
|
335
384
|
}),
|
|
336
|
-
_ts_metadata("design:type",
|
|
337
|
-
],
|
|
385
|
+
_ts_metadata("design:type", Boolean)
|
|
386
|
+
], NavigationAgentComponent.prototype, "autoRepath", void 0);
|
|
338
387
|
_ts_decorate([
|
|
339
388
|
Serialize(),
|
|
340
389
|
Property({
|
|
341
|
-
type: "
|
|
342
|
-
label: "
|
|
343
|
-
min: 1,
|
|
344
|
-
max: 60
|
|
390
|
+
type: "boolean",
|
|
391
|
+
label: "Smooth Steering"
|
|
345
392
|
}),
|
|
346
|
-
_ts_metadata("design:type",
|
|
347
|
-
],
|
|
348
|
-
|
|
349
|
-
ECSComponent("
|
|
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: "
|
|
399
|
+
typeId: "NavigationAgent"
|
|
353
400
|
})
|
|
354
|
-
],
|
|
401
|
+
], NavigationAgentComponent);
|
|
355
402
|
|
|
356
|
-
// src/ecs/
|
|
357
|
-
import {
|
|
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
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
__publicField(this, "
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
* @
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
* @
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
__publicField(this, "
|
|
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
|
-
//
|
|
449
|
+
// 时间切片状态 | Time Slicing State
|
|
484
450
|
// =========================================================================
|
|
485
451
|
/**
|
|
486
|
-
* @zh
|
|
487
|
-
* @en
|
|
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, "
|
|
455
|
+
__publicField(this, "isIncrementalPlanner", false);
|
|
500
456
|
/**
|
|
501
|
-
* @zh
|
|
502
|
-
* @en
|
|
457
|
+
* @zh 等待寻路的代理队列(按优先级排序)
|
|
458
|
+
* @en Queue of agents waiting for pathfinding (sorted by priority)
|
|
503
459
|
*/
|
|
504
|
-
__publicField(this, "
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
//
|
|
467
|
+
// 算法设置 | Algorithm Setters
|
|
513
468
|
// =========================================================================
|
|
514
469
|
/**
|
|
515
|
-
* @zh
|
|
516
|
-
* @en Set
|
|
470
|
+
* @zh 设置路径规划器
|
|
471
|
+
* @en Set path planner
|
|
517
472
|
*
|
|
518
|
-
* @param
|
|
519
|
-
*
|
|
520
|
-
* @
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
|
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
|
-
|
|
541
|
-
|
|
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
|
|
500
|
+
* @zh 设置流量控制器
|
|
501
|
+
* @en Set flow controller
|
|
551
502
|
*
|
|
552
|
-
* @param
|
|
553
|
-
*
|
|
554
|
-
* @
|
|
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
|
-
|
|
557
|
-
|
|
511
|
+
setFlowController(controller) {
|
|
512
|
+
this.flowController?.dispose();
|
|
513
|
+
this.flowController = controller;
|
|
558
514
|
}
|
|
559
515
|
/**
|
|
560
|
-
* @zh
|
|
561
|
-
* @en
|
|
516
|
+
* @zh 获取当前流量控制器
|
|
517
|
+
* @en Get current flow controller
|
|
562
518
|
*/
|
|
563
|
-
|
|
564
|
-
this.
|
|
565
|
-
this.agentsProcessedThisFrame = 0;
|
|
519
|
+
getFlowController() {
|
|
520
|
+
return this.flowController;
|
|
566
521
|
}
|
|
567
522
|
/**
|
|
568
|
-
* @zh
|
|
569
|
-
* @en
|
|
523
|
+
* @zh 设置局部避让算法
|
|
524
|
+
* @en Set local avoidance algorithm
|
|
570
525
|
*
|
|
571
|
-
* @
|
|
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
|
-
|
|
574
|
-
|
|
534
|
+
setLocalAvoidance(avoidance) {
|
|
535
|
+
this.localAvoidance?.dispose();
|
|
536
|
+
this.localAvoidance = avoidance;
|
|
575
537
|
}
|
|
576
538
|
/**
|
|
577
|
-
* @zh
|
|
578
|
-
* @en Get
|
|
579
|
-
*
|
|
580
|
-
* @returns @zh 缓存统计 @en Cache statistics
|
|
539
|
+
* @zh 获取当前局部避让算法
|
|
540
|
+
* @en Get current local avoidance algorithm
|
|
581
541
|
*/
|
|
582
|
-
|
|
583
|
-
|
|
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
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
|
562
|
+
* @zh 获取当前碰撞解决器
|
|
563
|
+
* @en Get current collision resolver
|
|
808
564
|
*/
|
|
809
|
-
|
|
810
|
-
this.
|
|
811
|
-
this.initializeMap();
|
|
565
|
+
getCollisionResolver() {
|
|
566
|
+
return this.collisionResolver;
|
|
812
567
|
}
|
|
568
|
+
// =========================================================================
|
|
569
|
+
// 障碍物管理 | Obstacle Management
|
|
570
|
+
// =========================================================================
|
|
813
571
|
/**
|
|
814
|
-
* @zh
|
|
815
|
-
* @en
|
|
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
|
-
|
|
818
|
-
this.
|
|
582
|
+
addStaticObstacle(obstacle) {
|
|
583
|
+
this.staticObstacles.push(obstacle);
|
|
819
584
|
}
|
|
820
585
|
/**
|
|
821
|
-
* @zh
|
|
822
|
-
* @en
|
|
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
|
-
|
|
825
|
-
|
|
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
|
|
598
|
+
* @zh 移除所有静态障碍物
|
|
599
|
+
* @en Remove all static obstacles
|
|
843
600
|
*/
|
|
844
|
-
|
|
845
|
-
|
|
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
|
|
605
|
+
* @zh 移除所有动态障碍物
|
|
606
|
+
* @en Remove all dynamic obstacles
|
|
862
607
|
*/
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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 (
|
|
875
|
-
this.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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(
|
|
907
|
-
if (!agent) continue;
|
|
908
|
-
if (!
|
|
909
|
-
|
|
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.
|
|
912
|
-
|
|
913
|
-
|
|
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.
|
|
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
|
|
837
|
+
* @zh 处理等待中的代理
|
|
838
|
+
* @en Handle waiting agent
|
|
921
839
|
*/
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
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
|
|
866
|
+
* @zh 清理已移除代理的进入时间记录
|
|
867
|
+
* @en Cleanup enter times for removed agents
|
|
948
868
|
*/
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
|
878
|
+
* @zh 处理路径规划(同步模式)
|
|
879
|
+
* @en Process path planning (synchronous mode)
|
|
968
880
|
*/
|
|
969
|
-
|
|
970
|
-
agent.state
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (agent.
|
|
1013
|
-
|
|
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.
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
|
996
|
+
* @zh 推进路径点
|
|
997
|
+
* @en Advance waypoint
|
|
1186
998
|
*/
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
|
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
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
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
|
|
1217
|
-
* @en
|
|
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
|
-
|
|
1228
|
-
return
|
|
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
|
|
1071
|
+
* @zh 应用避让结果
|
|
1072
|
+
* @en Apply avoidance result
|
|
1233
1073
|
*/
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
|
1097
|
+
* @zh 应用平滑转向
|
|
1098
|
+
* @en Apply smooth steering
|
|
1240
1099
|
*/
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
|
1123
|
+
* @zh 检查是否到达目标
|
|
1124
|
+
* @en Check if arrived at destination
|
|
1252
1125
|
*/
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
|
1140
|
+
* @zh 解决代理间碰撞
|
|
1141
|
+
* @en Resolve agent-agent collisions
|
|
1266
1142
|
*/
|
|
1267
|
-
|
|
1268
|
-
this.
|
|
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(
|
|
1272
|
-
var
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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/
|
|
1358
|
-
import { Component as
|
|
1359
|
-
function
|
|
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(
|
|
1366
|
-
function
|
|
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(
|
|
1370
|
-
var
|
|
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
|
|
1401
|
-
* @en
|
|
1402
|
-
|
|
1403
|
-
|
|
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, "
|
|
1414
|
-
// =========================================================================
|
|
1415
|
-
// 统计信息 | Statistics
|
|
1416
|
-
// =========================================================================
|
|
1227
|
+
__publicField(this, "neighborDist", 15);
|
|
1417
1228
|
/**
|
|
1418
|
-
* @zh
|
|
1419
|
-
* @en
|
|
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, "
|
|
1235
|
+
__publicField(this, "maxNeighbors", 10);
|
|
1422
1236
|
/**
|
|
1423
|
-
* @zh
|
|
1424
|
-
* @en
|
|
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, "
|
|
1243
|
+
__publicField(this, "timeHorizon", 2);
|
|
1427
1244
|
/**
|
|
1428
|
-
* @zh
|
|
1429
|
-
* @en
|
|
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, "
|
|
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(
|
|
1514
|
-
var
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
1524
|
-
],
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
1534
|
-
],
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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: "
|
|
1300
|
+
typeId: "ORCAConfig"
|
|
1550
1301
|
})
|
|
1551
|
-
],
|
|
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
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
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
|