@forgeax/engine-picking 0.1.33 → 0.1.34

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.
@@ -8,7 +8,6 @@
8
8
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
9
9
  import type { EntityHandle, World } from '@forgeax/engine-ecs';
10
10
  import { mat4, ray, type Vec3Like, vec3 } from '@forgeax/engine-math';
11
- import type { InstanceCollectionId, InstanceCollectionStore } from '@forgeax/engine-render';
12
11
  import { Instances, MeshFilter, MeshRenderer } from '@forgeax/engine-render';
13
12
  import { GlobalTransform, Transform } from '@forgeax/engine-scene';
14
13
  import type { MeshAsset } from '@forgeax/engine-types';
@@ -50,8 +49,6 @@ export type TrianglePickResult =
50
49
  export interface TrianglePickOptions {
51
50
  /** Resolve a loaded MeshAsset payload to its existing Catalog GUID. */
52
51
  readonly assetGuidOf?: (asset: MeshAsset) => string | undefined;
53
- /** Read explicit instance matrices without advancing the renderer upload cursor. */
54
- readonly instances?: Pick<InstanceCollectionStore, 'peek'>;
55
52
  }
56
53
 
57
54
  function narrowPosition(position: MeshAsset['attributes']['position']): Float32Array | undefined {
@@ -155,19 +152,13 @@ export function pickTriangle(
155
152
  let instanceTransforms: Float32Array | undefined;
156
153
  let instanceCount = 1;
157
154
  if (instancesData !== undefined) {
158
- if (options.instances === undefined) {
155
+ instanceTransforms = instancesData.transforms;
156
+ if (instanceTransforms.length % 16 !== 0 || !instanceTransforms.every(Number.isFinite)) {
159
157
  unsupportedReason ??= 'instance-transforms-unavailable';
160
158
  unsupportedEntities.push(entity);
161
159
  continue;
162
160
  }
163
- const snapshot = options.instances.peek(instancesData.collectionId as InstanceCollectionId);
164
- if (!snapshot.ok) {
165
- unsupportedReason ??= 'instance-transforms-unavailable';
166
- unsupportedEntities.push(entity);
167
- continue;
168
- }
169
- instanceTransforms = snapshot.value.transforms;
170
- instanceCount = snapshot.value.count;
161
+ instanceCount = instanceTransforms.length / 16;
171
162
  }
172
163
 
173
164
  const positions = narrowPosition(mesh.attributes.position);
@@ -50,7 +50,7 @@ import { MeshFilter, MeshRenderer } from '@forgeax/engine-render';
50
50
  import { GlobalTransform, Transform } from '@forgeax/engine-scene';
51
51
  import type { MeshAsset } from '@forgeax/engine-types';
52
52
  import { toShared } from '@forgeax/engine-types';
53
- import { computeScreenRay, readWorldMatrix } from './pick-core';
53
+ import { computeScreenRay, readWorldMatrix, type ScreenRay } from './pick-core';
54
54
 
55
55
  // ── types ────────────────────────────────────────────────────────────────
56
56
 
@@ -197,24 +197,22 @@ function narrowPosition(
197
197
  */
198
198
  function collectVertexHits(
199
199
  world: World,
200
- cameraEntity: EntityHandle,
200
+ cameraEntity: EntityHandle | undefined,
201
201
  screenX: number,
202
202
  screenY: number,
203
203
  viewportWidth: number,
204
204
  viewportHeight: number,
205
205
  entity: EntityHandle,
206
+ screenRayOverride?: ScreenRay,
206
207
  ): VertexHit[] {
207
208
  // ── camera validation + view/projection + screen->world ray (pick-core skeleton) ──
208
209
  // Throws PickError('camera-component-missing') when cameraEntity has no Camera;
209
210
  // returns undefined when the camera has no resolvable GlobalTransform.world (D-9 preamble miss).
210
- const screenRay = computeScreenRay(
211
- world,
212
- cameraEntity,
213
- screenX,
214
- screenY,
215
- viewportWidth,
216
- viewportHeight,
217
- );
211
+ const screenRay =
212
+ screenRayOverride ??
213
+ (cameraEntity === undefined
214
+ ? undefined
215
+ : computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight));
218
216
  if (screenRay === undefined) {
219
217
  return [];
220
218
  }
@@ -446,14 +444,39 @@ export function pickVertexOnEntity(
446
444
  entity: EntityHandle,
447
445
  options?: { limit: number },
448
446
  ): VertexHit | VertexHit[] | undefined {
447
+ return pickVertexOnEntityWithScreenRay(
448
+ world,
449
+ computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight),
450
+ screenX,
451
+ screenY,
452
+ viewportWidth,
453
+ viewportHeight,
454
+ entity,
455
+ options,
456
+ );
457
+ }
458
+
459
+ /** Query one entity using a receipt-bound screen ray from the accepted frame. */
460
+ export function pickVertexOnEntityWithScreenRay(
461
+ world: World,
462
+ screenRay: ScreenRay | undefined,
463
+ screenX: number,
464
+ screenY: number,
465
+ viewportWidth: number,
466
+ viewportHeight: number,
467
+ entity: EntityHandle,
468
+ options?: { limit: number },
469
+ ): VertexHit | VertexHit[] | undefined {
470
+ if (screenRay === undefined) return options === undefined ? undefined : [];
449
471
  const candidates = collectVertexHits(
450
472
  world,
451
- cameraEntity,
473
+ undefined,
452
474
  screenX,
453
475
  screenY,
454
476
  viewportWidth,
455
477
  viewportHeight,
456
478
  entity,
479
+ screenRay,
457
480
  );
458
481
 
459
482
  // ── sort by screenDist ascending ──
@@ -534,6 +557,47 @@ export function pickVertex(
534
557
  viewportWidth,
535
558
  viewportHeight,
536
559
  );
560
+ return pickVertexFromScreenRay(
561
+ world,
562
+ screenRay,
563
+ screenX,
564
+ screenY,
565
+ viewportWidth,
566
+ viewportHeight,
567
+ options,
568
+ );
569
+ }
570
+
571
+ /** Query all entities using a receipt-bound screen ray from the accepted frame. */
572
+ export function pickVertexWithScreenRay(
573
+ world: World,
574
+ screenRay: ScreenRay | undefined,
575
+ screenX: number,
576
+ screenY: number,
577
+ viewportWidth: number,
578
+ viewportHeight: number,
579
+ options?: { limit: number },
580
+ ): VertexHit | VertexHit[] | undefined {
581
+ return pickVertexFromScreenRay(
582
+ world,
583
+ screenRay,
584
+ screenX,
585
+ screenY,
586
+ viewportWidth,
587
+ viewportHeight,
588
+ options,
589
+ );
590
+ }
591
+
592
+ function pickVertexFromScreenRay(
593
+ world: World,
594
+ screenRay: ScreenRay | undefined,
595
+ screenX: number,
596
+ screenY: number,
597
+ viewportWidth: number,
598
+ viewportHeight: number,
599
+ options?: { limit: number },
600
+ ): VertexHit | VertexHit[] | undefined {
537
601
  if (screenRay === undefined) {
538
602
  if (options) return [];
539
603
  return undefined;
@@ -574,12 +638,13 @@ export function pickVertex(
574
638
  // Collect vertices for this entity
575
639
  const entityHits = collectVertexHits(
576
640
  world,
577
- cameraEntity,
641
+ undefined,
578
642
  screenX,
579
643
  screenY,
580
644
  viewportWidth,
581
645
  viewportHeight,
582
646
  entity,
647
+ screenRay,
583
648
  );
584
649
  for (const h of entityHits) {
585
650
  allCandidates.push(h);
package/src/pick.ts CHANGED
@@ -39,7 +39,7 @@ import { MeshFilter, MeshRenderer } from '@forgeax/engine-render';
39
39
  import { GlobalTransform, Transform } from '@forgeax/engine-scene';
40
40
  import type { MeshAsset } from '@forgeax/engine-types';
41
41
  import { toShared } from '@forgeax/engine-types';
42
- import { computeScreenRay, readWorldMatrix } from './pick-core';
42
+ import { computeScreenRay, readWorldMatrix, type ScreenRay } from './pick-core';
43
43
 
44
44
  /**
45
45
  * Result of a successful screen-to-entity pick.
@@ -92,6 +92,11 @@ export function pick(
92
92
  viewportHeight,
93
93
  );
94
94
  if (screenRay === undefined) return undefined;
95
+ return pickWithScreenRay(world, screenRay);
96
+ }
97
+
98
+ /** Raycast using a receipt-bound screen ray from the accepted render frame. */
99
+ export function pickWithScreenRay(world: World, screenRay: ScreenRay): PickHit | undefined {
95
100
  const r = screenRay.ray;
96
101
 
97
102
  const query = world