@forgeax/engine-picking 0.1.32 → 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.
- package/README.md +51 -4
- package/dist/__tests__/display-picking.unit.test.d.ts +2 -0
- package/dist/__tests__/display-picking.unit.test.d.ts.map +1 -0
- package/dist/__tests__/voxel-display-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/voxel-display-consumer.test-d.d.ts.map +1 -0
- package/dist/display-picking.d.ts +27 -0
- package/dist/display-picking.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +455 -273
- package/dist/index.mjs.map +1 -1
- package/dist/pick-core.d.ts +2 -0
- package/dist/pick-core.d.ts.map +1 -1
- package/dist/pick-triangle.d.ts +0 -3
- package/dist/pick-triangle.d.ts.map +1 -1
- package/dist/pick-vertex.d.ts +9 -0
- package/dist/pick-vertex.d.ts.map +1 -1
- package/dist/pick.d.ts +3 -0
- package/dist/pick.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/__tests__/display-picking.unit.test.ts +163 -0
- package/src/__tests__/pick-triangle.unit.test.ts +6 -9
- package/src/__tests__/voxel-display-consumer.test-d.ts +57 -0
- package/src/display-picking.ts +302 -0
- package/src/index.ts +9 -0
- package/src/pick-core.ts +33 -0
- package/src/pick-triangle.ts +3 -12
- package/src/pick-vertex.ts +77 -12
- package/src/pick.ts +6 -1
package/src/pick-triangle.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|
package/src/pick-vertex.ts
CHANGED
|
@@ -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 =
|
|
211
|
-
|
|
212
|
-
cameraEntity
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|