@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/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
2
1
  import { vec2, box3, ray, vec3, mat4 } from '@forgeax/engine-math';
3
- import { MeshFilter, MeshRenderer, Instances, Camera, cameraProjectionFromF32 } from '@forgeax/engine-render';
2
+ import { MeshFilter, MeshRenderer, mapDisplayToScene, Instances, Camera, cameraProjectionFromF32, mapSceneToDisplay } from '@forgeax/engine-render';
3
+ import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
4
4
  import { Transform, GlobalTransform, ChildOf } from '@forgeax/engine-scene';
5
5
  import { toShared, err, ok } from '@forgeax/engine-types';
6
6
  import { Entity } from '@forgeax/engine-ecs';
7
7
  import { Tilemap, TileLayer } from '@forgeax/engine-render/authoring';
8
8
 
9
- // src/pick.ts
9
+ // src/display-picking.ts
10
10
 
11
11
  // src/pick-errors.ts
12
12
  var PickError = class extends Error {
@@ -30,6 +30,18 @@ function readWorldMatrix(world, entity) {
30
30
  const result = world.get(entity, GlobalTransform);
31
31
  return result.ok ? new Float32Array(result.value.world) : void 0;
32
32
  }
33
+ function computeScreenRayFromMatrices(screenX, screenY, viewportWidth, viewportHeight, viewMatrix, projectionMatrix, projectionKind) {
34
+ if (!Number.isFinite(screenX) || !Number.isFinite(screenY) || !Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight) || viewportWidth <= 0 || viewportHeight <= 0 || viewMatrix.length !== 16 || projectionMatrix.length !== 16 || !Array.from(viewMatrix).every((value) => Number.isFinite(value)) || !Array.from(projectionMatrix).every((value) => Number.isFinite(value))) {
35
+ return void 0;
36
+ }
37
+ const view = mat4.create();
38
+ const proj = mat4.create();
39
+ view.set(viewMatrix);
40
+ proj.set(projectionMatrix);
41
+ const r = ray.create();
42
+ ray.screenToRay(r, screenX, screenY, viewportWidth, viewportHeight, view, proj, projectionKind);
43
+ return { ray: r, view, proj, projectionKind };
44
+ }
33
45
  function computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight) {
34
46
  if (!Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight) || viewportWidth <= 0 || viewportHeight <= 0) {
35
47
  return void 0;
@@ -68,6 +80,9 @@ function pick(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeig
68
80
  viewportHeight
69
81
  );
70
82
  if (screenRay === void 0) return void 0;
83
+ return pickWithScreenRay(world, screenRay);
84
+ }
85
+ function pickWithScreenRay(world, screenRay) {
71
86
  const r = screenRay.ray;
72
87
  const query = world.query({ read: [Transform, GlobalTransform, MeshFilter, MeshRenderer] }).unwrap();
73
88
  const worldAabb = box3.create();
@@ -107,6 +122,437 @@ function pick(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeig
107
122
  );
108
123
  return { entity: bestEntity, point, distance: bestDistance };
109
124
  }
