@forgeax/engine-picking 0.1.31 → 0.1.33
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 +26 -3
- package/dist/__tests__/pick-triangle.unit.test.d.ts +2 -0
- package/dist/__tests__/pick-triangle.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +244 -12
- package/dist/index.mjs.map +1 -1
- package/dist/pick-triangle.d.ts +49 -0
- package/dist/pick-triangle.d.ts.map +1 -0
- package/dist/pick-vertex.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/__tests__/pick-triangle.unit.test.ts +292 -0
- package/src/index.ts +7 -0
- package/src/pick-triangle.ts +347 -0
- package/src/pick-vertex.ts +11 -8
package/README.md
CHANGED
|
@@ -15,11 +15,16 @@ never the reverse.
|
|
|
15
15
|
through the camera, walks every renderable archetype, ray-AABB tests each
|
|
16
16
|
pickable mesh's world-space bounding box, and returns the nearest
|
|
17
17
|
`PickHit { entity, point, distance }` (or `undefined` on a miss). AABB
|
|
18
|
-
granularity
|
|
19
|
-
|
|
18
|
+
granularity is intentional; use `pickTriangle` when exact surface and
|
|
19
|
+
occlusion ordering are required.
|
|
20
20
|
- **`viewportToWorld(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)`**
|
|
21
21
|
— exposes the same camera unprojection as a world-space `Ray` for cursor
|
|
22
22
|
placement, gizmos, and custom plane/triangle queries.
|
|
23
|
+
- **`pickTriangle`** — exact static CPU triangle query for a screen ray. It
|
|
24
|
+
transforms indexed or non-indexed triangle-list vertices into world space,
|
|
25
|
+
returns the nearest world point, distance, barycentric weights, entity,
|
|
26
|
+
triangle index, and optional Catalog GUID, or reports `unavailable` when
|
|
27
|
+
CPU geometry or the current skinned pose cannot be tested.
|
|
23
28
|
- **`pickVertexOnEntity` / `pickVertex`** — per-triangle vertex-level queries
|
|
24
29
|
(editor vertex-snapping workflow). Three-state static-dispatch overload:
|
|
25
30
|
without options -> `VertexHit | undefined`; with `{ limit: N }` -> `VertexHit[]`
|
|
@@ -74,7 +79,7 @@ if (hit) {
|
|
|
74
79
|
|
|
75
80
|
`PickHit = { entity: EntityHandle; point: Vec3Like; distance: number }`. No
|
|
76
81
|
`face` / `uv` / `normal` — AABB picking has no triangle resolution, so those
|
|
77
|
-
would be a lie (use `pickVertex` for
|
|
82
|
+
would be a lie (use `pickTriangle` or `pickVertex` for precise geometry). Both `perspective`
|
|
78
83
|
and `orthographic` camera projections are supported. Reads the resolved
|
|
79
84
|
`GlobalTransform.world` mat4 directly (feat-20260601 D-3), so the camera + candidates
|
|
80
85
|
must have propagated transforms for the current frame.
|
|
@@ -117,6 +122,23 @@ Only `triangle-list` submeshes participate; skinned meshes report
|
|
|
117
122
|
> `GlobalTransform.world` column-major mat4 directly; they never re-propagate. The
|
|
118
123
|
> contract is identical across `pick` and `pickVertex*`.
|
|
119
124
|
|
|
125
|
+
### Exact triangle (`pickTriangle`)
|
|
126
|
+
|
|
127
|
+
| Function | Signature | Return |
|
|
128
|
+
|:--|:--|:--|
|
|
129
|
+
| `pickTriangle` | `(world, cameraEntity, screenX, screenY, vpW, vpH, options?)` | `TrianglePickResult` |
|
|
130
|
+
|
|
131
|
+
`TrianglePickResult` is a closed three-state result: `hit` contains the
|
|
132
|
+
nearest `TriangleHit`; `miss` means every candidate was tested and no triangle
|
|
133
|
+
intersects the ray; `unavailable` names intersecting entities whose CPU
|
|
134
|
+
geometry, current skinned pose, or explicit instance transforms cannot be
|
|
135
|
+
tested. For an entity carrying `Instances`, pass its renderer-owned
|
|
136
|
+
`InstanceCollectionStore` as `options.instances`; a hit then includes the
|
|
137
|
+
zero-based `instanceIndex`. The picker uses the store's non-consuming `peek`
|
|
138
|
+
read, so inspection cannot consume a later render upload range. Pass
|
|
139
|
+
`AssetRegistry.guidOf` as `assetGuidOf` when provenance is needed. The query
|
|
140
|
+
does not mutate the World or own an asset registry.
|
|
141
|
+
|
|
120
142
|
### Tile-cell (`pickTile`)
|
|
121
143
|
|
|
122
144
|
| Function | Signature | Return |
|
|
@@ -164,5 +186,6 @@ miss returns `undefined`).
|
|
|
164
186
|
- `src/pick.ts` — `pick` + `PickHit`
|
|
165
187
|
- `src/pick-vertex.ts` — `pickVertex` / `pickVertexOnEntity` + `VertexHit`
|
|
166
188
|
- `src/pick-tile.ts` — `pickTile` + `PickTileHit` / `PickTileError`
|
|
189
|
+
- `src/pick-triangle.ts` — exact triangle/occlusion query + unavailable state
|
|
167
190
|
- `src/pick-errors.ts` — `PickError` / `PickErrorCode` (error SSOT)
|
|
168
191
|
- `src/pick-core.ts` — shared camera->ray skeleton (internal)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-triangle.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pick-triangle.unit.test.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { pick } from './pick';
|
|
|
3
3
|
export type { PickErrorCode } from './pick-errors';
|
|
4
4
|
export { PickError } from './pick-errors';
|
|
5
5
|
export { type PickTileError, type PickTileHit, pickTile } from './pick-tile';
|
|
6
|
+
export { pickTriangle, type TriangleHit, type TrianglePickOptions, type TrianglePickResult, type TrianglePickUnavailableReason, } from './pick-triangle';
|
|
6
7
|
export type { VertexHit } from './pick-vertex';
|
|
7
8
|
export { pickVertex, pickVertexOnEntity } from './pick-vertex';
|
|
8
9
|
export { viewportToWorld } from './viewport-to-world';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,YAAY,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,YAAY,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EACL,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,GACnC,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
2
2
|
import { vec2, box3, ray, vec3, mat4 } from '@forgeax/engine-math';
|
|
3
|
-
import { MeshFilter, MeshRenderer, Camera, cameraProjectionFromF32 } from '@forgeax/engine-render';
|
|
3
|
+
import { MeshFilter, MeshRenderer, Instances, Camera, cameraProjectionFromF32 } from '@forgeax/engine-render';
|
|
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';
|
|
@@ -171,6 +171,238 @@ function pickTile(world, tilemapEntity, worldX, worldY) {
|
|
|
171
171
|
}
|
|
172
172
|
return ok(null);
|
|
173
173
|
}
|
|
174
|
+
function narrowPosition(position) {
|
|
175
|
+
if (position instanceof Float32Array) return position;
|
|
176
|
+
if (position instanceof ArrayBuffer) return new Float32Array(position);
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
function worldAabbHit(screenRay, aabb, worldMatrix) {
|
|
180
|
+
if (aabb === void 0 || aabb[0] > aabb[3]) return true;
|
|
181
|
+
const corners = [
|
|
182
|
+
[aabb[0], aabb[1], aabb[2]],
|
|
183
|
+
[aabb[3], aabb[1], aabb[2]],
|
|
184
|
+
[aabb[0], aabb[4], aabb[2]],
|
|
185
|
+
[aabb[3], aabb[4], aabb[2]],
|
|
186
|
+
[aabb[0], aabb[1], aabb[5]],
|
|
187
|
+
[aabb[3], aabb[1], aabb[5]],
|
|
188
|
+
[aabb[0], aabb[4], aabb[5]],
|
|
189
|
+
[aabb[3], aabb[4], aabb[5]]
|
|
190
|
+
];
|
|
191
|
+
const world = vec3.create();
|
|
192
|
+
let minX = Infinity;
|
|
193
|
+
let minY = Infinity;
|
|
194
|
+
let minZ = Infinity;
|
|
195
|
+
let maxX = -Infinity;
|
|
196
|
+
let maxY = -Infinity;
|
|
197
|
+
let maxZ = -Infinity;
|
|
198
|
+
for (const corner of corners) {
|
|
199
|
+
mat4.transformPoint(world, worldMatrix, corner);
|
|
200
|
+
minX = Math.min(minX, world[0]);
|
|
201
|
+
minY = Math.min(minY, world[1]);
|
|
202
|
+
minZ = Math.min(minZ, world[2]);
|
|
203
|
+
maxX = Math.max(maxX, world[0]);
|
|
204
|
+
maxY = Math.max(maxY, world[1]);
|
|
205
|
+
maxZ = Math.max(maxZ, world[2]);
|
|
206
|
+
}
|
|
207
|
+
return ray.rayAabbIntersects(screenRay, [minX, minY, minZ, maxX, maxY, maxZ]).hit;
|
|
208
|
+
}
|
|
209
|
+
function pointOnRay(screenRay, point) {
|
|
210
|
+
const dx = point[0] - screenRay[0];
|
|
211
|
+
const dy = point[1] - screenRay[1];
|
|
212
|
+
const dz = point[2] - screenRay[2];
|
|
213
|
+
return dx * screenRay[3] + dy * screenRay[4] + dz * screenRay[5];
|
|
214
|
+
}
|
|
215
|
+
function pickTriangle(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight, options = {}) {
|
|
216
|
+
const screen = computeScreenRay(
|
|
217
|
+
world,
|
|
218
|
+
cameraEntity,
|
|
219
|
+
screenX,
|
|
220
|
+
screenY,
|
|
221
|
+
viewportWidth,
|
|
222
|
+
viewportHeight
|
|
223
|
+
);
|
|
224
|
+
if (screen === void 0) return { status: "miss", precision: "triangle" };
|
|
225
|
+
const origin = screen.ray;
|
|
226
|
+
const query = world.query({
|
|
227
|
+
read: [Transform, GlobalTransform, MeshFilter, MeshRenderer],
|
|
228
|
+
optional: [Instances]
|
|
229
|
+
}).unwrap();
|
|
230
|
+
let best;
|
|
231
|
+
let unsupportedReason;
|
|
232
|
+
const unsupportedEntities = [];
|
|
233
|
+
for (const row of query) {
|
|
234
|
+
const rawHandle = Math.round(row.get(MeshFilter).assetHandle);
|
|
235
|
+
if (rawHandle === 0) continue;
|
|
236
|
+
const resolved = resolveAssetHandle(world, toShared(rawHandle));
|
|
237
|
+
if (!resolved.ok) continue;
|
|
238
|
+
const mesh = resolved.value;
|
|
239
|
+
const entity = row.entity;
|
|
240
|
+
const entityWorldMatrix = readWorldMatrix(world, entity);
|
|
241
|
+
if (entityWorldMatrix === void 0) continue;
|
|
242
|
+
const instancesData = row.get(Instances);
|
|
243
|
+
let instanceTransforms;
|
|
244
|
+
let instanceCount = 1;
|
|
245
|
+
if (instancesData !== void 0) {
|
|
246
|
+
if (options.instances === void 0) {
|
|
247
|
+
unsupportedReason ??= "instance-transforms-unavailable";
|
|
248
|
+
unsupportedEntities.push(entity);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
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;
|
|
259
|
+
}
|
|
260
|
+
const positions = narrowPosition(mesh.attributes.position);
|
|
261
|
+
const skinned = mesh.attributes.skinIndex !== void 0 && mesh.attributes.skinWeight !== void 0;
|
|
262
|
+
if (skinned) {
|
|
263
|
+
let intersects = false;
|
|
264
|
+
for (let instanceIndex = 0; instanceIndex < instanceCount; instanceIndex += 1) {
|
|
265
|
+
const drawWorld = instanceTransforms === void 0 ? entityWorldMatrix : mat4.multiply(
|
|
266
|
+
mat4.create(),
|
|
267
|
+
entityWorldMatrix,
|
|
268
|
+
instanceTransforms.subarray(instanceIndex * 16, instanceIndex * 16 + 16)
|
|
269
|
+
);
|
|
270
|
+
if (worldAabbHit(origin, mesh.aabb, drawWorld)) {
|
|
271
|
+
intersects = true;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!intersects) continue;
|
|
276
|
+
unsupportedReason ??= "skinned-pose-unavailable";
|
|
277
|
+
unsupportedEntities.push(entity);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (positions === void 0 || positions.length < 3) {
|
|
281
|
+
let intersects = false;
|
|
282
|
+
for (let instanceIndex = 0; instanceIndex < instanceCount; instanceIndex += 1) {
|
|
283
|
+
const drawWorld = instanceTransforms === void 0 ? entityWorldMatrix : mat4.multiply(
|
|
284
|
+
mat4.create(),
|
|
285
|
+
entityWorldMatrix,
|
|
286
|
+
instanceTransforms.subarray(instanceIndex * 16, instanceIndex * 16 + 16)
|
|
287
|
+
);
|
|
288
|
+
if (worldAabbHit(origin, mesh.aabb, drawWorld)) {
|
|
289
|
+
intersects = true;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!intersects) continue;
|
|
294
|
+
unsupportedReason ??= "cpu-geometry-unavailable";
|
|
295
|
+
unsupportedEntities.push(entity);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
for (let instanceIndex = 0; instanceIndex < instanceCount; instanceIndex += 1) {
|
|
299
|
+
const drawWorld = instanceTransforms === void 0 ? entityWorldMatrix : mat4.multiply(
|
|
300
|
+
mat4.create(),
|
|
301
|
+
entityWorldMatrix,
|
|
302
|
+
instanceTransforms.subarray(instanceIndex * 16, instanceIndex * 16 + 16)
|
|
303
|
+
);
|
|
304
|
+
if (!worldAabbHit(origin, mesh.aabb, drawWorld)) continue;
|
|
305
|
+
const worldA = vec3.create();
|
|
306
|
+
const worldB = vec3.create();
|
|
307
|
+
const worldC = vec3.create();
|
|
308
|
+
const worldPoint = vec3.create();
|
|
309
|
+
let triangleIndex = 0;
|
|
310
|
+
const indices = mesh.indices;
|
|
311
|
+
const maxVertex = Math.floor(positions.length / 3) - 1;
|
|
312
|
+
const test = (i0, i1, i2, index) => {
|
|
313
|
+
if (i0 < 0 || i1 < 0 || i2 < 0 || i0 > maxVertex || i1 > maxVertex || i2 > maxVertex)
|
|
314
|
+
return;
|
|
315
|
+
const a = [
|
|
316
|
+
positions[i0 * 3],
|
|
317
|
+
positions[i0 * 3 + 1],
|
|
318
|
+
positions[i0 * 3 + 2]
|
|
319
|
+
];
|
|
320
|
+
const b = [
|
|
321
|
+
positions[i1 * 3],
|
|
322
|
+
positions[i1 * 3 + 1],
|
|
323
|
+
positions[i1 * 3 + 2]
|
|
324
|
+
];
|
|
325
|
+
const c = [
|
|
326
|
+
positions[i2 * 3],
|
|
327
|
+
positions[i2 * 3 + 1],
|
|
328
|
+
positions[i2 * 3 + 2]
|
|
329
|
+
];
|
|
330
|
+
mat4.transformPoint(worldA, drawWorld, a);
|
|
331
|
+
mat4.transformPoint(worldB, drawWorld, b);
|
|
332
|
+
mat4.transformPoint(worldC, drawWorld, c);
|
|
333
|
+
const hit = ray.rayTriangleIntersects(origin, worldA, worldB, worldC);
|
|
334
|
+
if (!hit.hit) return;
|
|
335
|
+
worldPoint[0] = origin[0] + origin[3] * hit.t;
|
|
336
|
+
worldPoint[1] = origin[1] + origin[4] * hit.t;
|
|
337
|
+
worldPoint[2] = origin[2] + origin[5] * hit.t;
|
|
338
|
+
const distance = pointOnRay(origin, worldPoint);
|
|
339
|
+
if (!Number.isFinite(distance) || distance < 0 || best !== void 0 && distance >= best.distance)
|
|
340
|
+
return;
|
|
341
|
+
const assetGuid = options.assetGuidOf?.(mesh);
|
|
342
|
+
best = {
|
|
343
|
+
entity,
|
|
344
|
+
triangleIndex: index,
|
|
345
|
+
point: [worldPoint[0], worldPoint[1], worldPoint[2]],
|
|
346
|
+
distance,
|
|
347
|
+
barycentric: [1 - hit.u - hit.v, hit.u, hit.v],
|
|
348
|
+
...assetGuid === void 0 ? {} : { assetGuid },
|
|
349
|
+
...instanceTransforms === void 0 ? {} : { instanceIndex },
|
|
350
|
+
precision: "triangle"
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
for (const submesh of mesh.submeshes) {
|
|
354
|
+
if (submesh.topology !== "triangle-list" && submesh.topology !== "triangle-strip") continue;
|
|
355
|
+
const strip = submesh.topology === "triangle-strip";
|
|
356
|
+
if (indices !== void 0 && indices.length > 0 && submesh.indexCount > 0) {
|
|
357
|
+
const count = strip ? Math.max(0, submesh.indexCount - 2) : Math.floor(submesh.indexCount / 3);
|
|
358
|
+
for (let localTriangle = 0; localTriangle < count; localTriangle += 1) {
|
|
359
|
+
if (strip) {
|
|
360
|
+
const base = submesh.indexOffset + localTriangle;
|
|
361
|
+
const i0 = indices[base];
|
|
362
|
+
const i1 = indices[base + 1];
|
|
363
|
+
const i2 = indices[base + 2];
|
|
364
|
+
if ((localTriangle & 1) === 0) test(i0, i1, i2, triangleIndex);
|
|
365
|
+
else test(i2, i1, i0, triangleIndex);
|
|
366
|
+
} else {
|
|
367
|
+
const base = submesh.indexOffset + localTriangle * 3;
|
|
368
|
+
test(
|
|
369
|
+
indices[base],
|
|
370
|
+
indices[base + 1],
|
|
371
|
+
indices[base + 2],
|
|
372
|
+
triangleIndex
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
triangleIndex += 1;
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
const count = strip ? Math.max(0, submesh.vertexCount - 2) : Math.floor(submesh.vertexCount / 3);
|
|
379
|
+
for (let localTriangle = 0; localTriangle < count; localTriangle += 1) {
|
|
380
|
+
if (strip) {
|
|
381
|
+
const i0 = localTriangle;
|
|
382
|
+
const i1 = localTriangle + 1;
|
|
383
|
+
const i2 = localTriangle + 2;
|
|
384
|
+
if ((localTriangle & 1) === 0) test(i0, i1, i2, triangleIndex);
|
|
385
|
+
else test(i2, i1, i0, triangleIndex);
|
|
386
|
+
} else {
|
|
387
|
+
const base = localTriangle * 3;
|
|
388
|
+
test(base, base + 1, base + 2, triangleIndex);
|
|
389
|
+
}
|
|
390
|
+
triangleIndex += 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (unsupportedReason !== void 0) {
|
|
397
|
+
return {
|
|
398
|
+
status: "unavailable",
|
|
399
|
+
precision: "unavailable",
|
|
400
|
+
reason: unsupportedReason,
|
|
401
|
+
entities: unsupportedEntities
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return best === void 0 ? { status: "miss", precision: "triangle" } : { status: "hit", hit: best };
|
|
405
|
+
}
|
|
174
406
|
var _scratchVec2 = vec2.create();
|
|
175
407
|
function pointToRayDist(px, py, pz, ox, oy, oz, dx, dy, dz) {
|
|
176
408
|
const ex = px - ox;
|
|
@@ -221,7 +453,7 @@ function rayHitsWorldAabb(r, localAabb, worldMat) {
|
|
|
221
453
|
);
|
|
222
454
|
return aabbResult.hit;
|
|
223
455
|
}
|
|
224
|
-
function
|
|
456
|
+
function narrowPosition2(positionAttr) {
|
|
225
457
|
if (positionAttr instanceof Float32Array) {
|
|
226
458
|
return positionAttr;
|
|
227
459
|
}
|
|
@@ -263,7 +495,7 @@ function collectVertexHits(world, cameraEntity, screenX, screenY, viewportWidth,
|
|
|
263
495
|
return [];
|
|
264
496
|
}
|
|
265
497
|
const mesh = meshRes.value;
|
|
266
|
-
const positions =
|
|
498
|
+
const positions = narrowPosition2(mesh.attributes.position);
|
|
267
499
|
if (positions === void 0 || positions.length < 3) {
|
|
268
500
|
return [];
|
|
269
501
|
}
|
|
@@ -292,12 +524,13 @@ function collectVertexHits(world, cameraEntity, screenX, screenY, viewportWidth,
|
|
|
292
524
|
const cx = positions[i2 * 3 + 0];
|
|
293
525
|
const cy = positions[i2 * 3 + 1];
|
|
294
526
|
const cz = positions[i2 * 3 + 2];
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
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);
|
|
301
534
|
if (!triResult.hit) return;
|
|
302
535
|
for (const [vi, lx, ly, lz] of [
|
|
303
536
|
[i0, ax, ay, az],
|
|
@@ -306,8 +539,7 @@ function collectVertexHits(world, cameraEntity, screenX, screenY, viewportWidth,
|
|
|
306
539
|
]) {
|
|
307
540
|
if (Number.isNaN(lx) || Number.isNaN(ly) || Number.isNaN(lz)) continue;
|
|
308
541
|
if (!Number.isFinite(lx) || !Number.isFinite(ly) || !Number.isFinite(lz)) continue;
|
|
309
|
-
const worldVec =
|
|
310
|
-
mat4.transformPoint(worldVec, entityWMLike, [lx, ly, lz]);
|
|
542
|
+
const worldVec = vi === i0 ? worldA : vi === i1 ? worldB : worldC;
|
|
311
543
|
const wx = worldVec[0];
|
|
312
544
|
const wy = worldVec[1];
|
|
313
545
|
const wz = worldVec[2];
|
|
@@ -435,6 +667,6 @@ function viewportToWorld(world, cameraEntity, screenX, screenY, viewportWidth, v
|
|
|
435
667
|
return computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)?.ray;
|
|
436
668
|
}
|
|
437
669
|
|
|
438
|
-
export { PickError, pick, pickTile, pickVertex, pickVertexOnEntity, viewportToWorld };
|
|
670
|
+
export { PickError, pick, pickTile, pickTriangle, pickVertex, pickVertexOnEntity, viewportToWorld };
|
|
439
671
|
//# sourceMappingURL=index.mjs.map
|
|
440
672
|
//# sourceMappingURL=index.mjs.map
|