@esengine/pathfinding 12.1.2 → 13.1.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.
@@ -0,0 +1,935 @@
1
+ import {
2
+ __name,
3
+ __publicField
4
+ } from "./chunk-T626JPC7.js";
5
+
6
+ // src/avoidance/ILocalAvoidance.ts
7
+ var DEFAULT_ORCA_CONFIG = {
8
+ defaultTimeHorizon: 2,
9
+ defaultTimeHorizonObst: 1,
10
+ timeStep: 1 / 60,
11
+ epsilon: 1e-5
12
+ };
13
+ var DEFAULT_AGENT_PARAMS = {
14
+ radius: 0.5,
15
+ maxSpeed: 5,
16
+ neighborDist: 15,
17
+ maxNeighbors: 10,
18
+ timeHorizon: 2,
19
+ timeHorizonObst: 1
20
+ };
21
+
22
+ // src/avoidance/LinearProgram.ts
23
+ import { Vector2 } from "@esengine/ecs-framework-math";
24
+ var EPSILON = 1e-5;
25
+ var { dot, det, lengthSq } = Vector2;
26
+ function linearProgram1(lines, lineNo, radius, optVelocity, directionOpt, result) {
27
+ const line = lines[lineNo];
28
+ const dotProduct = dot(line.point, line.direction);
29
+ const discriminant = dotProduct * dotProduct + radius * radius - lengthSq(line.point);
30
+ if (discriminant < 0) {
31
+ return false;
32
+ }
33
+ const sqrtDiscriminant = Math.sqrt(discriminant);
34
+ let tLeft = -dotProduct - sqrtDiscriminant;
35
+ let tRight = -dotProduct + sqrtDiscriminant;
36
+ for (let i = 0; i < lineNo; i++) {
37
+ const constraint = lines[i];
38
+ const denominator = det(line.direction, constraint.direction);
39
+ const numerator = det(constraint.direction, {
40
+ x: line.point.x - constraint.point.x,
41
+ y: line.point.y - constraint.point.y
42
+ });
43
+ if (Math.abs(denominator) <= EPSILON) {
44
+ if (numerator < 0) {
45
+ return false;
46
+ }
47
+ continue;
48
+ }
49
+ const t2 = numerator / denominator;
50
+ if (denominator >= 0) {
51
+ tRight = Math.min(tRight, t2);
52
+ } else {
53
+ tLeft = Math.max(tLeft, t2);
54
+ }
55
+ if (tLeft > tRight) {
56
+ return false;
57
+ }
58
+ }
59
+ let t;
60
+ if (directionOpt) {
61
+ if (dot(optVelocity, line.direction) > 0) {
62
+ t = tRight;
63
+ } else {
64
+ t = tLeft;
65
+ }
66
+ } else {
67
+ t = dot(line.direction, {
68
+ x: optVelocity.x - line.point.x,
69
+ y: optVelocity.y - line.point.y
70
+ });
71
+ if (t < tLeft) {
72
+ t = tLeft;
73
+ } else if (t > tRight) {
74
+ t = tRight;
75
+ }
76
+ }
77
+ result.x = line.point.x + t * line.direction.x;
78
+ result.y = line.point.y + t * line.direction.y;
79
+ return true;
80
+ }
81
+ __name(linearProgram1, "linearProgram1");
82
+ function linearProgram2(lines, radius, optVelocity, directionOpt, result) {
83
+ if (directionOpt) {
84
+ result.x = optVelocity.x * radius;
85
+ result.y = optVelocity.y * radius;
86
+ } else if (lengthSq(optVelocity) > radius * radius) {
87
+ const len2 = Math.sqrt(lengthSq(optVelocity));
88
+ result.x = optVelocity.x / len2 * radius;
89
+ result.y = optVelocity.y / len2 * radius;
90
+ } else {
91
+ result.x = optVelocity.x;
92
+ result.y = optVelocity.y;
93
+ }
94
+ for (let i = 0; i < lines.length; i++) {
95
+ const line = lines[i];
96
+ const detVal = det(line.direction, {
97
+ x: line.point.x - result.x,
98
+ y: line.point.y - result.y
99
+ });
100
+ if (detVal > 0) {
101
+ const tempResult = result.clone();
102
+ if (!linearProgram1(lines, i, radius, optVelocity, directionOpt, result)) {
103
+ result.copy(tempResult);
104
+ return i;
105
+ }
106
+ }
107
+ }
108
+ return lines.length;
109
+ }
110
+ __name(linearProgram2, "linearProgram2");
111
+ function linearProgram3(lines, numObstLines, beginLine, radius, result) {
112
+ let distance = 0;
113
+ for (let i = beginLine; i < lines.length; i++) {
114
+ const line = lines[i];
115
+ if (det(line.direction, {
116
+ x: line.point.x - result.x,
117
+ y: line.point.y - result.y
118
+ }) > distance) {
119
+ const projLines = [];
120
+ for (let j = 0; j < numObstLines; j++) {
121
+ projLines.push(lines[j]);
122
+ }
123
+ for (let j = numObstLines; j < i; j++) {
124
+ const line1 = lines[j];
125
+ const line2 = lines[i];
126
+ let newLine;
127
+ const determinant = det(line1.direction, line2.direction);
128
+ if (Math.abs(determinant) <= EPSILON) {
129
+ if (dot(line1.direction, line2.direction) > 0) {
130
+ continue;
131
+ }
132
+ newLine = {
133
+ point: {
134
+ x: 0.5 * (line1.point.x + line2.point.x),
135
+ y: 0.5 * (line1.point.y + line2.point.y)
136
+ },
137
+ direction: {
138
+ x: 0,
139
+ y: 0
140
+ }
141
+ };
142
+ } else {
143
+ const diff = {
144
+ x: line1.point.x - line2.point.x,
145
+ y: line1.point.y - line2.point.y
146
+ };
147
+ const t = det(line2.direction, diff) / determinant;
148
+ newLine = {
149
+ point: {
150
+ x: line1.point.x + t * line1.direction.x,
151
+ y: line1.point.y + t * line1.direction.y
152
+ },
153
+ direction: {
154
+ x: 0,
155
+ y: 0
156
+ }
157
+ };
158
+ }
159
+ const dirDiff = {
160
+ x: line1.direction.x - line2.direction.x,
161
+ y: line1.direction.y - line2.direction.y
162
+ };
163
+ const dirLen = Math.sqrt(lengthSq(dirDiff));
164
+ if (dirLen > EPSILON) {
165
+ newLine.direction.x = dirDiff.x / dirLen;
166
+ newLine.direction.y = dirDiff.y / dirLen;
167
+ }
168
+ projLines.push(newLine);
169
+ }
170
+ const tempResult = result.clone();
171
+ const optVelocity = {
172
+ x: -lines[i].direction.y,
173
+ y: lines[i].direction.x
174
+ };
175
+ if (linearProgram2(projLines, radius, optVelocity, true, result) < projLines.length) {
176
+ result.copy(tempResult);
177
+ }
178
+ distance = det(lines[i].direction, {
179
+ x: lines[i].point.x - result.x,
180
+ y: lines[i].point.y - result.y
181
+ });
182
+ }
183
+ }
184
+ }
185
+ __name(linearProgram3, "linearProgram3");
186
+ function solveORCALinearProgram(lines, numObstLines, maxSpeed, preferredVelocity) {
187
+ const result = new Vector2();
188
+ const lineFail = linearProgram2(lines, maxSpeed, preferredVelocity, false, result);
189
+ if (lineFail < lines.length) {
190
+ linearProgram3(lines, numObstLines, lineFail, maxSpeed, result);
191
+ }
192
+ return result;
193
+ }
194
+ __name(solveORCALinearProgram, "solveORCALinearProgram");
195
+
196
+ // src/avoidance/ObstacleBuilder.ts
197
+ var EPSILON2 = 1e-5;
198
+ function leftOf(p1, p2, p3) {
199
+ return (p1.x - p3.x) * (p2.y - p1.y) - (p1.y - p3.y) * (p2.x - p1.x);
200
+ }
201
+ __name(leftOf, "leftOf");
202
+ function createObstacleVertices(vertices, startId = 0) {
203
+ const n = vertices.length;
204
+ if (n < 2) {
205
+ return [];
206
+ }
207
+ const obstacleVertices = [];
208
+ for (let i = 0; i < n; i++) {
209
+ obstacleVertices.push({
210
+ point: {
211
+ x: vertices[i].x,
212
+ y: vertices[i].y
213
+ },
214
+ direction: {
215
+ x: 0,
216
+ y: 0
217
+ },
218
+ next: null,
219
+ previous: null,
220
+ isConvex: false,
221
+ id: startId + i
222
+ });
223
+ }
224
+ for (let i = 0; i < n; i++) {
225
+ const curr = obstacleVertices[i];
226
+ const next = obstacleVertices[(i + 1) % n];
227
+ const prev = obstacleVertices[(i + n - 1) % n];
228
+ curr.next = next;
229
+ curr.previous = prev;
230
+ const dx = next.point.x - curr.point.x;
231
+ const dy = next.point.y - curr.point.y;
232
+ const edgeLen = Math.sqrt(dx * dx + dy * dy);
233
+ if (edgeLen > EPSILON2) {
234
+ curr.direction = {
235
+ x: dx / edgeLen,
236
+ y: dy / edgeLen
237
+ };
238
+ } else {
239
+ curr.direction = {
240
+ x: 1,
241
+ y: 0
242
+ };
243
+ }
244
+ }
245
+ for (let i = 0; i < n; i++) {
246
+ const curr = obstacleVertices[i];
247
+ const prev = curr.previous;
248
+ const next = curr.next;
249
+ curr.isConvex = leftOf(prev.point, curr.point, next.point) >= 0;
250
+ }
251
+ return obstacleVertices;
252
+ }
253
+ __name(createObstacleVertices, "createObstacleVertices");
254
+ function buildObstacleVertices(obstacles) {
255
+ const allVertices = [];
256
+ let nextId = 0;
257
+ for (const obstacle of obstacles) {
258
+ const vertices = createObstacleVertices(obstacle.vertices, nextId);
259
+ allVertices.push(...vertices);
260
+ nextId += vertices.length;
261
+ }
262
+ return allVertices;
263
+ }
264
+ __name(buildObstacleVertices, "buildObstacleVertices");
265
+ function ensureCCW(vertices, yAxisDown = false) {
266
+ if (vertices.length < 3) {
267
+ return vertices;
268
+ }
269
+ let signedArea = 0;
270
+ for (let i = 0; i < vertices.length; i++) {
271
+ const curr = vertices[i];
272
+ const next = vertices[(i + 1) % vertices.length];
273
+ signedArea += curr.x * next.y - next.x * curr.y;
274
+ }
275
+ signedArea *= 0.5;
276
+ const isCCW = yAxisDown ? signedArea < 0 : signedArea > 0;
277
+ if (isCCW) {
278
+ return vertices;
279
+ }
280
+ return [
281
+ ...vertices
282
+ ].reverse();
283
+ }
284
+ __name(ensureCCW, "ensureCCW");
285
+
286
+ // src/avoidance/ORCASolver.ts
287
+ import { Vector2 as Vector22 } from "@esengine/ecs-framework-math";
288
+ var EPSILON3 = 1e-5;
289
+ var { det: det2, dot: dot2, lengthSq: lengthSq2, len } = Vector22;
290
+ function normalize(v) {
291
+ const length = len(v);
292
+ if (length < EPSILON3) {
293
+ return {
294
+ x: 0,
295
+ y: 0
296
+ };
297
+ }
298
+ return {
299
+ x: v.x / length,
300
+ y: v.y / length
301
+ };
302
+ }
303
+ __name(normalize, "normalize");
304
+ var _ORCASolver = class _ORCASolver {
305
+ constructor(config = {}) {
306
+ __publicField(this, "config");
307
+ this.config = {
308
+ ...DEFAULT_ORCA_CONFIG,
309
+ ...config
310
+ };
311
+ }
312
+ /**
313
+ * @zh 计算代理的新速度
314
+ * @en Compute new velocity for agent
315
+ *
316
+ * @param agent - @zh 当前代理 @en Current agent
317
+ * @param neighbors - @zh 邻近代理列表 @en List of neighboring agents
318
+ * @param obstacles - @zh 障碍物列表 @en List of obstacles
319
+ * @param deltaTime - @zh 时间步长 @en Time step
320
+ * @returns @zh 计算得到的新速度 @en Computed new velocity
321
+ */
322
+ computeNewVelocity(agent, neighbors, obstacles, deltaTime) {
323
+ const orcaLines = [];
324
+ const obstacleVertices = buildObstacleVertices(obstacles);
325
+ const numObstLines = this.createObstacleORCALines(agent, obstacleVertices, orcaLines);
326
+ this.createAgentORCALines(agent, neighbors, deltaTime, orcaLines);
327
+ return solveORCALinearProgram(orcaLines, numObstLines, agent.maxSpeed, agent.preferredVelocity);
328
+ }
329
+ /**
330
+ * @zh 创建代理间的 ORCA 约束线
331
+ * @en Create ORCA constraint lines for agent-agent avoidance
332
+ */
333
+ createAgentORCALines(agent, neighbors, deltaTime, orcaLines) {
334
+ const invTimeHorizon = 1 / agent.timeHorizon;
335
+ for (const other of neighbors) {
336
+ if (other.id === agent.id) continue;
337
+ const relativePosition = {
338
+ x: other.position.x - agent.position.x,
339
+ y: other.position.y - agent.position.y
340
+ };
341
+ const relativeVelocity = {
342
+ x: agent.velocity.x - other.velocity.x,
343
+ y: agent.velocity.y - other.velocity.y
344
+ };
345
+ const distSq = lengthSq2(relativePosition);
346
+ const combinedRadius = agent.radius + other.radius;
347
+ const combinedRadiusSq = combinedRadius * combinedRadius;
348
+ const line = {
349
+ point: {
350
+ x: 0,
351
+ y: 0
352
+ },
353
+ direction: {
354
+ x: 0,
355
+ y: 0
356
+ }
357
+ };
358
+ let u;
359
+ if (distSq > combinedRadiusSq) {
360
+ const w = {
361
+ x: relativeVelocity.x - invTimeHorizon * relativePosition.x,
362
+ y: relativeVelocity.y - invTimeHorizon * relativePosition.y
363
+ };
364
+ const wLengthSq = lengthSq2(w);
365
+ const dotProduct1 = dot2(w, relativePosition);
366
+ if (dotProduct1 < 0 && dotProduct1 * dotProduct1 > combinedRadiusSq * wLengthSq) {
367
+ const wLength = Math.sqrt(wLengthSq);
368
+ const unitW = normalize(w);
369
+ line.direction = {
370
+ x: unitW.y,
371
+ y: -unitW.x
372
+ };
373
+ u = {
374
+ x: (combinedRadius * invTimeHorizon - wLength) * unitW.x,
375
+ y: (combinedRadius * invTimeHorizon - wLength) * unitW.y
376
+ };
377
+ } else {
378
+ const leg = Math.sqrt(distSq - combinedRadiusSq);
379
+ if (det2(relativePosition, w) > 0) {
380
+ line.direction = {
381
+ x: (relativePosition.x * leg - relativePosition.y * combinedRadius) / distSq,
382
+ y: (relativePosition.x * combinedRadius + relativePosition.y * leg) / distSq
383
+ };
384
+ } else {
385
+ line.direction = {
386
+ x: -(relativePosition.x * leg + relativePosition.y * combinedRadius) / distSq,
387
+ y: -(-relativePosition.x * combinedRadius + relativePosition.y * leg) / distSq
388
+ };
389
+ }
390
+ const dotProduct2 = dot2(relativeVelocity, line.direction);
391
+ u = {
392
+ x: dotProduct2 * line.direction.x - relativeVelocity.x,
393
+ y: dotProduct2 * line.direction.y - relativeVelocity.y
394
+ };
395
+ }
396
+ } else {
397
+ const invTimeStep = 1 / deltaTime;
398
+ const w = {
399
+ x: relativeVelocity.x - invTimeStep * relativePosition.x,
400
+ y: relativeVelocity.y - invTimeStep * relativePosition.y
401
+ };
402
+ const wLength = len(w);
403
+ const unitW = wLength > EPSILON3 ? {
404
+ x: w.x / wLength,
405
+ y: w.y / wLength
406
+ } : {
407
+ x: 1,
408
+ y: 0
409
+ };
410
+ line.direction = {
411
+ x: unitW.y,
412
+ y: -unitW.x
413
+ };
414
+ u = {
415
+ x: (combinedRadius * invTimeStep - wLength) * unitW.x,
416
+ y: (combinedRadius * invTimeStep - wLength) * unitW.y
417
+ };
418
+ }
419
+ line.point = {
420
+ x: agent.velocity.x + 0.5 * u.x,
421
+ y: agent.velocity.y + 0.5 * u.y
422
+ };
423
+ orcaLines.push(line);
424
+ }
425
+ }
426
+ /**
427
+ * @zh 创建障碍物的 ORCA 约束线
428
+ * @en Create ORCA constraint lines for obstacle avoidance
429
+ */
430
+ createObstacleORCALines(agent, obstacleVertices, orcaLines) {
431
+ const invTimeHorizonObst = 1 / agent.timeHorizonObst;
432
+ const radiusSq = agent.radius * agent.radius;
433
+ let numObstLines = 0;
434
+ for (const obstacle1 of obstacleVertices) {
435
+ const obstacle2 = obstacle1.next;
436
+ const relativePosition1 = {
437
+ x: obstacle1.point.x - agent.position.x,
438
+ y: obstacle1.point.y - agent.position.y
439
+ };
440
+ const relativePosition2 = {
441
+ x: obstacle2.point.x - agent.position.x,
442
+ y: obstacle2.point.y - agent.position.y
443
+ };
444
+ const obstacleVector = {
445
+ x: obstacle2.point.x - obstacle1.point.x,
446
+ y: obstacle2.point.y - obstacle1.point.y
447
+ };
448
+ const signedDistToEdge = det2(obstacleVector, relativePosition1);
449
+ if (signedDistToEdge < -EPSILON3) {
450
+ continue;
451
+ }
452
+ let alreadyCovered = false;
453
+ for (const existingLine of orcaLines) {
454
+ const scaledRelPos1 = {
455
+ x: invTimeHorizonObst * relativePosition1.x - existingLine.point.x,
456
+ y: invTimeHorizonObst * relativePosition1.y - existingLine.point.y
457
+ };
458
+ const scaledRelPos2 = {
459
+ x: invTimeHorizonObst * relativePosition2.x - existingLine.point.x,
460
+ y: invTimeHorizonObst * relativePosition2.y - existingLine.point.y
461
+ };
462
+ if (det2(scaledRelPos1, existingLine.direction) - invTimeHorizonObst * agent.radius >= -EPSILON3 && det2(scaledRelPos2, existingLine.direction) - invTimeHorizonObst * agent.radius >= -EPSILON3) {
463
+ alreadyCovered = true;
464
+ break;
465
+ }
466
+ }
467
+ if (alreadyCovered) {
468
+ continue;
469
+ }
470
+ const distSq1 = lengthSq2(relativePosition1);
471
+ const distSq2 = lengthSq2(relativePosition2);
472
+ const obstacleVectorSq = lengthSq2(obstacleVector);
473
+ const s = obstacleVectorSq > EPSILON3 ? -dot2(relativePosition1, obstacleVector) / obstacleVectorSq : 0;
474
+ const distSqLineToEdge = lengthSq2({
475
+ x: -relativePosition1.x - s * obstacleVector.x,
476
+ y: -relativePosition1.y - s * obstacleVector.y
477
+ });
478
+ const line = {
479
+ point: {
480
+ x: 0,
481
+ y: 0
482
+ },
483
+ direction: {
484
+ x: 0,
485
+ y: 0
486
+ }
487
+ };
488
+ if (s < 0 && distSq1 <= radiusSq) {
489
+ if (obstacle1.isConvex) {
490
+ line.point = {
491
+ x: 0,
492
+ y: 0
493
+ };
494
+ line.direction = normalize({
495
+ x: -relativePosition1.y,
496
+ y: relativePosition1.x
497
+ });
498
+ orcaLines.push(line);
499
+ numObstLines++;
500
+ }
501
+ continue;
502
+ }
503
+ if (s > 1 && distSq2 <= radiusSq) {
504
+ if (obstacle2.isConvex && det2(relativePosition2, obstacle2.direction) >= 0) {
505
+ line.point = {
506
+ x: 0,
507
+ y: 0
508
+ };
509
+ line.direction = normalize({
510
+ x: -relativePosition2.y,
511
+ y: relativePosition2.x
512
+ });
513
+ orcaLines.push(line);
514
+ numObstLines++;
515
+ }
516
+ continue;
517
+ }
518
+ if (s >= 0 && s <= 1 && distSqLineToEdge <= radiusSq) {
519
+ line.point = {
520
+ x: 0,
521
+ y: 0
522
+ };
523
+ line.direction = {
524
+ x: -obstacle1.direction.x,
525
+ y: -obstacle1.direction.y
526
+ };
527
+ orcaLines.push(line);
528
+ numObstLines++;
529
+ continue;
530
+ }
531
+ let obs1 = obstacle1;
532
+ let obs2 = obstacle2;
533
+ let leftLegDirection;
534
+ let rightLegDirection;
535
+ if (s < 0 && distSqLineToEdge <= radiusSq) {
536
+ if (!obstacle1.isConvex) continue;
537
+ obs2 = obstacle1;
538
+ const leg1 = Math.sqrt(Math.max(0, distSq1 - radiusSq));
539
+ leftLegDirection = {
540
+ x: (relativePosition1.x * leg1 - relativePosition1.y * agent.radius) / distSq1,
541
+ y: (relativePosition1.x * agent.radius + relativePosition1.y * leg1) / distSq1
542
+ };
543
+ rightLegDirection = {
544
+ x: (relativePosition1.x * leg1 + relativePosition1.y * agent.radius) / distSq1,
545
+ y: (-relativePosition1.x * agent.radius + relativePosition1.y * leg1) / distSq1
546
+ };
547
+ } else if (s > 1 && distSqLineToEdge <= radiusSq) {
548
+ if (!obstacle2.isConvex) continue;
549
+ obs1 = obstacle2;
550
+ const leg2 = Math.sqrt(Math.max(0, distSq2 - radiusSq));
551
+ leftLegDirection = {
552
+ x: (relativePosition2.x * leg2 - relativePosition2.y * agent.radius) / distSq2,
553
+ y: (relativePosition2.x * agent.radius + relativePosition2.y * leg2) / distSq2
554
+ };
555
+ rightLegDirection = {
556
+ x: (relativePosition2.x * leg2 + relativePosition2.y * agent.radius) / distSq2,
557
+ y: (-relativePosition2.x * agent.radius + relativePosition2.y * leg2) / distSq2
558
+ };
559
+ } else {
560
+ if (obstacle1.isConvex) {
561
+ const leg1 = Math.sqrt(Math.max(0, distSq1 - radiusSq));
562
+ leftLegDirection = {
563
+ x: (relativePosition1.x * leg1 - relativePosition1.y * agent.radius) / distSq1,
564
+ y: (relativePosition1.x * agent.radius + relativePosition1.y * leg1) / distSq1
565
+ };
566
+ } else {
567
+ leftLegDirection = {
568
+ x: -obstacle1.direction.x,
569
+ y: -obstacle1.direction.y
570
+ };
571
+ }
572
+ if (obstacle2.isConvex) {
573
+ const leg2 = Math.sqrt(Math.max(0, distSq2 - radiusSq));
574
+ rightLegDirection = {
575
+ x: (relativePosition2.x * leg2 + relativePosition2.y * agent.radius) / distSq2,
576
+ y: (-relativePosition2.x * agent.radius + relativePosition2.y * leg2) / distSq2
577
+ };
578
+ } else {
579
+ rightLegDirection = {
580
+ x: obstacle1.direction.x,
581
+ y: obstacle1.direction.y
582
+ };
583
+ }
584
+ }
585
+ const leftNeighbor = obs1.previous;
586
+ let isLeftLegForeign = false;
587
+ let isRightLegForeign = false;
588
+ if (obs1.isConvex) {
589
+ const negLeftNeighborDir = {
590
+ x: -leftNeighbor.direction.x,
591
+ y: -leftNeighbor.direction.y
592
+ };
593
+ if (det2(leftLegDirection, negLeftNeighborDir) >= 0) {
594
+ leftLegDirection = negLeftNeighborDir;
595
+ isLeftLegForeign = true;
596
+ }
597
+ }
598
+ if (obs2.isConvex) {
599
+ if (det2(rightLegDirection, obs2.direction) <= 0) {
600
+ rightLegDirection = {
601
+ x: obs2.direction.x,
602
+ y: obs2.direction.y
603
+ };
604
+ isRightLegForeign = true;
605
+ }
606
+ }
607
+ const leftCutoff = {
608
+ x: invTimeHorizonObst * (obs1.point.x - agent.position.x),
609
+ y: invTimeHorizonObst * (obs1.point.y - agent.position.y)
610
+ };
611
+ const rightCutoff = {
612
+ x: invTimeHorizonObst * (obs2.point.x - agent.position.x),
613
+ y: invTimeHorizonObst * (obs2.point.y - agent.position.y)
614
+ };
615
+ const cutoffVector = {
616
+ x: rightCutoff.x - leftCutoff.x,
617
+ y: rightCutoff.y - leftCutoff.y
618
+ };
619
+ const sameVertex = obs1 === obs2;
620
+ const cutoffVectorSq = lengthSq2(cutoffVector);
621
+ const t = sameVertex ? 0.5 : cutoffVectorSq > EPSILON3 ? dot2({
622
+ x: agent.velocity.x - leftCutoff.x,
623
+ y: agent.velocity.y - leftCutoff.y
624
+ }, cutoffVector) / cutoffVectorSq : 0.5;
625
+ const tLeft = dot2({
626
+ x: agent.velocity.x - leftCutoff.x,
627
+ y: agent.velocity.y - leftCutoff.y
628
+ }, leftLegDirection);
629
+ const tRight = dot2({
630
+ x: agent.velocity.x - rightCutoff.x,
631
+ y: agent.velocity.y - rightCutoff.y
632
+ }, rightLegDirection);
633
+ if (t < 0 && tLeft < 0 || sameVertex && tLeft < 0 && tRight < 0) {
634
+ const unitW = normalize({
635
+ x: agent.velocity.x - leftCutoff.x,
636
+ y: agent.velocity.y - leftCutoff.y
637
+ });
638
+ line.direction = {
639
+ x: unitW.y,
640
+ y: -unitW.x
641
+ };
642
+ line.point = {
643
+ x: leftCutoff.x + agent.radius * invTimeHorizonObst * unitW.x,
644
+ y: leftCutoff.y + agent.radius * invTimeHorizonObst * unitW.y
645
+ };
646
+ orcaLines.push(line);
647
+ numObstLines++;
648
+ continue;
649
+ }
650
+ if (t > 1 && tRight < 0) {
651
+ const unitW = normalize({
652
+ x: agent.velocity.x - rightCutoff.x,
653
+ y: agent.velocity.y - rightCutoff.y
654
+ });
655
+ line.direction = {
656
+ x: unitW.y,
657
+ y: -unitW.x
658
+ };
659
+ line.point = {
660
+ x: rightCutoff.x + agent.radius * invTimeHorizonObst * unitW.x,
661
+ y: rightCutoff.y + agent.radius * invTimeHorizonObst * unitW.y
662
+ };
663
+ orcaLines.push(line);
664
+ numObstLines++;
665
+ continue;
666
+ }
667
+ const distSqCutoff = t < 0 || t > 1 || sameVertex ? Infinity : lengthSq2({
668
+ x: agent.velocity.x - (leftCutoff.x + t * cutoffVector.x),
669
+ y: agent.velocity.y - (leftCutoff.y + t * cutoffVector.y)
670
+ });
671
+ const distSqLeft = tLeft < 0 ? Infinity : lengthSq2({
672
+ x: agent.velocity.x - (leftCutoff.x + tLeft * leftLegDirection.x),
673
+ y: agent.velocity.y - (leftCutoff.y + tLeft * leftLegDirection.y)
674
+ });
675
+ const distSqRight = tRight < 0 ? Infinity : lengthSq2({
676
+ x: agent.velocity.x - (rightCutoff.x + tRight * rightLegDirection.x),
677
+ y: agent.velocity.y - (rightCutoff.y + tRight * rightLegDirection.y)
678
+ });
679
+ if (distSqCutoff <= distSqLeft && distSqCutoff <= distSqRight) {
680
+ line.direction = {
681
+ x: -obs1.direction.x,
682
+ y: -obs1.direction.y
683
+ };
684
+ line.point = {
685
+ x: leftCutoff.x + agent.radius * invTimeHorizonObst * -line.direction.y,
686
+ y: leftCutoff.y + agent.radius * invTimeHorizonObst * line.direction.x
687
+ };
688
+ orcaLines.push(line);
689
+ numObstLines++;
690
+ continue;
691
+ }
692
+ if (distSqLeft <= distSqRight) {
693
+ if (isLeftLegForeign) {
694
+ continue;
695
+ }
696
+ line.direction = {
697
+ x: leftLegDirection.x,
698
+ y: leftLegDirection.y
699
+ };
700
+ line.point = {
701
+ x: leftCutoff.x + agent.radius * invTimeHorizonObst * -line.direction.y,
702
+ y: leftCutoff.y + agent.radius * invTimeHorizonObst * line.direction.x
703
+ };
704
+ orcaLines.push(line);
705
+ numObstLines++;
706
+ continue;
707
+ }
708
+ if (isRightLegForeign) {
709
+ continue;
710
+ }
711
+ line.direction = {
712
+ x: -rightLegDirection.x,
713
+ y: -rightLegDirection.y
714
+ };
715
+ line.point = {
716
+ x: rightCutoff.x + agent.radius * invTimeHorizonObst * -line.direction.y,
717
+ y: rightCutoff.y + agent.radius * invTimeHorizonObst * line.direction.x
718
+ };
719
+ orcaLines.push(line);
720
+ numObstLines++;
721
+ }
722
+ return numObstLines;
723
+ }
724
+ };
725
+ __name(_ORCASolver, "ORCASolver");
726
+ var ORCASolver = _ORCASolver;
727
+ function createORCASolver(config) {
728
+ return new ORCASolver(config);
729
+ }
730
+ __name(createORCASolver, "createORCASolver");
731
+
732
+ // src/avoidance/KDTree.ts
733
+ var _KDTree = class _KDTree {
734
+ constructor() {
735
+ __publicField(this, "agents", []);
736
+ __publicField(this, "agentIndices", []);
737
+ __publicField(this, "nodes", []);
738
+ /**
739
+ * @zh 最大叶节点大小
740
+ * @en Maximum leaf size
741
+ */
742
+ __publicField(this, "maxLeafSize", 10);
743
+ }
744
+ /**
745
+ * @zh 构建 KD-Tree
746
+ * @en Build KD-Tree
747
+ */
748
+ build(agents) {
749
+ this.agents = agents;
750
+ this.agentIndices = [];
751
+ this.nodes = [];
752
+ if (agents.length === 0) {
753
+ return;
754
+ }
755
+ for (let i = 0; i < agents.length; i++) {
756
+ this.agentIndices.push(i);
757
+ }
758
+ this.buildRecursive(0, agents.length, 0);
759
+ }
760
+ /**
761
+ * @zh 递归构建 KD-Tree
762
+ * @en Recursively build KD-Tree
763
+ */
764
+ buildRecursive(begin, end, depth) {
765
+ const nodeIndex = this.nodes.length;
766
+ const node = {
767
+ agentIndex: -1,
768
+ splitValue: 0,
769
+ left: -1,
770
+ right: -1,
771
+ begin,
772
+ end,
773
+ minX: Infinity,
774
+ minY: Infinity,
775
+ maxX: -Infinity,
776
+ maxY: -Infinity
777
+ };
778
+ this.nodes.push(node);
779
+ for (let i = begin; i < end; i++) {
780
+ const agent = this.agents[this.agentIndices[i]];
781
+ node.minX = Math.min(node.minX, agent.position.x);
782
+ node.minY = Math.min(node.minY, agent.position.y);
783
+ node.maxX = Math.max(node.maxX, agent.position.x);
784
+ node.maxY = Math.max(node.maxY, agent.position.y);
785
+ }
786
+ const count = end - begin;
787
+ if (count <= this.maxLeafSize) {
788
+ return nodeIndex;
789
+ }
790
+ const splitDim = depth % 2;
791
+ if (splitDim === 0) {
792
+ this.sortByX(begin, end);
793
+ } else {
794
+ this.sortByY(begin, end);
795
+ }
796
+ const mid = Math.floor((begin + end) / 2);
797
+ const midAgent = this.agents[this.agentIndices[mid]];
798
+ node.splitValue = splitDim === 0 ? midAgent.position.x : midAgent.position.y;
799
+ node.left = this.buildRecursive(begin, mid, depth + 1);
800
+ node.right = this.buildRecursive(mid, end, depth + 1);
801
+ return nodeIndex;
802
+ }
803
+ /**
804
+ * @zh 按 X 坐标排序
805
+ * @en Sort by X coordinate
806
+ */
807
+ sortByX(begin, end) {
808
+ const indices = this.agentIndices;
809
+ const agents = this.agents;
810
+ for (let i = begin + 1; i < end; i++) {
811
+ const key = indices[i];
812
+ const keyX = agents[key].position.x;
813
+ let j = i - 1;
814
+ while (j >= begin && agents[indices[j]].position.x > keyX) {
815
+ indices[j + 1] = indices[j];
816
+ j--;
817
+ }
818
+ indices[j + 1] = key;
819
+ }
820
+ }
821
+ /**
822
+ * @zh 按 Y 坐标排序
823
+ * @en Sort by Y coordinate
824
+ */
825
+ sortByY(begin, end) {
826
+ const indices = this.agentIndices;
827
+ const agents = this.agents;
828
+ for (let i = begin + 1; i < end; i++) {
829
+ const key = indices[i];
830
+ const keyY = agents[key].position.y;
831
+ let j = i - 1;
832
+ while (j >= begin && agents[indices[j]].position.y > keyY) {
833
+ indices[j + 1] = indices[j];
834
+ j--;
835
+ }
836
+ indices[j + 1] = key;
837
+ }
838
+ }
839
+ /**
840
+ * @zh 查询邻居
841
+ * @en Query neighbors
842
+ */
843
+ queryNeighbors(position, radius, maxResults, excludeId) {
844
+ const results = [];
845
+ const radiusSq = radius * radius;
846
+ if (this.nodes.length === 0) {
847
+ return results;
848
+ }
849
+ this.queryRecursive(0, position, radiusSq, maxResults, excludeId, results);
850
+ results.sort((a, b) => a.distanceSq - b.distanceSq);
851
+ if (results.length > maxResults) {
852
+ results.length = maxResults;
853
+ }
854
+ return results;
855
+ }
856
+ /**
857
+ * @zh 递归查询
858
+ * @en Recursive query
859
+ */
860
+ queryRecursive(nodeIndex, position, radiusSq, maxResults, excludeId, results) {
861
+ const node = this.nodes[nodeIndex];
862
+ if (!node) return;
863
+ const closestX = Math.max(node.minX, Math.min(position.x, node.maxX));
864
+ const closestY = Math.max(node.minY, Math.min(position.y, node.maxY));
865
+ const dx = position.x - closestX;
866
+ const dy = position.y - closestY;
867
+ const distSqToBBox = dx * dx + dy * dy;
868
+ if (distSqToBBox > radiusSq) {
869
+ return;
870
+ }
871
+ if (node.left === -1 && node.right === -1) {
872
+ for (let i = node.begin; i < node.end; i++) {
873
+ const agentIndex = this.agentIndices[i];
874
+ const agent = this.agents[agentIndex];
875
+ if (excludeId !== void 0 && agent.id === excludeId) {
876
+ continue;
877
+ }
878
+ const adx = position.x - agent.position.x;
879
+ const ady = position.y - agent.position.y;
880
+ const distSq = adx * adx + ady * ady;
881
+ if (distSq < radiusSq) {
882
+ results.push({
883
+ agent,
884
+ distanceSq: distSq
885
+ });
886
+ }
887
+ }
888
+ return;
889
+ }
890
+ if (node.left !== -1) {
891
+ this.queryRecursive(node.left, position, radiusSq, maxResults, excludeId, results);
892
+ }
893
+ if (node.right !== -1) {
894
+ this.queryRecursive(node.right, position, radiusSq, maxResults, excludeId, results);
895
+ }
896
+ }
897
+ /**
898
+ * @zh 清空索引
899
+ * @en Clear the index
900
+ */
901
+ clear() {
902
+ this.agents = [];
903
+ this.agentIndices = [];
904
+ this.nodes = [];
905
+ }
906
+ /**
907
+ * @zh 获取代理数量
908
+ * @en Get agent count
909
+ */
910
+ get agentCount() {
911
+ return this.agents.length;
912
+ }
913
+ };
914
+ __name(_KDTree, "KDTree");
915
+ var KDTree = _KDTree;
916
+ function createKDTree() {
917
+ return new KDTree();
918
+ }
919
+ __name(createKDTree, "createKDTree");
920
+
921
+ export {
922
+ DEFAULT_ORCA_CONFIG,
923
+ DEFAULT_AGENT_PARAMS,
924
+ linearProgram2,
925
+ linearProgram3,
926
+ solveORCALinearProgram,
927
+ createObstacleVertices,
928
+ buildObstacleVertices,
929
+ ensureCCW,
930
+ ORCASolver,
931
+ createORCASolver,
932
+ KDTree,
933
+ createKDTree
934
+ };
935
+ //# sourceMappingURL=chunk-JTZP55BJ.js.map