125
+ var _scratchVec2 = vec2.create();
126
+ function pointToRayDist(px, py, pz, ox, oy, oz, dx, dy, dz) {
127
+ const ex = px - ox;
128
+ const ey = py - oy;
129
+ const ez = pz - oz;
130
+ const cx = ey * dz - ez * dy;
131
+ const cy = ez * dx - ex * dz;
132
+ const cz = ex * dy - ey * dx;
133
+ return Math.sqrt(cx * cx + cy * cy + cz * cz);
134
+ }
135
+ function rayHitsWorldAabb(r, localAabb, worldMat) {
136
+ if (localAabb[0] > localAabb[3]) return true;
137
+ const corners = [
138
+ [localAabb[0], localAabb[1], localAabb[2]],
139
+ [localAabb[3], localAabb[1], localAabb[2]],
140
+ [localAabb[0], localAabb[4], localAabb[2]],
141
+ [localAabb[3], localAabb[4], localAabb[2]],
142
+ [localAabb[0], localAabb[1], localAabb[5]],
143
+ [localAabb[3], localAabb[1], localAabb[5]],
144
+ [localAabb[0], localAabb[4], localAabb[5]],
145
+ [localAabb[3], localAabb[4], localAabb[5]]
146
+ ];
147
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
148
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
149
+ const tmpP = vec3.create();
150
+ for (const c of corners) {
151
+ mat4.transformPoint(tmpP, worldMat, c);
152
+ const cx = tmpP[0];
153
+ const cy = tmpP[1];
154
+ const cz = tmpP[2];
155
+ if (cx < minX) minX = cx;
156
+ if (cy < minY) minY = cy;
157
+ if (cz < minZ) minZ = cz;
158
+ if (cx > maxX) maxX = cx;
159
+ if (cy > maxY) maxY = cy;
160
+ if (cz > maxZ) maxZ = cz;
161
+ }
162
+ const worldAabb = new Float32Array(6);
163
+ worldAabb[0] = minX;
164
+ worldAabb[1] = minY;
165
+ worldAabb[2] = minZ;
166
+ worldAabb[3] = maxX;
167
+ worldAabb[4] = maxY;
168
+ worldAabb[5] = maxZ;
169
+ const aabbResult = ray.rayAabbIntersects(
170
+ r,
171
+ worldAabb
172
+ );
173
+ return aabbResult.hit;
174
+ }
175
+ function narrowPosition(positionAttr) {
176
+ if (positionAttr instanceof Float32Array) {
177
+ return positionAttr;
178
+ }
179
+ if (positionAttr instanceof ArrayBuffer) {
180
+ return new Float32Array(positionAttr);
181
+ }
182
+ return void 0;
183
+ }
184
+ function collectVertexHits(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, entity, screenRayOverride) {
185
+ const screenRay = screenRayOverride ?? (void 0 );
186
+ if (screenRay === void 0) {
187
+ return [];
188
+ }
189
+ const { ray: r, view, proj } = screenRay;
190
+ const viewProj = mat4.create();
191
+ mat4.multiply(viewProj, proj, view);
192
+ const rOx = r[0];
193
+ const rOy = r[1];
194
+ const rOz = r[2];
195
+ const rDx = r[3];
196
+ const rDy = r[4];
197
+ const rDz = r[5];
198
+ const mfRes = world.get(entity, MeshFilter);
199
+ if (!mfRes.ok) {
200
+ return [];
201
+ }
202
+ const meshRes = resolveAssetHandle(
203
+ world,
204
+ toShared(mfRes.value.assetHandle)
205
+ );
206
+ if (!meshRes.ok) {
207
+ return [];
208
+ }
209
+ const mesh = meshRes.value;
210
+ const positions = narrowPosition(mesh.attributes.position);
211
+ if (positions === void 0 || positions.length < 3) {
212
+ return [];
213
+ }
214
+ const entityWorld = readWorldMatrix(world, entity);
215
+ if (entityWorld === void 0) {
216
+ return [];
217
+ }
218
+ const attrs = mesh.attributes;
219
+ const deformed = attrs !== void 0 && attrs.skinIndex !== void 0 && attrs.skinWeight !== void 0;
220
+ const entityWMLike = entityWorld;
221
+ if (mesh.aabb !== void 0 && !rayHitsWorldAabb(r, mesh.aabb, entityWMLike)) {
222
+ return [];
223
+ }
224
+ const candidates = [];
225
+ const seen = /* @__PURE__ */ new Set();
226
+ const indices = mesh.indices;
227
+ const submeshes = mesh.submeshes;
228
+ const maxVertexIndex = Math.floor(positions.length / 3) - 1;
229
+ const emitTriangleVertices = (i0, i1, i2) => {
230
+ const ax = positions[i0 * 3 + 0];
231
+ const ay = positions[i0 * 3 + 1];
232
+ const az = positions[i0 * 3 + 2];
233
+ const bx = positions[i1 * 3 + 0];
234
+ const by = positions[i1 * 3 + 1];
235
+ const bz = positions[i1 * 3 + 2];
236
+ const cx = positions[i2 * 3 + 0];
237
+ const cy = positions[i2 * 3 + 1];
238
+ const cz = positions[i2 * 3 + 2];
239
+ const worldA = vec3.create();
240
+ const worldB = vec3.create();
241
+ const worldC = vec3.create();
242
+ mat4.transformPoint(worldA, entityWMLike, [ax, ay, az]);
243
+ mat4.transformPoint(worldB, entityWMLike, [bx, by, bz]);
244
+ mat4.transformPoint(worldC, entityWMLike, [cx, cy, cz]);
245
+ const triResult = ray.rayTriangleIntersects(r, worldA, worldB, worldC);
246
+ if (!triResult.hit) return;
247
+ for (const [vi, lx, ly, lz] of [
248
+ [i0, ax, ay, az],
249
+ [i1, bx, by, bz],
250
+ [i2, cx, cy, cz]
251
+ ]) {
252
+ if (Number.isNaN(lx) || Number.isNaN(ly) || Number.isNaN(lz)) continue;
253
+ if (!Number.isFinite(lx) || !Number.isFinite(ly) || !Number.isFinite(lz)) continue;
254
+ const worldVec = vi === i0 ? worldA : vi === i1 ? worldB : worldC;
255
+ const wx = worldVec[0];
256
+ const wy = worldVec[1];
257
+ const wz = worldVec[2];
258
+ const screenRes = ray.worldToScreen(
259
+ _scratchVec2,
260
+ [wx, wy, wz],
261
+ viewProj,
262
+ viewportWidth,
263
+ viewportHeight
264
+ );
265
+ if (screenRes.behind) continue;
266
+ if (seen.has(vi)) continue;
267
+ seen.add(vi);
268
+ const sx = _scratchVec2[0];
269
+ const sy = _scratchVec2[1];
270
+ const sdx = sx - screenX;
271
+ const sdy = sy - screenY;
272
+ const screenDist = Math.sqrt(sdx * sdx + sdy * sdy);
273
+ const worldDist = pointToRayDist(wx, wy, wz, rOx, rOy, rOz, rDx, rDy, rDz);
274
+ candidates.push({
275
+ entity,
276
+ vertexIndex: vi,
277
+ worldPos: [wx, wy, wz],
278
+ screenDist,
279
+ worldDist,
280
+ deformed
281
+ });
282
+ }
283
+ };
284
+ for (const submesh of submeshes) {
285
+ if (submesh.topology !== "triangle-list") continue;
286
+ const idxOffset = submesh.indexOffset;
287
+ const idxCount = submesh.indexCount;
288
+ if (indices !== void 0 && indices.length > 0 && idxCount > 0) {
289
+ const triCount = Math.floor(idxCount / 3);
290
+ for (let ti = 0; ti < triCount; ti++) {
291
+ const i0 = indices[idxOffset + ti * 3 + 0];
292
+ const i1 = indices[idxOffset + ti * 3 + 1];
293
+ const i2 = indices[idxOffset + ti * 3 + 2];
294
+ if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;
295
+ emitTriangleVertices(i0, i1, i2);
296
+ }
297
+ } else {
298
+ const submeshVertexCount = submesh.vertexCount;
299
+ const triCount = Math.floor(submeshVertexCount / 3);
300
+ for (let ti = 0; ti < triCount; ti++) {
301
+ const i0 = ti * 3;
302
+ const i1 = ti * 3 + 1;
303
+ const i2 = ti * 3 + 2;
304
+ if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;
305
+ emitTriangleVertices(i0, i1, i2);
306
+ }
307
+ }
308
+ }
309
+ return candidates;
310
+ }
311
+ function pickVertexOnEntity(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, entity, options) {
312
+ return pickVertexOnEntityWithScreenRay(
313
+ world,
314
+ computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight),
315
+ screenX,
316
+ screenY,
317
+ viewportWidth,
318
+ viewportHeight,
319
+ entity,
320
+ options
321
+ );
322
+ }
323
+ function pickVertexOnEntityWithScreenRay(world, screenRay, screenX, screenY, viewportWidth, viewportHeight, entity, options) {
324
+ if (screenRay === void 0) return options === void 0 ? void 0 : [];
325
+ const candidates = collectVertexHits(
326
+ world,
327
+ void 0,
328
+ screenX,
329
+ screenY,
330
+ viewportWidth,
331
+ viewportHeight,
332
+ entity,
333
+ screenRay
334
+ );
335
+ candidates.sort((a, b) => a.screenDist - b.screenDist);
336
+ const limit = options?.limit;
337
+ if (limit !== void 0) {
338
+ return candidates.slice(0, limit);
339
+ }
340
+ return candidates[0];
341
+ }
342
+ function pickVertex(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, options) {
343
+ const screenRay = computeScreenRay(
344
+ world,
345
+ cameraEntity,
346
+ screenX,
347
+ screenY,
348
+ viewportWidth,
349
+ viewportHeight
350
+ );
351
+ return pickVertexFromScreenRay(
352
+ world,
353
+ screenRay,
354
+ screenX,
355
+ screenY,
356
+ viewportWidth,
357
+ viewportHeight,
358
+ options
359
+ );
360
+ }
361
+ function pickVertexWithScreenRay(world, screenRay, screenX, screenY, viewportWidth, viewportHeight, options) {
362
+ return pickVertexFromScreenRay(
363
+ world,
364
+ screenRay,
365
+ screenX,
366
+ screenY,
367
+ viewportWidth,
368
+ viewportHeight,
369
+ options
370
+ );
371
+ }
372
+ function pickVertexFromScreenRay(world, screenRay, screenX, screenY, viewportWidth, viewportHeight, options) {
373
+ if (screenRay === void 0) {
374
+ if (options) return [];
375
+ return void 0;
376
+ }
377
+ const r = screenRay.ray;
378
+ const query = world.query({ read: [Transform, GlobalTransform, MeshFilter, MeshRenderer] }).unwrap();
379
+ const allCandidates = [];
380
+ for (const row of query) {
381
+ const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle);
382
+ if (assetHandleRaw === 0) continue;
383
+ const meshRes = resolveAssetHandle(world, toShared(assetHandleRaw));
384
+ if (!meshRes.ok) continue;
385
+ const entity = row.entity;
386
+ const entityWorld = readWorldMatrix(world, entity);
387
+ if (entityWorld === void 0) continue;
388
+ const mesh = meshRes.value;
389
+ if (mesh.aabb !== void 0 && !rayHitsWorldAabb(r, mesh.aabb, entityWorld)) {
390
+ continue;
391
+ }
392
+ const entityHits = collectVertexHits(
393
+ world,
394
+ void 0,
395
+ screenX,
396
+ screenY,
397
+ viewportWidth,
398
+ viewportHeight,
399
+ entity,
400
+ screenRay
401
+ );
402
+ for (const h of entityHits) {
403
+ allCandidates.push(h);
404
+ }
405
+ }
406
+ allCandidates.sort((a, b) => a.screenDist - b.screenDist);
407
+ const limit = options?.limit;
408
+ if (limit !== void 0) {
409
+ return allCandidates.slice(0, limit);
410
+ }
411
+ return allCandidates[0];
412
+ }
413
+
414
+ // src/display-picking.ts
415
+ function displayToScenePixel(out, mapping, displayX, displayY) {
416
+ if (mapping === void 0) return false;
417
+ return mapDisplayToScene(out, mapping, displayX, displayY);
418
+ }
419
+ function submittedExtent(mapping, viewportWidth, viewportHeight) {
420
+ if (mapping === void 0) return void 0;
421
+ if (!Number.isFinite(mapping.width) || !Number.isFinite(mapping.height) || mapping.width <= 0 || mapping.height <= 0 || !Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight) || viewportWidth !== mapping.width || viewportHeight !== mapping.height) {
422
+ return void 0;
423
+ }
424
+ return { width: mapping.width, height: mapping.height };
425
+ }
426
+ function computeDisplayScreenRay(world, cameraEntity, displayX, displayY, mapping, viewportWidth, viewportHeight) {
427
+ const extent = submittedExtent(mapping, viewportWidth, viewportHeight);
428
+ if (extent === void 0 || !Number.isFinite(displayX) || !Number.isFinite(displayY) || displayX < 0 || displayX > extent.width || displayY < 0 || displayY > extent.height) {
429
+ return void 0;
430
+ }
431
+ const scene = { x: 0, y: 0 };
432
+ if (!displayToScenePixel(scene, mapping, displayX, displayY)) return void 0;
433
+ return computeSceneScreenRay(scene.x, scene.y, mapping);
434
+ }
435
+ function computeSceneScreenRay(sceneX, sceneY, mapping) {
436
+ if (mapping === void 0 || mapping.camera === void 0) return void 0;
437
+ const camera = mapping.camera;
438
+ const submitted = mapping;
439
+ return computeScreenRayFromMatrices(
440
+ sceneX,
441
+ sceneY,
442
+ submitted.width,
443
+ submitted.height,
444
+ camera.viewMatrix,
445
+ camera.projectionMatrix,
446
+ camera.projection
447
+ );
448
+ }
449
+ function pickDisplay(world, _cameraEntity, displayX, displayY, mapping, viewportWidth, viewportHeight) {
450
+ const extent = submittedExtent(mapping, viewportWidth, viewportHeight);
451
+ if (extent === void 0 || !Number.isFinite(displayX) || !Number.isFinite(displayY) || displayX < 0 || displayX > extent.width || displayY < 0 || displayY > extent.height) {
452
+ return void 0;
453
+ }
454
+ const scene = { x: 0, y: 0 };
455
+ if (!displayToScenePixel(scene, mapping, displayX, displayY)) return void 0;
456
+ const screenRay = computeSceneScreenRay(scene.x, scene.y, mapping);
457
+ if (screenRay === void 0) return void 0;
458
+ return pickWithScreenRay(world, screenRay);
459
+ }
460
+ function remapVertexHits(hits, displayX, displayY, mapping, screenRay, viewportWidth, viewportHeight, radius) {
461
+ const viewProj = mat4.create();
462
+ mat4.multiply(viewProj, screenRay.proj, screenRay.view);
463
+ const scenePoint = vec2.create();
464
+ const displayPoint = { x: 0, y: 0 };
465
+ const remapped = [];
466
+ for (const hit of hits) {
467
+ const projected = ray.worldToScreen(
468
+ scenePoint,
469
+ hit.worldPos,
470
+ viewProj,
471
+ viewportWidth,
472
+ viewportHeight
473
+ );
474
+ if (projected.behind) continue;
475
+ if (mapping === void 0 || !mapSceneToDisplay(displayPoint, mapping, scenePoint[0], scenePoint[1]))
476
+ continue;
477
+ const dx = displayPoint.x - displayX;
478
+ const dy = displayPoint.y - displayY;
479
+ const screenDist = Math.hypot(dx, dy);
480
+ if (radius !== void 0 && (!Number.isFinite(radius) || screenDist > radius)) continue;
481
+ remapped.push({ ...hit, screenDist });
482
+ }
483
+ remapped.sort((left, right) => left.screenDist - right.screenDist);
484
+ return remapped;
485
+ }
486
+ function displayVertexQuery(displayX, displayY, mapping, viewportWidth, viewportHeight) {
487
+ const extent = submittedExtent(mapping, viewportWidth, viewportHeight);
488
+ if (extent === void 0 || !Number.isFinite(displayX) || !Number.isFinite(displayY) || displayX < 0 || displayX > extent.width || displayY < 0 || displayY > extent.height) {
489
+ return void 0;
490
+ }
491
+ const scene = { x: 0, y: 0 };
492
+ if (!displayToScenePixel(scene, mapping, displayX, displayY)) return void 0;
493
+ return scene;
494
+ }
495
+ function pickVertexDisplay(world, _cameraEntity, displayX, displayY, mapping, viewportWidth, viewportHeight, options) {
496
+ const scene = displayVertexQuery(displayX, displayY, mapping, viewportWidth, viewportHeight);
497
+ if (scene === void 0) return options === void 0 ? void 0 : [];
498
+ const screenRay = computeSceneScreenRay(scene.x, scene.y, mapping);
499
+ if (screenRay === void 0) return options === void 0 ? void 0 : [];
500
+ const extent = mapping === void 0 ? void 0 : { width: mapping.width, height: mapping.height };
501
+ if (extent === void 0) return options === void 0 ? void 0 : [];
502
+ const hitResult = pickVertexWithScreenRay(
503
+ world,
504
+ screenRay,
505
+ scene.x,
506
+ scene.y,
507
+ extent.width,
508
+ extent.height,
509
+ {
510
+ limit: Number.MAX_SAFE_INTEGER
511
+ }
512
+ );
513
+ const hits = Array.isArray(hitResult) ? hitResult : [];
514
+ const remapped = remapVertexHits(
515
+ hits,
516
+ displayX,
517
+ displayY,
518
+ mapping,
519
+ screenRay,
520
+ extent.width,
521
+ extent.height,
522
+ options?.radius
523
+ );
524
+ return options === void 0 ? remapped[0] : options.limit === void 0 ? remapped : remapped.slice(0, options.limit);
525
+ }
526
+ function pickVertexOnEntityDisplay(world, _cameraEntity, displayX, displayY, mapping, viewportWidth, viewportHeight, entity, options) {
527
+ const scene = displayVertexQuery(displayX, displayY, mapping, viewportWidth, viewportHeight);
528
+ if (scene === void 0) return options === void 0 ? void 0 : [];
529
+ const screenRay = computeSceneScreenRay(scene.x, scene.y, mapping);
530
+ if (screenRay === void 0) return options === void 0 ? void 0 : [];
531
+ const extent = mapping === void 0 ? void 0 : { width: mapping.width, height: mapping.height };
532
+ if (extent === void 0) return options === void 0 ? void 0 : [];
533
+ const hitResult = pickVertexOnEntityWithScreenRay(
534
+ world,
535
+ screenRay,
536
+ scene.x,
537
+ scene.y,
538
+ extent.width,
539
+ extent.height,
540
+ entity,
541
+ { limit: Number.MAX_SAFE_INTEGER }
542
+ );
543
+ const hits = Array.isArray(hitResult) ? hitResult : [];
544
+ const remapped = remapVertexHits(
545
+ hits,
546
+ displayX,
547
+ displayY,
548
+ mapping,
549
+ screenRay,
550
+ extent.width,
551
+ extent.height,
552
+ options?.radius
553
+ );
554
+ return options === void 0 ? remapped[0] : options.limit === void 0 ? remapped : remapped.slice(0, options.limit);
555
+ }
110
556
  function pickTile(world, tilemapEntity, worldX, worldY) {
111
557
  const entityResult = world.get(tilemapEntity, Entity);
112
558
  if (!entityResult.ok) {
@@ -171,7 +617,7 @@ function pickTile(world, tilemapEntity, worldX, worldY) {
171
617
  }
172
618
  return ok(null);
173
619
  }
174
- function narrowPosition(position) {
620
+ function narrowPosition2(position) {
175
621
  if (position instanceof Float32Array) return position;
176
622
  if (position instanceof ArrayBuffer) return new Float32Array(position);
177
623
  return void 0;
@@ -243,21 +689,15 @@ function pickTriangle(world, cameraEntity, screenX, screenY, viewportWidth, view
243
689
  let instanceTransforms;
244
690
  let instanceCount = 1;
245
691
  if (instancesData !== void 0) {
246
- if (options.instances === void 0) {
692
+ instanceTransforms = instancesData.transforms;
693
+ if (instanceTransforms.length % 16 !== 0 || !instanceTransforms.every(Number.isFinite)) {
247
694
  unsupportedReason ??= "instance-transforms-unavailable";
248
695
  unsupportedEntities.push(entity);
249
696
  continue;
250
697
  }
251
- const snapshot = options.instances.peek(instancesData.collectionId);
252
- if (!snapshot.ok) {
253
- unsupportedReason ??= "instance-transforms-unavailable";
254
- unsupportedEntities.push(entity);
255
- continue;
256
- }
257
- instanceTransforms = snapshot.value.transforms;
258
- instanceCount = snapshot.value.count;
698
+ instanceCount = instanceTransforms.length / 16;
259
699
  }
260
- const positions = narrowPosition(mesh.attributes.position);
700
+ const positions = narrowPosition2(mesh.attributes.position);
261
701
  const skinned = mesh.attributes.skinIndex !== void 0 && mesh.attributes.skinWeight !== void 0;
262
702
  if (skinned) {
263
703
  let intersects = false;
@@ -403,270 +843,12 @@ function pickTriangle(world, cameraEntity, screenX, screenY, viewportWidth, view
403
843
  }
404
844
  return best === void 0 ? { status: "miss", precision: "triangle" } : { status: "hit", hit: best };
405
845
  }
406
- var _scratchVec2 = vec2.create();
407
- function pointToRayDist(px, py, pz, ox, oy, oz, dx, dy, dz) {
408
- const ex = px - ox;
409
- const ey = py - oy;
410
- const ez = pz - oz;
411
- const cx = ey * dz - ez * dy;
412
- const cy = ez * dx - ex * dz;
413
- const cz = ex * dy - ey * dx;
414
- return Math.sqrt(cx * cx + cy * cy + cz * cz);
415
- }
416
- function rayHitsWorldAabb(r, localAabb, worldMat) {
417
- if (localAabb[0] > localAabb[3]) return true;
418
- const corners = [
419
- [localAabb[0], localAabb[1], localAabb[2]],
420
- [localAabb[3], localAabb[1], localAabb[2]],
421
- [localAabb[0], localAabb[4], localAabb[2]],
422
- [localAabb[3], localAabb[4], localAabb[2]],
423
- [localAabb[0], localAabb[1], localAabb[5]],
424
- [localAabb[3], localAabb[1], localAabb[5]],
425
- [localAabb[0], localAabb[4], localAabb[5]],
426
- [localAabb[3], localAabb[4], localAabb[5]]
427
- ];
428
- let minX = Infinity, minY = Infinity, minZ = Infinity;
429
- let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
430
- const tmpP = vec3.create();
431
- for (const c of corners) {
432
- mat4.transformPoint(tmpP, worldMat, c);
433
- const cx = tmpP[0];
434
- const cy = tmpP[1];
435
- const cz = tmpP[2];
436
- if (cx < minX) minX = cx;
437
- if (cy < minY) minY = cy;
438
- if (cz < minZ) minZ = cz;
439
- if (cx > maxX) maxX = cx;
440
- if (cy > maxY) maxY = cy;
441
- if (cz > maxZ) maxZ = cz;
442
- }
443
- const worldAabb = new Float32Array(6);
444
- worldAabb[0] = minX;
445
- worldAabb[1] = minY;
446
- worldAabb[2] = minZ;
447
- worldAabb[3] = maxX;
448
- worldAabb[4] = maxY;
449
- worldAabb[5] = maxZ;
450
- const aabbResult = ray.rayAabbIntersects(
451
- r,
452
- worldAabb
453
- );
454
- return aabbResult.hit;
455
- }
456
- function narrowPosition2(positionAttr) {
457
- if (positionAttr instanceof Float32Array) {
458
- return positionAttr;
459
- }
460
- if (positionAttr instanceof ArrayBuffer) {
461
- return new Float32Array(positionAttr);
462
- }
463
- return void 0;
464
- }
465
- function collectVertexHits(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, entity) {
466
- const screenRay = computeScreenRay(
467
- world,
468
- cameraEntity,
469
- screenX,
470
- screenY,
471
- viewportWidth,
472
- viewportHeight
473
- );
474
- if (screenRay === void 0) {
475
- return [];
476
- }
477
- const { ray: r, view, proj } = screenRay;
478
- const viewProj = mat4.create();
479
- mat4.multiply(viewProj, proj, view);
480
- const rOx = r[0];
481
- const rOy = r[1];
482
- const rOz = r[2];
483
- const rDx = r[3];
484
- const rDy = r[4];
485
- const rDz = r[5];
486
- const mfRes = world.get(entity, MeshFilter);
487
- if (!mfRes.ok) {
488
- return [];
489
- }
490
- const meshRes = resolveAssetHandle(
491
- world,
492
- toShared(mfRes.value.assetHandle)
493
- );
494
- if (!meshRes.ok) {
495
- return [];
496
- }
497
- const mesh = meshRes.value;
498
- const positions = narrowPosition2(mesh.attributes.position);
499
- if (positions === void 0 || positions.length < 3) {
500
- return [];
501
- }
502
- const entityWorld = readWorldMatrix(world, entity);
503
- if (entityWorld === void 0) {
504
- return [];
505
- }
506
- const attrs = mesh.attributes;
507
- const deformed = attrs !== void 0 && attrs.skinIndex !== void 0 && attrs.skinWeight !== void 0;
508
- const entityWMLike = entityWorld;
509
- if (mesh.aabb !== void 0 && !rayHitsWorldAabb(r, mesh.aabb, entityWMLike)) {
510
- return [];
511
- }
512
- const candidates = [];
513
- const seen = /* @__PURE__ */ new Set();
514
- const indices = mesh.indices;
515
- const submeshes = mesh.submeshes;
516
- const maxVertexIndex = Math.floor(positions.length / 3) - 1;
517
- const emitTriangleVertices = (i0, i1, i2) => {
518
- const ax = positions[i0 * 3 + 0];
519
- const ay = positions[i0 * 3 + 1];
520
- const az = positions[i0 * 3 + 2];
521
- const bx = positions[i1 * 3 + 0];
522
- const by = positions[i1 * 3 + 1];
523
- const bz = positions[i1 * 3 + 2];
524
- const cx = positions[i2 * 3 + 0];
525
- const cy = positions[i2 * 3 + 1];
526
- const cz = positions[i2 * 3 + 2];
527
- const worldA = vec3.create();
528
- const worldB = vec3.create();
529
- const worldC = vec3.create();
530
- mat4.transformPoint(worldA, entityWMLike, [ax, ay, az]);
531
- mat4.transformPoint(worldB, entityWMLike, [bx, by, bz]);
532
- mat4.transformPoint(worldC, entityWMLike, [cx, cy, cz]);
533
- const triResult = ray.rayTriangleIntersects(r, worldA, worldB, worldC);
534
- if (!triResult.hit) return;
535
- for (const [vi, lx, ly, lz] of [
536
- [i0, ax, ay, az],
537
- [i1, bx, by, bz],
538
- [i2, cx, cy, cz]
539
- ]) {
540
- if (Number.isNaN(lx) || Number.isNaN(ly) || Number.isNaN(lz)) continue;
541
- if (!Number.isFinite(lx) || !Number.isFinite(ly) || !Number.isFinite(lz)) continue;
542
- const worldVec = vi === i0 ? worldA : vi === i1 ? worldB : worldC;
543
- const wx = worldVec[0];
544
- const wy = worldVec[1];
545
- const wz = worldVec[2];
546
- const screenRes = ray.worldToScreen(
547
- _scratchVec2,
548
- [wx, wy, wz],
549
- viewProj,
550
- viewportWidth,
551
- viewportHeight
552
- );
553
- if (screenRes.behind) continue;
554
- if (seen.has(vi)) continue;
555
- seen.add(vi);
556
- const sx = _scratchVec2[0];
557
- const sy = _scratchVec2[1];
558
- const sdx = sx - screenX;
559
- const sdy = sy - screenY;
560
- const screenDist = Math.sqrt(sdx * sdx + sdy * sdy);
561
- const worldDist = pointToRayDist(wx, wy, wz, rOx, rOy, rOz, rDx, rDy, rDz);
562
- candidates.push({
563
- entity,
564
- vertexIndex: vi,
565
- worldPos: [wx, wy, wz],
566
- screenDist,
567
- worldDist,
568
- deformed
569
- });
570
- }
571
- };
572
- for (const submesh of submeshes) {
573
- if (submesh.topology !== "triangle-list") continue;
574
- const idxOffset = submesh.indexOffset;
575
- const idxCount = submesh.indexCount;
576
- if (indices !== void 0 && indices.length > 0 && idxCount > 0) {
577
- const triCount = Math.floor(idxCount / 3);
578
- for (let ti = 0; ti < triCount; ti++) {
579
- const i0 = indices[idxOffset + ti * 3 + 0];
580
- const i1 = indices[idxOffset + ti * 3 + 1];
581
- const i2 = indices[idxOffset + ti * 3 + 2];
582
- if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;
583
- emitTriangleVertices(i0, i1, i2);
584
- }
585
- } else {
586
- const submeshVertexCount = submesh.vertexCount;
587
- const triCount = Math.floor(submeshVertexCount / 3);
588
- for (let ti = 0; ti < triCount; ti++) {
589
- const i0 = ti * 3;
590
- const i1 = ti * 3 + 1;
591
- const i2 = ti * 3 + 2;
592
- if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;
593
- emitTriangleVertices(i0, i1, i2);
594
- }
595
- }
596
- }
597
- return candidates;
598
- }
599
- function pickVertexOnEntity(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, entity, options) {
600
- const candidates = collectVertexHits(
601
- world,
602
- cameraEntity,
603
- screenX,
604
- screenY,
605
- viewportWidth,
606
- viewportHeight,
607
- entity
608
- );
609
- candidates.sort((a, b) => a.screenDist - b.screenDist);
610
- const limit = options?.limit;
611
- if (limit !== void 0) {
612
- return candidates.slice(0, limit);
613
- }
614
- return candidates[0];
615
- }
616
- function pickVertex(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, options) {
617
- const screenRay = computeScreenRay(
618
- world,
619
- cameraEntity,
620
- screenX,
621
- screenY,
622
- viewportWidth,
623
- viewportHeight
624
- );
625
- if (screenRay === void 0) {
626
- if (options) return [];
627
- return void 0;
628
- }
629
- const r = screenRay.ray;
630
- const query = world.query({ read: [Transform, GlobalTransform, MeshFilter, MeshRenderer] }).unwrap();
631
- const allCandidates = [];
632
- for (const row of query) {
633
- const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle);
634
- if (assetHandleRaw === 0) continue;
635
- const meshRes = resolveAssetHandle(world, toShared(assetHandleRaw));
636
- if (!meshRes.ok) continue;
637
- const entity = row.entity;
638
- const entityWorld = readWorldMatrix(world, entity);
639
- if (entityWorld === void 0) continue;
640
- const mesh = meshRes.value;
641
- if (mesh.aabb !== void 0 && !rayHitsWorldAabb(r, mesh.aabb, entityWorld)) {
642
- continue;
643
- }
644
- const entityHits = collectVertexHits(
645
- world,
646
- cameraEntity,
647
- screenX,
648
- screenY,
649
- viewportWidth,
650
- viewportHeight,
651
- entity
652
- );
653
- for (const h of entityHits) {
654
- allCandidates.push(h);
655
- }
656
- }
657
- allCandidates.sort((a, b) => a.screenDist - b.screenDist);
658
- const limit = options?.limit;
659
- if (limit !== void 0) {
660
- return allCandidates.slice(0, limit);
661
- }
662
- return allCandidates[0];
663
- }
664
846
 
665
847
  // src/viewport-to-world.ts
666
848
  function viewportToWorld(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight) {
667
849
  return computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)?.ray;
668
850
  }
669
851
 
670
- export { PickError, pick, pickTile, pickTriangle, pickVertex, pickVertexOnEntity, viewportToWorld };
852
+ export { PickError, computeDisplayScreenRay, displayToScenePixel, pick, pickDisplay, pickTile, pickTriangle, pickVertex, pickVertexDisplay, pickVertexOnEntity, pickVertexOnEntityDisplay, viewportToWorld };
671
853
  //# sourceMappingURL=index.mjs.map
672
854
  //# sourceMappingURL=index.mjs.map