@esengine/spatial 1.0.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/index.js ADDED
@@ -0,0 +1,1607 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // src/ISpatialQuery.ts
7
+ function createBounds(minX, minY, maxX, maxY) {
8
+ return {
9
+ minX,
10
+ minY,
11
+ maxX,
12
+ maxY
13
+ };
14
+ }
15
+ __name(createBounds, "createBounds");
16
+ function createBoundsFromCenter(center, width, height) {
17
+ const halfWidth = width / 2;
18
+ const halfHeight = height / 2;
19
+ return {
20
+ minX: center.x - halfWidth,
21
+ minY: center.y - halfHeight,
22
+ maxX: center.x + halfWidth,
23
+ maxY: center.y + halfHeight
24
+ };
25
+ }
26
+ __name(createBoundsFromCenter, "createBoundsFromCenter");
27
+ function createBoundsFromCircle(center, radius) {
28
+ return {
29
+ minX: center.x - radius,
30
+ minY: center.y - radius,
31
+ maxX: center.x + radius,
32
+ maxY: center.y + radius
33
+ };
34
+ }
35
+ __name(createBoundsFromCircle, "createBoundsFromCircle");
36
+ function isPointInBounds(point, bounds) {
37
+ return point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
38
+ }
39
+ __name(isPointInBounds, "isPointInBounds");
40
+ function boundsIntersect(a, b) {
41
+ return a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
42
+ }
43
+ __name(boundsIntersect, "boundsIntersect");
44
+ function boundsIntersectsCircle(bounds, center, radius) {
45
+ const closestX = Math.max(bounds.minX, Math.min(center.x, bounds.maxX));
46
+ const closestY = Math.max(bounds.minY, Math.min(center.y, bounds.maxY));
47
+ const distanceX = center.x - closestX;
48
+ const distanceY = center.y - closestY;
49
+ return distanceX * distanceX + distanceY * distanceY <= radius * radius;
50
+ }
51
+ __name(boundsIntersectsCircle, "boundsIntersectsCircle");
52
+ function distanceSquared(a, b) {
53
+ const dx = a.x - b.x;
54
+ const dy = a.y - b.y;
55
+ return dx * dx + dy * dy;
56
+ }
57
+ __name(distanceSquared, "distanceSquared");
58
+ function distance(a, b) {
59
+ return Math.sqrt(distanceSquared(a, b));
60
+ }
61
+ __name(distance, "distance");
62
+
63
+ // src/GridSpatialIndex.ts
64
+ var _GridSpatialIndex = class _GridSpatialIndex {
65
+ constructor(config) {
66
+ __publicField(this, "_cellSize");
67
+ __publicField(this, "_cells", /* @__PURE__ */ new Map());
68
+ __publicField(this, "_itemMap", /* @__PURE__ */ new Map());
69
+ this._cellSize = config.cellSize;
70
+ }
71
+ // =========================================================================
72
+ // ISpatialIndex 实现 | ISpatialIndex Implementation
73
+ // =========================================================================
74
+ get count() {
75
+ return this._itemMap.size;
76
+ }
77
+ /**
78
+ * @zh 插入对象
79
+ * @en Insert object
80
+ */
81
+ insert(item, position) {
82
+ if (this._itemMap.has(item)) {
83
+ this.update(item, position);
84
+ return;
85
+ }
86
+ const cellKey = this._getCellKey(position);
87
+ const gridItem = {
88
+ item,
89
+ position: {
90
+ x: position.x,
91
+ y: position.y
92
+ },
93
+ cellKey
94
+ };
95
+ this._itemMap.set(item, gridItem);
96
+ this._addToCell(cellKey, gridItem);
97
+ }
98
+ /**
99
+ * @zh 移除对象
100
+ * @en Remove object
101
+ */
102
+ remove(item) {
103
+ const gridItem = this._itemMap.get(item);
104
+ if (!gridItem) {
105
+ return false;
106
+ }
107
+ this._removeFromCell(gridItem.cellKey, gridItem);
108
+ this._itemMap.delete(item);
109
+ return true;
110
+ }
111
+ /**
112
+ * @zh 更新对象位置
113
+ * @en Update object position
114
+ */
115
+ update(item, newPosition) {
116
+ const gridItem = this._itemMap.get(item);
117
+ if (!gridItem) {
118
+ return false;
119
+ }
120
+ const newCellKey = this._getCellKey(newPosition);
121
+ if (newCellKey !== gridItem.cellKey) {
122
+ this._removeFromCell(gridItem.cellKey, gridItem);
123
+ gridItem.cellKey = newCellKey;
124
+ this._addToCell(newCellKey, gridItem);
125
+ }
126
+ gridItem.position = {
127
+ x: newPosition.x,
128
+ y: newPosition.y
129
+ };
130
+ return true;
131
+ }
132
+ /**
133
+ * @zh 清空索引
134
+ * @en Clear index
135
+ */
136
+ clear() {
137
+ this._cells.clear();
138
+ this._itemMap.clear();
139
+ }
140
+ /**
141
+ * @zh 获取所有对象
142
+ * @en Get all objects
143
+ */
144
+ getAll() {
145
+ return Array.from(this._itemMap.keys());
146
+ }
147
+ // =========================================================================
148
+ // ISpatialQuery 实现 | ISpatialQuery Implementation
149
+ // =========================================================================
150
+ /**
151
+ * @zh 查找半径内的所有对象
152
+ * @en Find all objects within radius
153
+ */
154
+ findInRadius(center, radius, filter) {
155
+ const results = [];
156
+ const radiusSq = radius * radius;
157
+ const bounds = createBoundsFromCircle(center, radius);
158
+ this._forEachInBounds(bounds, (gridItem) => {
159
+ const distSq = distanceSquared(center, gridItem.position);
160
+ if (distSq <= radiusSq) {
161
+ if (!filter || filter(gridItem.item)) {
162
+ results.push(gridItem.item);
163
+ }
164
+ }
165
+ });
166
+ return results;
167
+ }
168
+ /**
169
+ * @zh 查找矩形区域内的所有对象
170
+ * @en Find all objects within rectangle
171
+ */
172
+ findInRect(bounds, filter) {
173
+ const results = [];
174
+ this._forEachInBounds(bounds, (gridItem) => {
175
+ const pos = gridItem.position;
176
+ if (pos.x >= bounds.minX && pos.x <= bounds.maxX && pos.y >= bounds.minY && pos.y <= bounds.maxY) {
177
+ if (!filter || filter(gridItem.item)) {
178
+ results.push(gridItem.item);
179
+ }
180
+ }
181
+ });
182
+ return results;
183
+ }
184
+ /**
185
+ * @zh 查找最近的对象
186
+ * @en Find nearest object
187
+ */
188
+ findNearest(center, maxDistance, filter) {
189
+ let nearest = null;
190
+ let nearestDistSq = maxDistance !== void 0 ? maxDistance * maxDistance : Infinity;
191
+ const searchRadius = maxDistance ?? this._cellSize * 10;
192
+ const bounds = createBoundsFromCircle(center, searchRadius);
193
+ this._forEachInBounds(bounds, (gridItem) => {
194
+ const distSq = distanceSquared(center, gridItem.position);
195
+ if (distSq < nearestDistSq) {
196
+ if (!filter || filter(gridItem.item)) {
197
+ nearest = gridItem.item;
198
+ nearestDistSq = distSq;
199
+ }
200
+ }
201
+ });
202
+ return nearest;
203
+ }
204
+ /**
205
+ * @zh 查找最近的 K 个对象
206
+ * @en Find K nearest objects
207
+ */
208
+ findKNearest(center, k, maxDistance, filter) {
209
+ if (k <= 0) return [];
210
+ const candidates = [];
211
+ const maxDistSq = maxDistance !== void 0 ? maxDistance * maxDistance : Infinity;
212
+ const searchRadius = maxDistance ?? this._cellSize * 10;
213
+ const bounds = createBoundsFromCircle(center, searchRadius);
214
+ this._forEachInBounds(bounds, (gridItem) => {
215
+ const distSq = distanceSquared(center, gridItem.position);
216
+ if (distSq <= maxDistSq) {
217
+ if (!filter || filter(gridItem.item)) {
218
+ candidates.push({
219
+ item: gridItem.item,
220
+ distSq
221
+ });
222
+ }
223
+ }
224
+ });
225
+ candidates.sort((a, b) => a.distSq - b.distSq);
226
+ return candidates.slice(0, k).map((c) => c.item);
227
+ }
228
+ /**
229
+ * @zh 射线检测
230
+ * @en Raycast
231
+ */
232
+ raycast(origin, direction, maxDistance, filter) {
233
+ const hits = [];
234
+ const rayBounds = this._getRayBounds(origin, direction, maxDistance);
235
+ this._forEachInBounds(rayBounds, (gridItem) => {
236
+ const hit = this._rayIntersectsPoint(origin, direction, gridItem.position, maxDistance);
237
+ if (hit && hit.distance <= maxDistance) {
238
+ if (!filter || filter(gridItem.item)) {
239
+ hits.push({
240
+ target: gridItem.item,
241
+ point: hit.point,
242
+ normal: hit.normal,
243
+ distance: hit.distance
244
+ });
245
+ }
246
+ }
247
+ });
248
+ hits.sort((a, b) => a.distance - b.distance);
249
+ return hits;
250
+ }
251
+ /**
252
+ * @zh 射线检测(仅返回第一个命中)
253
+ * @en Raycast (return first hit only)
254
+ */
255
+ raycastFirst(origin, direction, maxDistance, filter) {
256
+ const hits = this.raycast(origin, direction, maxDistance, filter);
257
+ return hits.length > 0 ? hits[0] : null;
258
+ }
259
+ // =========================================================================
260
+ // 私有方法 | Private Methods
261
+ // =========================================================================
262
+ _getCellKey(position) {
263
+ const cellX = Math.floor(position.x / this._cellSize);
264
+ const cellY = Math.floor(position.y / this._cellSize);
265
+ return `${cellX},${cellY}`;
266
+ }
267
+ _getCellCoords(position) {
268
+ return {
269
+ x: Math.floor(position.x / this._cellSize),
270
+ y: Math.floor(position.y / this._cellSize)
271
+ };
272
+ }
273
+ _addToCell(cellKey, gridItem) {
274
+ let cell = this._cells.get(cellKey);
275
+ if (!cell) {
276
+ cell = /* @__PURE__ */ new Set();
277
+ this._cells.set(cellKey, cell);
278
+ }
279
+ cell.add(gridItem);
280
+ }
281
+ _removeFromCell(cellKey, gridItem) {
282
+ const cell = this._cells.get(cellKey);
283
+ if (cell) {
284
+ cell.delete(gridItem);
285
+ if (cell.size === 0) {
286
+ this._cells.delete(cellKey);
287
+ }
288
+ }
289
+ }
290
+ _forEachInBounds(bounds, callback) {
291
+ const minCell = this._getCellCoords({
292
+ x: bounds.minX,
293
+ y: bounds.minY
294
+ });
295
+ const maxCell = this._getCellCoords({
296
+ x: bounds.maxX,
297
+ y: bounds.maxY
298
+ });
299
+ for (let x = minCell.x; x <= maxCell.x; x++) {
300
+ for (let y = minCell.y; y <= maxCell.y; y++) {
301
+ const cell = this._cells.get(`${x},${y}`);
302
+ if (cell) {
303
+ for (const gridItem of cell) {
304
+ callback(gridItem);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ _getRayBounds(origin, direction, maxDistance) {
311
+ const endX = origin.x + direction.x * maxDistance;
312
+ const endY = origin.y + direction.y * maxDistance;
313
+ return {
314
+ minX: Math.min(origin.x, endX),
315
+ minY: Math.min(origin.y, endY),
316
+ maxX: Math.max(origin.x, endX),
317
+ maxY: Math.max(origin.y, endY)
318
+ };
319
+ }
320
+ _rayIntersectsPoint(origin, direction, point, _maxDistance, hitRadius = 0.5) {
321
+ const toPoint = {
322
+ x: point.x - origin.x,
323
+ y: point.y - origin.y
324
+ };
325
+ const projection = toPoint.x * direction.x + toPoint.y * direction.y;
326
+ if (projection < 0) {
327
+ return null;
328
+ }
329
+ const closestX = origin.x + direction.x * projection;
330
+ const closestY = origin.y + direction.y * projection;
331
+ const distToLine = Math.sqrt((point.x - closestX) * (point.x - closestX) + (point.y - closestY) * (point.y - closestY));
332
+ if (distToLine <= hitRadius) {
333
+ const hitDist = projection - Math.sqrt(hitRadius * hitRadius - distToLine * distToLine);
334
+ if (hitDist >= 0) {
335
+ const hitPoint = {
336
+ x: origin.x + direction.x * hitDist,
337
+ y: origin.y + direction.y * hitDist
338
+ };
339
+ const normal = {
340
+ x: hitPoint.x - point.x,
341
+ y: hitPoint.y - point.y
342
+ };
343
+ const normalLen = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
344
+ if (normalLen > 0) {
345
+ normal.x /= normalLen;
346
+ normal.y /= normalLen;
347
+ }
348
+ return {
349
+ point: hitPoint,
350
+ normal,
351
+ distance: hitDist
352
+ };
353
+ }
354
+ }
355
+ return null;
356
+ }
357
+ };
358
+ __name(_GridSpatialIndex, "GridSpatialIndex");
359
+ var GridSpatialIndex = _GridSpatialIndex;
360
+ function createGridSpatialIndex(cellSize = 100) {
361
+ return new GridSpatialIndex({
362
+ cellSize
363
+ });
364
+ }
365
+ __name(createGridSpatialIndex, "createGridSpatialIndex");
366
+
367
+ // src/aoi/GridAOI.ts
368
+ var _GridAOI = class _GridAOI {
369
+ constructor(config) {
370
+ __publicField(this, "_cellSize");
371
+ __publicField(this, "_cells", /* @__PURE__ */ new Map());
372
+ __publicField(this, "_observers", /* @__PURE__ */ new Map());
373
+ __publicField(this, "_globalListeners", /* @__PURE__ */ new Set());
374
+ this._cellSize = config.cellSize;
375
+ }
376
+ // =========================================================================
377
+ // IAOIManager 实现 | IAOIManager Implementation
378
+ // =========================================================================
379
+ get count() {
380
+ return this._observers.size;
381
+ }
382
+ /**
383
+ * @zh 添加观察者
384
+ * @en Add observer
385
+ */
386
+ addObserver(entity, position, config) {
387
+ if (this._observers.has(entity)) {
388
+ this.updatePosition(entity, position);
389
+ this.updateViewRange(entity, config.viewRange);
390
+ return;
391
+ }
392
+ const cellKey = this._getCellKey(position);
393
+ const data = {
394
+ entity,
395
+ position: {
396
+ x: position.x,
397
+ y: position.y
398
+ },
399
+ viewRange: config.viewRange,
400
+ viewRangeSq: config.viewRange * config.viewRange,
401
+ observable: config.observable !== false,
402
+ cellKey,
403
+ visibleEntities: /* @__PURE__ */ new Set(),
404
+ listeners: /* @__PURE__ */ new Set()
405
+ };
406
+ this._observers.set(entity, data);
407
+ this._addToCell(cellKey, data);
408
+ this._updateVisibility(data);
409
+ }
410
+ /**
411
+ * @zh 移除观察者
412
+ * @en Remove observer
413
+ */
414
+ removeObserver(entity) {
415
+ const data = this._observers.get(entity);
416
+ if (!data) {
417
+ return false;
418
+ }
419
+ if (data.observable) {
420
+ for (const [, otherData] of this._observers) {
421
+ if (otherData !== data && otherData.visibleEntities.has(entity)) {
422
+ otherData.visibleEntities.delete(entity);
423
+ this._emitEvent({
424
+ type: "exit",
425
+ observer: otherData.entity,
426
+ target: entity,
427
+ position: data.position
428
+ }, otherData);
429
+ }
430
+ }
431
+ }
432
+ for (const visible of data.visibleEntities) {
433
+ const visibleData = this._observers.get(visible);
434
+ if (visibleData) {
435
+ this._emitEvent({
436
+ type: "exit",
437
+ observer: entity,
438
+ target: visible,
439
+ position: visibleData.position
440
+ }, data);
441
+ }
442
+ }
443
+ this._removeFromCell(data.cellKey, data);
444
+ this._observers.delete(entity);
445
+ return true;
446
+ }
447
+ /**
448
+ * @zh 更新观察者位置
449
+ * @en Update observer position
450
+ */
451
+ updatePosition(entity, newPosition) {
452
+ const data = this._observers.get(entity);
453
+ if (!data) {
454
+ return false;
455
+ }
456
+ const newCellKey = this._getCellKey(newPosition);
457
+ if (newCellKey !== data.cellKey) {
458
+ this._removeFromCell(data.cellKey, data);
459
+ data.cellKey = newCellKey;
460
+ this._addToCell(newCellKey, data);
461
+ }
462
+ data.position = {
463
+ x: newPosition.x,
464
+ y: newPosition.y
465
+ };
466
+ this._updateVisibility(data);
467
+ if (data.observable) {
468
+ this._updateObserversOfEntity(data);
469
+ }
470
+ return true;
471
+ }
472
+ /**
473
+ * @zh 更新观察者视野范围
474
+ * @en Update observer view range
475
+ */
476
+ updateViewRange(entity, newRange) {
477
+ const data = this._observers.get(entity);
478
+ if (!data) {
479
+ return false;
480
+ }
481
+ data.viewRange = newRange;
482
+ data.viewRangeSq = newRange * newRange;
483
+ this._updateVisibility(data);
484
+ return true;
485
+ }
486
+ /**
487
+ * @zh 获取实体视野内的所有对象
488
+ * @en Get all objects within entity's view
489
+ */
490
+ getEntitiesInView(entity) {
491
+ const data = this._observers.get(entity);
492
+ if (!data) {
493
+ return [];
494
+ }
495
+ return Array.from(data.visibleEntities);
496
+ }
497
+ /**
498
+ * @zh 获取能看到指定实体的所有观察者
499
+ * @en Get all observers who can see the specified entity
500
+ */
501
+ getObserversOf(entity) {
502
+ const data = this._observers.get(entity);
503
+ if (!data || !data.observable) {
504
+ return [];
505
+ }
506
+ const observers = [];
507
+ for (const [, otherData] of this._observers) {
508
+ if (otherData !== data && otherData.visibleEntities.has(entity)) {
509
+ observers.push(otherData.entity);
510
+ }
511
+ }
512
+ return observers;
513
+ }
514
+ /**
515
+ * @zh 检查观察者是否能看到目标
516
+ * @en Check if observer can see target
517
+ */
518
+ canSee(observer, target) {
519
+ const data = this._observers.get(observer);
520
+ if (!data) {
521
+ return false;
522
+ }
523
+ return data.visibleEntities.has(target);
524
+ }
525
+ /**
526
+ * @zh 添加全局事件监听器
527
+ * @en Add global event listener
528
+ */
529
+ addListener(listener) {
530
+ this._globalListeners.add(listener);
531
+ }
532
+ /**
533
+ * @zh 移除全局事件监听器
534
+ * @en Remove global event listener
535
+ */
536
+ removeListener(listener) {
537
+ this._globalListeners.delete(listener);
538
+ }
539
+ /**
540
+ * @zh 为特定观察者添加事件监听器
541
+ * @en Add event listener for specific observer
542
+ */
543
+ addEntityListener(entity, listener) {
544
+ const data = this._observers.get(entity);
545
+ if (data) {
546
+ data.listeners.add(listener);
547
+ }
548
+ }
549
+ /**
550
+ * @zh 移除特定观察者的事件监听器
551
+ * @en Remove event listener for specific observer
552
+ */
553
+ removeEntityListener(entity, listener) {
554
+ const data = this._observers.get(entity);
555
+ if (data) {
556
+ data.listeners.delete(listener);
557
+ }
558
+ }
559
+ /**
560
+ * @zh 清空所有观察者
561
+ * @en Clear all observers
562
+ */
563
+ clear() {
564
+ this._cells.clear();
565
+ this._observers.clear();
566
+ }
567
+ // =========================================================================
568
+ // 私有方法 | Private Methods
569
+ // =========================================================================
570
+ _getCellKey(position) {
571
+ const cellX = Math.floor(position.x / this._cellSize);
572
+ const cellY = Math.floor(position.y / this._cellSize);
573
+ return `${cellX},${cellY}`;
574
+ }
575
+ _getCellCoords(position) {
576
+ return {
577
+ x: Math.floor(position.x / this._cellSize),
578
+ y: Math.floor(position.y / this._cellSize)
579
+ };
580
+ }
581
+ _addToCell(cellKey, data) {
582
+ let cell = this._cells.get(cellKey);
583
+ if (!cell) {
584
+ cell = /* @__PURE__ */ new Set();
585
+ this._cells.set(cellKey, cell);
586
+ }
587
+ cell.add(data);
588
+ }
589
+ _removeFromCell(cellKey, data) {
590
+ const cell = this._cells.get(cellKey);
591
+ if (cell) {
592
+ cell.delete(data);
593
+ if (cell.size === 0) {
594
+ this._cells.delete(cellKey);
595
+ }
596
+ }
597
+ }
598
+ /**
599
+ * @zh 更新观察者的可见实体列表
600
+ * @en Update observer's visible entities list
601
+ */
602
+ _updateVisibility(data) {
603
+ const newVisible = /* @__PURE__ */ new Set();
604
+ const cellRadius = Math.ceil(data.viewRange / this._cellSize);
605
+ const centerCell = this._getCellCoords(data.position);
606
+ for (let dx = -cellRadius; dx <= cellRadius; dx++) {
607
+ for (let dy = -cellRadius; dy <= cellRadius; dy++) {
608
+ const cellKey = `${centerCell.x + dx},${centerCell.y + dy}`;
609
+ const cell = this._cells.get(cellKey);
610
+ if (!cell) continue;
611
+ for (const otherData of cell) {
612
+ if (otherData === data) continue;
613
+ if (!otherData.observable) continue;
614
+ const distSq = distanceSquared(data.position, otherData.position);
615
+ if (distSq <= data.viewRangeSq) {
616
+ newVisible.add(otherData.entity);
617
+ }
618
+ }
619
+ }
620
+ }
621
+ for (const entity of newVisible) {
622
+ if (!data.visibleEntities.has(entity)) {
623
+ const targetData = this._observers.get(entity);
624
+ if (targetData) {
625
+ this._emitEvent({
626
+ type: "enter",
627
+ observer: data.entity,
628
+ target: entity,
629
+ position: targetData.position
630
+ }, data);
631
+ }
632
+ }
633
+ }
634
+ for (const entity of data.visibleEntities) {
635
+ if (!newVisible.has(entity)) {
636
+ const targetData = this._observers.get(entity);
637
+ const position = targetData?.position ?? {
638
+ x: 0,
639
+ y: 0
640
+ };
641
+ this._emitEvent({
642
+ type: "exit",
643
+ observer: data.entity,
644
+ target: entity,
645
+ position
646
+ }, data);
647
+ }
648
+ }
649
+ data.visibleEntities = newVisible;
650
+ }
651
+ /**
652
+ * @zh 更新其他观察者对于某个实体的可见性
653
+ * @en Update other observers' visibility of an entity
654
+ */
655
+ _updateObserversOfEntity(movedData) {
656
+ const cellRadius = Math.ceil(this._getMaxViewRange() / this._cellSize) + 1;
657
+ const centerCell = this._getCellCoords(movedData.position);
658
+ for (let dx = -cellRadius; dx <= cellRadius; dx++) {
659
+ for (let dy = -cellRadius; dy <= cellRadius; dy++) {
660
+ const cellKey = `${centerCell.x + dx},${centerCell.y + dy}`;
661
+ const cell = this._cells.get(cellKey);
662
+ if (!cell) continue;
663
+ for (const otherData of cell) {
664
+ if (otherData === movedData) continue;
665
+ const distSq = distanceSquared(otherData.position, movedData.position);
666
+ const wasVisible = otherData.visibleEntities.has(movedData.entity);
667
+ const isVisible = distSq <= otherData.viewRangeSq;
668
+ if (isVisible && !wasVisible) {
669
+ otherData.visibleEntities.add(movedData.entity);
670
+ this._emitEvent({
671
+ type: "enter",
672
+ observer: otherData.entity,
673
+ target: movedData.entity,
674
+ position: movedData.position
675
+ }, otherData);
676
+ } else if (!isVisible && wasVisible) {
677
+ otherData.visibleEntities.delete(movedData.entity);
678
+ this._emitEvent({
679
+ type: "exit",
680
+ observer: otherData.entity,
681
+ target: movedData.entity,
682
+ position: movedData.position
683
+ }, otherData);
684
+ }
685
+ }
686
+ }
687
+ }
688
+ }
689
+ /**
690
+ * @zh 获取最大视野范围(用于优化搜索)
691
+ * @en Get maximum view range (for search optimization)
692
+ */
693
+ _getMaxViewRange() {
694
+ let max = 0;
695
+ for (const [, data] of this._observers) {
696
+ if (data.viewRange > max) {
697
+ max = data.viewRange;
698
+ }
699
+ }
700
+ return max;
701
+ }
702
+ /**
703
+ * @zh 发送事件
704
+ * @en Emit event
705
+ */
706
+ _emitEvent(event, observerData) {
707
+ for (const listener of observerData.listeners) {
708
+ try {
709
+ listener(event);
710
+ } catch (e) {
711
+ console.error("AOI entity listener error:", e);
712
+ }
713
+ }
714
+ for (const listener of this._globalListeners) {
715
+ try {
716
+ listener(event);
717
+ } catch (e) {
718
+ console.error("AOI global listener error:", e);
719
+ }
720
+ }
721
+ }
722
+ };
723
+ __name(_GridAOI, "GridAOI");
724
+ var GridAOI = _GridAOI;
725
+ function createGridAOI(cellSize = 100) {
726
+ return new GridAOI({
727
+ cellSize
728
+ });
729
+ }
730
+ __name(createGridAOI, "createGridAOI");
731
+
732
+ // src/tokens.ts
733
+ import { createServiceToken } from "@esengine/ecs-framework";
734
+ var SpatialIndexToken = createServiceToken("spatialIndex");
735
+ var SpatialQueryToken = createServiceToken("spatialQuery");
736
+ var AOIManagerToken = createServiceToken("aoiManager");
737
+
738
+ // src/nodes/SpatialQueryNodes.ts
739
+ var FindInRadiusTemplate = {
740
+ type: "FindInRadius",
741
+ title: "Find In Radius",
742
+ category: "entity",
743
+ description: "Find all objects within radius / \u67E5\u627E\u534A\u5F84\u5185\u7684\u6240\u6709\u5BF9\u8C61",
744
+ keywords: [
745
+ "spatial",
746
+ "find",
747
+ "radius",
748
+ "range",
749
+ "query"
750
+ ],
751
+ menuPath: [
752
+ "Spatial",
753
+ "Find In Radius"
754
+ ],
755
+ isPure: true,
756
+ inputs: [
757
+ {
758
+ name: "centerX",
759
+ displayName: "Center X",
760
+ type: "float",
761
+ defaultValue: 0
762
+ },
763
+ {
764
+ name: "centerY",
765
+ displayName: "Center Y",
766
+ type: "float",
767
+ defaultValue: 0
768
+ },
769
+ {
770
+ name: "radius",
771
+ displayName: "Radius",
772
+ type: "float",
773
+ defaultValue: 100
774
+ }
775
+ ],
776
+ outputs: [
777
+ {
778
+ name: "results",
779
+ displayName: "Results",
780
+ type: "array"
781
+ },
782
+ {
783
+ name: "count",
784
+ displayName: "Count",
785
+ type: "int"
786
+ }
787
+ ],
788
+ color: "#4a9eff"
789
+ };
790
+ var _FindInRadiusExecutor = class _FindInRadiusExecutor {
791
+ execute(node, context) {
792
+ const ctx = context;
793
+ const centerX = ctx.evaluateInput(node.id, "centerX", 0);
794
+ const centerY = ctx.evaluateInput(node.id, "centerY", 0);
795
+ const radius = ctx.evaluateInput(node.id, "radius", 100);
796
+ const results = ctx.spatialQuery?.findInRadius({
797
+ x: centerX,
798
+ y: centerY
799
+ }, radius) ?? [];
800
+ return {
801
+ outputs: {
802
+ results,
803
+ count: results.length
804
+ }
805
+ };
806
+ }
807
+ };
808
+ __name(_FindInRadiusExecutor, "FindInRadiusExecutor");
809
+ var FindInRadiusExecutor = _FindInRadiusExecutor;
810
+ var FindInRectTemplate = {
811
+ type: "FindInRect",
812
+ title: "Find In Rect",
813
+ category: "entity",
814
+ description: "Find all objects within rectangle / \u67E5\u627E\u77E9\u5F62\u533A\u57DF\u5185\u7684\u6240\u6709\u5BF9\u8C61",
815
+ keywords: [
816
+ "spatial",
817
+ "find",
818
+ "rect",
819
+ "rectangle",
820
+ "area",
821
+ "query"
822
+ ],
823
+ menuPath: [
824
+ "Spatial",
825
+ "Find In Rect"
826
+ ],
827
+ isPure: true,
828
+ inputs: [
829
+ {
830
+ name: "minX",
831
+ displayName: "Min X",
832
+ type: "float",
833
+ defaultValue: 0
834
+ },
835
+ {
836
+ name: "minY",
837
+ displayName: "Min Y",
838
+ type: "float",
839
+ defaultValue: 0
840
+ },
841
+ {
842
+ name: "maxX",
843
+ displayName: "Max X",
844
+ type: "float",
845
+ defaultValue: 100
846
+ },
847
+ {
848
+ name: "maxY",
849
+ displayName: "Max Y",
850
+ type: "float",
851
+ defaultValue: 100
852
+ }
853
+ ],
854
+ outputs: [
855
+ {
856
+ name: "results",
857
+ displayName: "Results",
858
+ type: "array"
859
+ },
860
+ {
861
+ name: "count",
862
+ displayName: "Count",
863
+ type: "int"
864
+ }
865
+ ],
866
+ color: "#4a9eff"
867
+ };
868
+ var _FindInRectExecutor = class _FindInRectExecutor {
869
+ execute(node, context) {
870
+ const ctx = context;
871
+ const minX = ctx.evaluateInput(node.id, "minX", 0);
872
+ const minY = ctx.evaluateInput(node.id, "minY", 0);
873
+ const maxX = ctx.evaluateInput(node.id, "maxX", 100);
874
+ const maxY = ctx.evaluateInput(node.id, "maxY", 100);
875
+ const bounds = {
876
+ minX,
877
+ minY,
878
+ maxX,
879
+ maxY
880
+ };
881
+ const results = ctx.spatialQuery?.findInRect(bounds) ?? [];
882
+ return {
883
+ outputs: {
884
+ results,
885
+ count: results.length
886
+ }
887
+ };
888
+ }
889
+ };
890
+ __name(_FindInRectExecutor, "FindInRectExecutor");
891
+ var FindInRectExecutor = _FindInRectExecutor;
892
+ var FindNearestTemplate = {
893
+ type: "FindNearest",
894
+ title: "Find Nearest",
895
+ category: "entity",
896
+ description: "Find nearest object to a point / \u67E5\u627E\u8DDD\u79BB\u70B9\u6700\u8FD1\u7684\u5BF9\u8C61",
897
+ keywords: [
898
+ "spatial",
899
+ "find",
900
+ "nearest",
901
+ "closest",
902
+ "query"
903
+ ],
904
+ menuPath: [
905
+ "Spatial",
906
+ "Find Nearest"
907
+ ],
908
+ isPure: true,
909
+ inputs: [
910
+ {
911
+ name: "centerX",
912
+ displayName: "Center X",
913
+ type: "float",
914
+ defaultValue: 0
915
+ },
916
+ {
917
+ name: "centerY",
918
+ displayName: "Center Y",
919
+ type: "float",
920
+ defaultValue: 0
921
+ },
922
+ {
923
+ name: "maxDistance",
924
+ displayName: "Max Distance",
925
+ type: "float",
926
+ defaultValue: 1e3
927
+ }
928
+ ],
929
+ outputs: [
930
+ {
931
+ name: "result",
932
+ displayName: "Result",
933
+ type: "any"
934
+ },
935
+ {
936
+ name: "found",
937
+ displayName: "Found",
938
+ type: "bool"
939
+ }
940
+ ],
941
+ color: "#4a9eff"
942
+ };
943
+ var _FindNearestExecutor = class _FindNearestExecutor {
944
+ execute(node, context) {
945
+ const ctx = context;
946
+ const centerX = ctx.evaluateInput(node.id, "centerX", 0);
947
+ const centerY = ctx.evaluateInput(node.id, "centerY", 0);
948
+ const maxDistance = ctx.evaluateInput(node.id, "maxDistance", 1e3);
949
+ const result = ctx.spatialQuery?.findNearest({
950
+ x: centerX,
951
+ y: centerY
952
+ }, maxDistance) ?? null;
953
+ return {
954
+ outputs: {
955
+ result,
956
+ found: result !== null
957
+ }
958
+ };
959
+ }
960
+ };
961
+ __name(_FindNearestExecutor, "FindNearestExecutor");
962
+ var FindNearestExecutor = _FindNearestExecutor;
963
+ var FindKNearestTemplate = {
964
+ type: "FindKNearest",
965
+ title: "Find K Nearest",
966
+ category: "entity",
967
+ description: "Find K nearest objects to a point / \u67E5\u627E\u8DDD\u79BB\u70B9\u6700\u8FD1\u7684 K \u4E2A\u5BF9\u8C61",
968
+ keywords: [
969
+ "spatial",
970
+ "find",
971
+ "nearest",
972
+ "k",
973
+ "query"
974
+ ],
975
+ menuPath: [
976
+ "Spatial",
977
+ "Find K Nearest"
978
+ ],
979
+ isPure: true,
980
+ inputs: [
981
+ {
982
+ name: "centerX",
983
+ displayName: "Center X",
984
+ type: "float",
985
+ defaultValue: 0
986
+ },
987
+ {
988
+ name: "centerY",
989
+ displayName: "Center Y",
990
+ type: "float",
991
+ defaultValue: 0
992
+ },
993
+ {
994
+ name: "k",
995
+ displayName: "K",
996
+ type: "int",
997
+ defaultValue: 5
998
+ },
999
+ {
1000
+ name: "maxDistance",
1001
+ displayName: "Max Distance",
1002
+ type: "float",
1003
+ defaultValue: 1e3
1004
+ }
1005
+ ],
1006
+ outputs: [
1007
+ {
1008
+ name: "results",
1009
+ displayName: "Results",
1010
+ type: "array"
1011
+ },
1012
+ {
1013
+ name: "count",
1014
+ displayName: "Count",
1015
+ type: "int"
1016
+ }
1017
+ ],
1018
+ color: "#4a9eff"
1019
+ };
1020
+ var _FindKNearestExecutor = class _FindKNearestExecutor {
1021
+ execute(node, context) {
1022
+ const ctx = context;
1023
+ const centerX = ctx.evaluateInput(node.id, "centerX", 0);
1024
+ const centerY = ctx.evaluateInput(node.id, "centerY", 0);
1025
+ const k = ctx.evaluateInput(node.id, "k", 5);
1026
+ const maxDistance = ctx.evaluateInput(node.id, "maxDistance", 1e3);
1027
+ const results = ctx.spatialQuery?.findKNearest({
1028
+ x: centerX,
1029
+ y: centerY
1030
+ }, k, maxDistance) ?? [];
1031
+ return {
1032
+ outputs: {
1033
+ results,
1034
+ count: results.length
1035
+ }
1036
+ };
1037
+ }
1038
+ };
1039
+ __name(_FindKNearestExecutor, "FindKNearestExecutor");
1040
+ var FindKNearestExecutor = _FindKNearestExecutor;
1041
+ var RaycastTemplate = {
1042
+ type: "Raycast",
1043
+ title: "Raycast",
1044
+ category: "entity",
1045
+ description: "Cast a ray and get all hits / \u53D1\u5C04\u5C04\u7EBF\u5E76\u83B7\u53D6\u6240\u6709\u547D\u4E2D",
1046
+ keywords: [
1047
+ "spatial",
1048
+ "raycast",
1049
+ "ray",
1050
+ "hit",
1051
+ "query"
1052
+ ],
1053
+ menuPath: [
1054
+ "Spatial",
1055
+ "Raycast"
1056
+ ],
1057
+ isPure: true,
1058
+ inputs: [
1059
+ {
1060
+ name: "originX",
1061
+ displayName: "Origin X",
1062
+ type: "float",
1063
+ defaultValue: 0
1064
+ },
1065
+ {
1066
+ name: "originY",
1067
+ displayName: "Origin Y",
1068
+ type: "float",
1069
+ defaultValue: 0
1070
+ },
1071
+ {
1072
+ name: "directionX",
1073
+ displayName: "Direction X",
1074
+ type: "float",
1075
+ defaultValue: 1
1076
+ },
1077
+ {
1078
+ name: "directionY",
1079
+ displayName: "Direction Y",
1080
+ type: "float",
1081
+ defaultValue: 0
1082
+ },
1083
+ {
1084
+ name: "maxDistance",
1085
+ displayName: "Max Distance",
1086
+ type: "float",
1087
+ defaultValue: 1e3
1088
+ }
1089
+ ],
1090
+ outputs: [
1091
+ {
1092
+ name: "hits",
1093
+ displayName: "Hits",
1094
+ type: "array"
1095
+ },
1096
+ {
1097
+ name: "hitCount",
1098
+ displayName: "Hit Count",
1099
+ type: "int"
1100
+ },
1101
+ {
1102
+ name: "hasHit",
1103
+ displayName: "Has Hit",
1104
+ type: "bool"
1105
+ }
1106
+ ],
1107
+ color: "#4a9eff"
1108
+ };
1109
+ var _RaycastExecutor = class _RaycastExecutor {
1110
+ execute(node, context) {
1111
+ const ctx = context;
1112
+ const originX = ctx.evaluateInput(node.id, "originX", 0);
1113
+ const originY = ctx.evaluateInput(node.id, "originY", 0);
1114
+ const directionX = ctx.evaluateInput(node.id, "directionX", 1);
1115
+ const directionY = ctx.evaluateInput(node.id, "directionY", 0);
1116
+ const maxDistance = ctx.evaluateInput(node.id, "maxDistance", 1e3);
1117
+ const hits = ctx.spatialQuery?.raycast({
1118
+ x: originX,
1119
+ y: originY
1120
+ }, {
1121
+ x: directionX,
1122
+ y: directionY
1123
+ }, maxDistance) ?? [];
1124
+ return {
1125
+ outputs: {
1126
+ hits,
1127
+ hitCount: hits.length,
1128
+ hasHit: hits.length > 0
1129
+ }
1130
+ };
1131
+ }
1132
+ };
1133
+ __name(_RaycastExecutor, "RaycastExecutor");
1134
+ var RaycastExecutor = _RaycastExecutor;
1135
+ var RaycastFirstTemplate = {
1136
+ type: "RaycastFirst",
1137
+ title: "Raycast First",
1138
+ category: "entity",
1139
+ description: "Cast a ray and get first hit / \u53D1\u5C04\u5C04\u7EBF\u5E76\u83B7\u53D6\u7B2C\u4E00\u4E2A\u547D\u4E2D",
1140
+ keywords: [
1141
+ "spatial",
1142
+ "raycast",
1143
+ "ray",
1144
+ "first",
1145
+ "hit",
1146
+ "query"
1147
+ ],
1148
+ menuPath: [
1149
+ "Spatial",
1150
+ "Raycast First"
1151
+ ],
1152
+ isPure: true,
1153
+ inputs: [
1154
+ {
1155
+ name: "originX",
1156
+ displayName: "Origin X",
1157
+ type: "float",
1158
+ defaultValue: 0
1159
+ },
1160
+ {
1161
+ name: "originY",
1162
+ displayName: "Origin Y",
1163
+ type: "float",
1164
+ defaultValue: 0
1165
+ },
1166
+ {
1167
+ name: "directionX",
1168
+ displayName: "Direction X",
1169
+ type: "float",
1170
+ defaultValue: 1
1171
+ },
1172
+ {
1173
+ name: "directionY",
1174
+ displayName: "Direction Y",
1175
+ type: "float",
1176
+ defaultValue: 0
1177
+ },
1178
+ {
1179
+ name: "maxDistance",
1180
+ displayName: "Max Distance",
1181
+ type: "float",
1182
+ defaultValue: 1e3
1183
+ }
1184
+ ],
1185
+ outputs: [
1186
+ {
1187
+ name: "hit",
1188
+ displayName: "Hit",
1189
+ type: "object"
1190
+ },
1191
+ {
1192
+ name: "target",
1193
+ displayName: "Target",
1194
+ type: "any"
1195
+ },
1196
+ {
1197
+ name: "hitPointX",
1198
+ displayName: "Hit Point X",
1199
+ type: "float"
1200
+ },
1201
+ {
1202
+ name: "hitPointY",
1203
+ displayName: "Hit Point Y",
1204
+ type: "float"
1205
+ },
1206
+ {
1207
+ name: "distance",
1208
+ displayName: "Distance",
1209
+ type: "float"
1210
+ },
1211
+ {
1212
+ name: "hasHit",
1213
+ displayName: "Has Hit",
1214
+ type: "bool"
1215
+ }
1216
+ ],
1217
+ color: "#4a9eff"
1218
+ };
1219
+ var _RaycastFirstExecutor = class _RaycastFirstExecutor {
1220
+ execute(node, context) {
1221
+ const ctx = context;
1222
+ const originX = ctx.evaluateInput(node.id, "originX", 0);
1223
+ const originY = ctx.evaluateInput(node.id, "originY", 0);
1224
+ const directionX = ctx.evaluateInput(node.id, "directionX", 1);
1225
+ const directionY = ctx.evaluateInput(node.id, "directionY", 0);
1226
+ const maxDistance = ctx.evaluateInput(node.id, "maxDistance", 1e3);
1227
+ const hit = ctx.spatialQuery?.raycastFirst({
1228
+ x: originX,
1229
+ y: originY
1230
+ }, {
1231
+ x: directionX,
1232
+ y: directionY
1233
+ }, maxDistance) ?? null;
1234
+ return {
1235
+ outputs: {
1236
+ hit,
1237
+ target: hit?.target ?? null,
1238
+ hitPointX: hit?.point.x ?? 0,
1239
+ hitPointY: hit?.point.y ?? 0,
1240
+ distance: hit?.distance ?? 0,
1241
+ hasHit: hit !== null
1242
+ }
1243
+ };
1244
+ }
1245
+ };
1246
+ __name(_RaycastFirstExecutor, "RaycastFirstExecutor");
1247
+ var RaycastFirstExecutor = _RaycastFirstExecutor;
1248
+ var SpatialQueryNodeDefinitions = [
1249
+ {
1250
+ template: FindInRadiusTemplate,
1251
+ executor: new FindInRadiusExecutor()
1252
+ },
1253
+ {
1254
+ template: FindInRectTemplate,
1255
+ executor: new FindInRectExecutor()
1256
+ },
1257
+ {
1258
+ template: FindNearestTemplate,
1259
+ executor: new FindNearestExecutor()
1260
+ },
1261
+ {
1262
+ template: FindKNearestTemplate,
1263
+ executor: new FindKNearestExecutor()
1264
+ },
1265
+ {
1266
+ template: RaycastTemplate,
1267
+ executor: new RaycastExecutor()
1268
+ },
1269
+ {
1270
+ template: RaycastFirstTemplate,
1271
+ executor: new RaycastFirstExecutor()
1272
+ }
1273
+ ];
1274
+
1275
+ // src/aoi/AOINodes.ts
1276
+ var GetEntitiesInViewTemplate = {
1277
+ type: "GetEntitiesInView",
1278
+ title: "Get Entities In View",
1279
+ category: "entity",
1280
+ description: "Get all entities within view range / \u83B7\u53D6\u89C6\u91CE\u8303\u56F4\u5185\u7684\u6240\u6709\u5B9E\u4F53",
1281
+ keywords: [
1282
+ "aoi",
1283
+ "view",
1284
+ "entities",
1285
+ "visible"
1286
+ ],
1287
+ menuPath: [
1288
+ "AOI",
1289
+ "Get Entities In View"
1290
+ ],
1291
+ isPure: true,
1292
+ inputs: [
1293
+ {
1294
+ name: "observer",
1295
+ displayName: "Observer",
1296
+ type: "object"
1297
+ }
1298
+ ],
1299
+ outputs: [
1300
+ {
1301
+ name: "entities",
1302
+ displayName: "Entities",
1303
+ type: "array"
1304
+ },
1305
+ {
1306
+ name: "count",
1307
+ displayName: "Count",
1308
+ type: "int"
1309
+ }
1310
+ ],
1311
+ color: "#9c27b0"
1312
+ };
1313
+ var _GetEntitiesInViewExecutor = class _GetEntitiesInViewExecutor {
1314
+ execute(node, context) {
1315
+ const ctx = context;
1316
+ const observer = ctx.evaluateInput(node.id, "observer", ctx.entity);
1317
+ const entities = ctx.aoiManager?.getEntitiesInView(observer) ?? [];
1318
+ return {
1319
+ outputs: {
1320
+ entities,
1321
+ count: entities.length
1322
+ }
1323
+ };
1324
+ }
1325
+ };
1326
+ __name(_GetEntitiesInViewExecutor, "GetEntitiesInViewExecutor");
1327
+ var GetEntitiesInViewExecutor = _GetEntitiesInViewExecutor;
1328
+ var GetObserversOfTemplate = {
1329
+ type: "GetObserversOf",
1330
+ title: "Get Observers Of",
1331
+ category: "entity",
1332
+ description: "Get all observers who can see the entity / \u83B7\u53D6\u80FD\u770B\u5230\u8BE5\u5B9E\u4F53\u7684\u6240\u6709\u89C2\u5BDF\u8005",
1333
+ keywords: [
1334
+ "aoi",
1335
+ "observers",
1336
+ "watchers",
1337
+ "visible"
1338
+ ],
1339
+ menuPath: [
1340
+ "AOI",
1341
+ "Get Observers Of"
1342
+ ],
1343
+ isPure: true,
1344
+ inputs: [
1345
+ {
1346
+ name: "target",
1347
+ displayName: "Target",
1348
+ type: "object"
1349
+ }
1350
+ ],
1351
+ outputs: [
1352
+ {
1353
+ name: "observers",
1354
+ displayName: "Observers",
1355
+ type: "array"
1356
+ },
1357
+ {
1358
+ name: "count",
1359
+ displayName: "Count",
1360
+ type: "int"
1361
+ }
1362
+ ],
1363
+ color: "#9c27b0"
1364
+ };
1365
+ var _GetObserversOfExecutor = class _GetObserversOfExecutor {
1366
+ execute(node, context) {
1367
+ const ctx = context;
1368
+ const target = ctx.evaluateInput(node.id, "target", ctx.entity);
1369
+ const observers = ctx.aoiManager?.getObserversOf(target) ?? [];
1370
+ return {
1371
+ outputs: {
1372
+ observers,
1373
+ count: observers.length
1374
+ }
1375
+ };
1376
+ }
1377
+ };
1378
+ __name(_GetObserversOfExecutor, "GetObserversOfExecutor");
1379
+ var GetObserversOfExecutor = _GetObserversOfExecutor;
1380
+ var CanSeeTemplate = {
1381
+ type: "CanSee",
1382
+ title: "Can See",
1383
+ category: "entity",
1384
+ description: "Check if observer can see target / \u68C0\u67E5\u89C2\u5BDF\u8005\u662F\u5426\u80FD\u770B\u5230\u76EE\u6807",
1385
+ keywords: [
1386
+ "aoi",
1387
+ "visibility",
1388
+ "can",
1389
+ "see",
1390
+ "check"
1391
+ ],
1392
+ menuPath: [
1393
+ "AOI",
1394
+ "Can See"
1395
+ ],
1396
+ isPure: true,
1397
+ inputs: [
1398
+ {
1399
+ name: "observer",
1400
+ displayName: "Observer",
1401
+ type: "object"
1402
+ },
1403
+ {
1404
+ name: "target",
1405
+ displayName: "Target",
1406
+ type: "object"
1407
+ }
1408
+ ],
1409
+ outputs: [
1410
+ {
1411
+ name: "canSee",
1412
+ displayName: "Can See",
1413
+ type: "bool"
1414
+ }
1415
+ ],
1416
+ color: "#9c27b0"
1417
+ };
1418
+ var _CanSeeExecutor = class _CanSeeExecutor {
1419
+ execute(node, context) {
1420
+ const ctx = context;
1421
+ const observer = ctx.evaluateInput(node.id, "observer", ctx.entity);
1422
+ const target = ctx.evaluateInput(node.id, "target", null);
1423
+ const canSee = ctx.aoiManager?.canSee(observer, target) ?? false;
1424
+ return {
1425
+ outputs: {
1426
+ canSee
1427
+ }
1428
+ };
1429
+ }
1430
+ };
1431
+ __name(_CanSeeExecutor, "CanSeeExecutor");
1432
+ var CanSeeExecutor = _CanSeeExecutor;
1433
+ var OnEntityEnterViewTemplate = {
1434
+ type: "EventEntityEnterView",
1435
+ title: "On Entity Enter View",
1436
+ category: "event",
1437
+ description: "Triggered when an entity enters view / \u5F53\u5B9E\u4F53\u8FDB\u5165\u89C6\u91CE\u65F6\u89E6\u53D1",
1438
+ keywords: [
1439
+ "aoi",
1440
+ "event",
1441
+ "enter",
1442
+ "view",
1443
+ "visible"
1444
+ ],
1445
+ menuPath: [
1446
+ "AOI",
1447
+ "Events",
1448
+ "On Entity Enter View"
1449
+ ],
1450
+ color: "#e91e63",
1451
+ inputs: [],
1452
+ outputs: [
1453
+ {
1454
+ name: "exec",
1455
+ displayName: "",
1456
+ type: "exec"
1457
+ },
1458
+ {
1459
+ name: "entity",
1460
+ displayName: "Entity",
1461
+ type: "object"
1462
+ },
1463
+ {
1464
+ name: "positionX",
1465
+ displayName: "Position X",
1466
+ type: "float"
1467
+ },
1468
+ {
1469
+ name: "positionY",
1470
+ displayName: "Position Y",
1471
+ type: "float"
1472
+ }
1473
+ ]
1474
+ };
1475
+ var _OnEntityEnterViewExecutor = class _OnEntityEnterViewExecutor {
1476
+ execute(_node, _context) {
1477
+ return {
1478
+ nextExec: "exec"
1479
+ };
1480
+ }
1481
+ };
1482
+ __name(_OnEntityEnterViewExecutor, "OnEntityEnterViewExecutor");
1483
+ var OnEntityEnterViewExecutor = _OnEntityEnterViewExecutor;
1484
+ var OnEntityExitViewTemplate = {
1485
+ type: "EventEntityExitView",
1486
+ title: "On Entity Exit View",
1487
+ category: "event",
1488
+ description: "Triggered when an entity exits view / \u5F53\u5B9E\u4F53\u79BB\u5F00\u89C6\u91CE\u65F6\u89E6\u53D1",
1489
+ keywords: [
1490
+ "aoi",
1491
+ "event",
1492
+ "exit",
1493
+ "view",
1494
+ "invisible"
1495
+ ],
1496
+ menuPath: [
1497
+ "AOI",
1498
+ "Events",
1499
+ "On Entity Exit View"
1500
+ ],
1501
+ color: "#e91e63",
1502
+ inputs: [],
1503
+ outputs: [
1504
+ {
1505
+ name: "exec",
1506
+ displayName: "",
1507
+ type: "exec"
1508
+ },
1509
+ {
1510
+ name: "entity",
1511
+ displayName: "Entity",
1512
+ type: "object"
1513
+ },
1514
+ {
1515
+ name: "positionX",
1516
+ displayName: "Position X",
1517
+ type: "float"
1518
+ },
1519
+ {
1520
+ name: "positionY",
1521
+ displayName: "Position Y",
1522
+ type: "float"
1523
+ }
1524
+ ]
1525
+ };
1526
+ var _OnEntityExitViewExecutor = class _OnEntityExitViewExecutor {
1527
+ execute(_node, _context) {
1528
+ return {
1529
+ nextExec: "exec"
1530
+ };
1531
+ }
1532
+ };
1533
+ __name(_OnEntityExitViewExecutor, "OnEntityExitViewExecutor");
1534
+ var OnEntityExitViewExecutor = _OnEntityExitViewExecutor;
1535
+ var AOINodeDefinitions = {
1536
+ templates: [
1537
+ GetEntitiesInViewTemplate,
1538
+ GetObserversOfTemplate,
1539
+ CanSeeTemplate,
1540
+ OnEntityEnterViewTemplate,
1541
+ OnEntityExitViewTemplate
1542
+ ],
1543
+ executors: /* @__PURE__ */ new Map([
1544
+ [
1545
+ "GetEntitiesInView",
1546
+ new GetEntitiesInViewExecutor()
1547
+ ],
1548
+ [
1549
+ "GetObserversOf",
1550
+ new GetObserversOfExecutor()
1551
+ ],
1552
+ [
1553
+ "CanSee",
1554
+ new CanSeeExecutor()
1555
+ ],
1556
+ [
1557
+ "EventEntityEnterView",
1558
+ new OnEntityEnterViewExecutor()
1559
+ ],
1560
+ [
1561
+ "EventEntityExitView",
1562
+ new OnEntityExitViewExecutor()
1563
+ ]
1564
+ ])
1565
+ };
1566
+ export {
1567
+ AOIManagerToken,
1568
+ AOINodeDefinitions,
1569
+ CanSeeExecutor,
1570
+ CanSeeTemplate,
1571
+ FindInRadiusExecutor,
1572
+ FindInRadiusTemplate,
1573
+ FindInRectExecutor,
1574
+ FindInRectTemplate,
1575
+ FindKNearestExecutor,
1576
+ FindKNearestTemplate,
1577
+ FindNearestExecutor,
1578
+ FindNearestTemplate,
1579
+ GetEntitiesInViewExecutor,
1580
+ GetEntitiesInViewTemplate,
1581
+ GetObserversOfExecutor,
1582
+ GetObserversOfTemplate,
1583
+ GridAOI,
1584
+ GridSpatialIndex,
1585
+ OnEntityEnterViewExecutor,
1586
+ OnEntityEnterViewTemplate,
1587
+ OnEntityExitViewExecutor,
1588
+ OnEntityExitViewTemplate,
1589
+ RaycastExecutor,
1590
+ RaycastFirstExecutor,
1591
+ RaycastFirstTemplate,
1592
+ RaycastTemplate,
1593
+ SpatialIndexToken,
1594
+ SpatialQueryNodeDefinitions,
1595
+ SpatialQueryToken,
1596
+ boundsIntersect,
1597
+ boundsIntersectsCircle,
1598
+ createBounds,
1599
+ createBoundsFromCenter,
1600
+ createBoundsFromCircle,
1601
+ createGridAOI,
1602
+ createGridSpatialIndex,
1603
+ distance,
1604
+ distanceSquared,
1605
+ isPointInBounds
1606
+ };
1607
+ //# sourceMappingURL=index.js.map