@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.
- 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/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/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|