@flighthq/picking 0.1.0 → 0.2.0
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/pickScene.d.ts +11 -2
- package/dist/pickScene.d.ts.map +1 -1
- package/dist/pickScene.js +204 -44
- package/dist/pickScene.js.map +1 -1
- package/package.json +12 -7
- package/src/pickScene.test.ts +264 -14
package/dist/pickScene.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { Scene } from '@flighthq/scene';
|
|
2
|
-
import type { Camera, SceneHit } from '@flighthq/types';
|
|
3
|
-
export
|
|
2
|
+
import type { Camera, Mesh, Ray3D, SceneHit } from '@flighthq/types';
|
|
3
|
+
export interface ScenePickOptions {
|
|
4
|
+
cullBackfaces?: boolean;
|
|
5
|
+
maxDistance?: number;
|
|
6
|
+
predicate?: (mesh: Readonly<Mesh>) => boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function createSceneHit(): SceneHit;
|
|
9
|
+
export declare function pickScene(scene: Readonly<Scene>, camera: Readonly<Camera>, screenX: number, screenY: number, out: SceneHit, options?: Readonly<ScenePickOptions>): SceneHit | null;
|
|
10
|
+
export declare function pickSceneAll(scene: Readonly<Scene>, camera: Readonly<Camera>, screenX: number, screenY: number, outArray: SceneHit[], options?: Readonly<ScenePickOptions>): SceneHit[];
|
|
11
|
+
export declare function pickSceneAllWithRay3D(scene: Readonly<Scene>, ray: Readonly<Ray3D>, outArray: SceneHit[], options?: Readonly<ScenePickOptions>): SceneHit[];
|
|
12
|
+
export declare function pickSceneWithRay3D(scene: Readonly<Scene>, ray: Readonly<Ray3D>, out: SceneHit, options?: Readonly<ScenePickOptions>): SceneHit | null;
|
|
4
13
|
//# sourceMappingURL=pickScene.d.ts.map
|
package/dist/pickScene.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pickScene.d.ts","sourceRoot":"","sources":["../src/pickScene.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"pickScene.d.ts","sourceRoot":"","sources":["../src/pickScene.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAsB,MAAM,iBAAiB,CAAC;AASzF,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC;CAC/C;AAID,wBAAgB,cAAc,IAAI,QAAQ,CAezC;AAQD,wBAAgB,SAAS,CACvB,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,QAAQ,EACb,OAAO,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,QAAQ,GAAG,IAAI,CAGjB;AAKD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAAE,EACpB,OAAO,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,QAAQ,EAAE,CAMZ;AAMD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,EACpB,QAAQ,EAAE,QAAQ,EAAE,EACpB,OAAO,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,QAAQ,EAAE,CAcZ;AAcD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,EACpB,GAAG,EAAE,QAAQ,EACb,OAAO,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,QAAQ,GAAG,IAAI,CAUjB"}
|
package/dist/pickScene.js
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
1
|
import { getCameraScreenToWorldRay } from '@flighthq/camera';
|
|
2
2
|
import { createAabb, createMatrix4, createRay3D, createVector3, getRay3DPointAt, intersectRay3DAabb, intersectRay3DTriangle, inverseMatrix4, } from '@flighthq/geometry';
|
|
3
3
|
import { getMeshGeometryTriangleCount, getMeshGeometryVertexPosition } from '@flighthq/mesh';
|
|
4
|
-
import { ensureNodeWorldTransformMatrix4,
|
|
4
|
+
import { ensureNodeWorldTransformMatrix4, getNodeRuntime, getNodeWorldTransformMatrix4 } from '@flighthq/node';
|
|
5
5
|
import { getSceneNodeWorldBounds, isMesh } from '@flighthq/scene';
|
|
6
|
+
// Allocates a zeroed SceneHit. Handy for the `out` of `pickScene`/`pickSceneWithRay3D` and for
|
|
7
|
+
// growing the `outArray` of the multi-hit queries. `node` is null until a pick fills it.
|
|
8
|
+
export function createSceneHit() {
|
|
9
|
+
return {
|
|
10
|
+
distance: 0,
|
|
11
|
+
node: null,
|
|
12
|
+
normalX: 0,
|
|
13
|
+
normalY: 0,
|
|
14
|
+
normalZ: 0,
|
|
15
|
+
pointX: 0,
|
|
16
|
+
pointY: 0,
|
|
17
|
+
pointZ: 0,
|
|
18
|
+
triangleIndex: -1,
|
|
19
|
+
u: 0,
|
|
20
|
+
v: 0,
|
|
21
|
+
w: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
6
24
|
// Resolves the nearest Mesh hit by a camera pick ray through a scene, filling `out` and returning it,
|
|
7
25
|
// or `null` on a miss (sentinel, not a throw). `screenX`/`screenY` are normalized device coordinates
|
|
8
26
|
// in [-1, 1] (the viewport→NDC mapping is the caller's responsibility, mirroring the camera unproject).
|
|
27
|
+
// A thin wrapper: it unprojects the camera ray, then delegates to `pickSceneWithRay3D`.
|
|
28
|
+
//
|
|
29
|
+
// Alias-safe: `out` is only written when a hit is found; on a miss `out` is untouched.
|
|
30
|
+
export function pickScene(scene, camera, screenX, screenY, out, options) {
|
|
31
|
+
if (!buildCameraPickRay(_cameraRay, camera, screenX, screenY))
|
|
32
|
+
return null;
|
|
33
|
+
return pickSceneWithRay3D(scene, _cameraRay, out, options);
|
|
34
|
+
}
|
|
35
|
+
// Collects every Mesh hit by a camera pick ray into `outArray`, sorted by ascending distance, and
|
|
36
|
+
// returns `outArray`. A thin wrapper over `pickSceneAllWithRay3D` that first unprojects the camera
|
|
37
|
+
// ray; on an unprojectable camera the array is emptied and returned.
|
|
38
|
+
export function pickSceneAll(scene, camera, screenX, screenY, outArray, options) {
|
|
39
|
+
if (!buildCameraPickRay(_cameraRay, camera, screenX, screenY)) {
|
|
40
|
+
outArray.length = 0;
|
|
41
|
+
return outArray;
|
|
42
|
+
}
|
|
43
|
+
return pickSceneAllWithRay3D(scene, _cameraRay, outArray, options);
|
|
44
|
+
}
|
|
45
|
+
// Collects every Mesh hit by a world-space `ray` into `outArray`, sorted by ascending distance, and
|
|
46
|
+
// returns `outArray` (its `length` is set to the hit count; empty on a miss). Existing slots are
|
|
47
|
+
// reused and new ones allocated as needed, so the array can be kept and refilled across picks. The
|
|
48
|
+
// general primitive behind `pickSceneAll`; the ray need not be unit-length (see `ScenePickOptions`).
|
|
49
|
+
export function pickSceneAllWithRay3D(scene, ray, outArray, options) {
|
|
50
|
+
let count = 0;
|
|
51
|
+
forEachSceneRayHit(scene, ray, options, (hit) => {
|
|
52
|
+
let slot = outArray[count];
|
|
53
|
+
if (slot === undefined) {
|
|
54
|
+
slot = createSceneHit();
|
|
55
|
+
outArray[count] = slot;
|
|
56
|
+
}
|
|
57
|
+
copySceneHit(slot, hit);
|
|
58
|
+
count++;
|
|
59
|
+
});
|
|
60
|
+
outArray.length = count;
|
|
61
|
+
outArray.sort(compareSceneHitByDistance);
|
|
62
|
+
return outArray;
|
|
63
|
+
}
|
|
64
|
+
// Resolves the nearest Mesh hit by a world-space `ray` through a scene, filling `out` and returning
|
|
65
|
+
// it, or `null` on a miss. The general primitive behind `pickScene`: `pickScene` builds the ray from
|
|
66
|
+
// a camera and delegates here. The ray direction need not be unit-length; `t` (and `out.distance`)
|
|
67
|
+
// is in direction-length units.
|
|
9
68
|
//
|
|
10
69
|
// Bronze precision: per-node broad-phase against the world AABB, then a brute-force ray↔triangle test
|
|
11
70
|
// over the candidate `MeshGeometry` (a per-`MeshGeometry` BVH is a later additive optimization). The
|
|
@@ -14,54 +73,148 @@ import { getSceneNodeWorldBounds, isMesh } from '@flighthq/scene';
|
|
|
14
73
|
// `t` stays in world-ray units and is comparable across meshes.
|
|
15
74
|
//
|
|
16
75
|
// Alias-safe: `out` is only written when a nearer hit is found; on a miss `out` is untouched.
|
|
17
|
-
export function
|
|
18
|
-
const aspect = camera.projection.kind === 'perspective' ? camera.projection.aspect : 1;
|
|
19
|
-
if (!getCameraScreenToWorldRay(_worldRay, camera, screenX, screenY, aspect))
|
|
20
|
-
return null;
|
|
76
|
+
export function pickSceneWithRay3D(scene, ray, out, options) {
|
|
21
77
|
let found = false;
|
|
22
78
|
let bestT = Infinity;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!isMesh(mesh))
|
|
26
|
-
return;
|
|
27
|
-
// Broad-phase: skip meshes whose world bounds the ray cannot reach.
|
|
28
|
-
getSceneNodeWorldBounds(_worldBounds, mesh);
|
|
29
|
-
if (intersectRay3DAabb(_worldRay, _worldBounds) < 0)
|
|
79
|
+
forEachSceneRayHit(scene, ray, options, (hit) => {
|
|
80
|
+
if (hit.distance >= bestT)
|
|
30
81
|
return;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
-
transformPointByMatrix4(_localRay.origin, _worldRay.origin, _inverseWorld.m);
|
|
36
|
-
transformDirectionByMatrix4(_localRay.direction, _worldRay.direction, _inverseWorld.m);
|
|
37
|
-
const geometry = mesh.geometry;
|
|
38
|
-
const indices = geometry.indices;
|
|
39
|
-
const triangleCount = getMeshGeometryTriangleCount(geometry);
|
|
40
|
-
for (let triangle = 0; triangle < triangleCount; triangle++) {
|
|
41
|
-
const base = triangle * 3;
|
|
42
|
-
const i0 = indices ? indices[base] : base;
|
|
43
|
-
const i1 = indices ? indices[base + 1] : base + 1;
|
|
44
|
-
const i2 = indices ? indices[base + 2] : base + 2;
|
|
45
|
-
getMeshGeometryVertexPosition(_a, geometry, i0);
|
|
46
|
-
getMeshGeometryVertexPosition(_b, geometry, i1);
|
|
47
|
-
getMeshGeometryVertexPosition(_c, geometry, i2);
|
|
48
|
-
const t = intersectRay3DTriangle(_localRay, _a, _b, _c);
|
|
49
|
-
if (t < 0 || t >= bestT)
|
|
50
|
-
continue;
|
|
51
|
-
bestT = t;
|
|
52
|
-
found = true;
|
|
53
|
-
out.node = mesh;
|
|
54
|
-
out.distance = t;
|
|
55
|
-
getRay3DPointAt(_worldPoint, _worldRay, t);
|
|
56
|
-
out.pointX = _worldPoint.x;
|
|
57
|
-
out.pointY = _worldPoint.y;
|
|
58
|
-
out.pointZ = _worldPoint.z;
|
|
59
|
-
getRay3DPointAt(_localPoint, _localRay, t);
|
|
60
|
-
writeBarycentric(out, _localPoint, _a, _b, _c);
|
|
61
|
-
}
|
|
82
|
+
bestT = hit.distance;
|
|
83
|
+
found = true;
|
|
84
|
+
copySceneHit(out, hit);
|
|
62
85
|
});
|
|
63
86
|
return found ? out : null;
|
|
64
87
|
}
|
|
88
|
+
// Unprojects a camera pick ray into `out`. Orthographic projections ignore `aspect` (their extents
|
|
89
|
+
// are the half-width/half-height on the descriptor), so the passed value only matters for
|
|
90
|
+
// perspective, where it must be the projection's own aspect to avoid a horizontal ray skew.
|
|
91
|
+
function buildCameraPickRay(out, camera, screenX, screenY) {
|
|
92
|
+
const aspect = camera.projection.kind === 'perspective' ? camera.projection.aspect : 1;
|
|
93
|
+
return getCameraScreenToWorldRay(out, camera, screenX, screenY, aspect);
|
|
94
|
+
}
|
|
95
|
+
// Field-copies `src` into `out`. SceneHit is a plain result struct (no runtime identity), so a shallow
|
|
96
|
+
// field copy is a full copy.
|
|
97
|
+
function copySceneHit(out, src) {
|
|
98
|
+
out.node = src.node;
|
|
99
|
+
out.distance = src.distance;
|
|
100
|
+
out.triangleIndex = src.triangleIndex;
|
|
101
|
+
out.u = src.u;
|
|
102
|
+
out.v = src.v;
|
|
103
|
+
out.w = src.w;
|
|
104
|
+
out.pointX = src.pointX;
|
|
105
|
+
out.pointY = src.pointY;
|
|
106
|
+
out.pointZ = src.pointZ;
|
|
107
|
+
out.normalX = src.normalX;
|
|
108
|
+
out.normalY = src.normalY;
|
|
109
|
+
out.normalZ = src.normalZ;
|
|
110
|
+
}
|
|
111
|
+
// Ascending-distance comparator for `Array.sort` over SceneHit results.
|
|
112
|
+
function compareSceneHitByDistance(a, b) {
|
|
113
|
+
return a.distance - b.distance;
|
|
114
|
+
}
|
|
115
|
+
// Walks every enabled Mesh in the scene, tests `ray` against its triangles under `options`, and calls
|
|
116
|
+
// `onHit` once per passing triangle with the fully-populated shared `_hit` scratch. Disabled nodes
|
|
117
|
+
// (`enabled === false`) prune their whole subtree, mirroring scene culling; the `predicate` option
|
|
118
|
+
// excludes an individual mesh without pruning its descendants. `onHit` must consume `_hit` before it
|
|
119
|
+
// returns — the scratch is overwritten on the next triangle.
|
|
120
|
+
//
|
|
121
|
+
// Non-reentrant (C/C++ port note): this pass and its narrow-phase run on module-level scratch
|
|
122
|
+
// (`_hit`, `_localRay`, the vertex/point/normal temporaries, and the shared `_cameraRay` filled by the
|
|
123
|
+
// camera wrappers). It must not be called re-entrantly, and `onHit` must not itself start another
|
|
124
|
+
// pick. A native port replaces this shared scratch with per-call stack storage.
|
|
125
|
+
function forEachSceneRayHit(scene, ray, options, onHit) {
|
|
126
|
+
const predicate = options?.predicate;
|
|
127
|
+
const maxDistance = options?.maxDistance ?? Infinity;
|
|
128
|
+
const cullBackfaces = options?.cullBackfaces ?? false;
|
|
129
|
+
pickNode(scene, ray, predicate, maxDistance, cullBackfaces, onHit);
|
|
130
|
+
}
|
|
131
|
+
// Depth-first pruning walk: a disabled node contributes nothing and skips its subtree; an enabled
|
|
132
|
+
// Mesh is narrow-phase tested; every enabled node's children are descended.
|
|
133
|
+
function pickNode(node, ray, predicate, maxDistance, cullBackfaces, onHit) {
|
|
134
|
+
if (!node.enabled)
|
|
135
|
+
return;
|
|
136
|
+
if (isMesh(node) && (predicate === undefined || predicate(node))) {
|
|
137
|
+
intersectMeshTriangles(node, ray, maxDistance, cullBackfaces, onHit);
|
|
138
|
+
}
|
|
139
|
+
const children = getNodeRuntime(node).children;
|
|
140
|
+
if (children !== null) {
|
|
141
|
+
for (let i = 0; i < children.length; i++) {
|
|
142
|
+
pickNode(children[i], ray, predicate, maxDistance, cullBackfaces, onHit);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Broad-phase (world AABB) then brute-force ray↔triangle narrow-phase for one mesh, reporting each
|
|
147
|
+
// passing triangle through `onHit`.
|
|
148
|
+
function intersectMeshTriangles(mesh, ray, maxDistance, cullBackfaces, onHit) {
|
|
149
|
+
getSceneNodeWorldBounds(_worldBounds, mesh);
|
|
150
|
+
if (intersectRay3DAabb(ray, _worldBounds) < 0)
|
|
151
|
+
return;
|
|
152
|
+
ensureNodeWorldTransformMatrix4(mesh);
|
|
153
|
+
const worldMatrix = getNodeWorldTransformMatrix4(mesh);
|
|
154
|
+
if (!inverseMatrix4(_inverseWorld, worldMatrix))
|
|
155
|
+
return;
|
|
156
|
+
transformPointByMatrix4(_localRay.origin, ray.origin, _inverseWorld.m);
|
|
157
|
+
transformDirectionByMatrix4(_localRay.direction, ray.direction, _inverseWorld.m);
|
|
158
|
+
const geometry = mesh.geometry;
|
|
159
|
+
const indices = geometry.indices;
|
|
160
|
+
const triangleCount = getMeshGeometryTriangleCount(geometry);
|
|
161
|
+
for (let triangle = 0; triangle < triangleCount; triangle++) {
|
|
162
|
+
const base = triangle * 3;
|
|
163
|
+
const i0 = indices ? indices[base] : base;
|
|
164
|
+
const i1 = indices ? indices[base + 1] : base + 1;
|
|
165
|
+
const i2 = indices ? indices[base + 2] : base + 2;
|
|
166
|
+
getMeshGeometryVertexPosition(_a, geometry, i0);
|
|
167
|
+
getMeshGeometryVertexPosition(_b, geometry, i1);
|
|
168
|
+
getMeshGeometryVertexPosition(_c, geometry, i2);
|
|
169
|
+
const t = intersectRay3DTriangle(_localRay, _a, _b, _c);
|
|
170
|
+
if (t < 0 || t > maxDistance)
|
|
171
|
+
continue;
|
|
172
|
+
// World-space geometric face normal (normalized cross of two world edges). Transforming the
|
|
173
|
+
// three verts by the world matrix — rather than the local normal by the inverse-transpose —
|
|
174
|
+
// keeps the sign correct under mirroring/negative scale and yields the world normal directly.
|
|
175
|
+
transformPointByMatrix4(_wa, _a, worldMatrix.m);
|
|
176
|
+
transformPointByMatrix4(_wb, _b, worldMatrix.m);
|
|
177
|
+
transformPointByMatrix4(_wc, _c, worldMatrix.m);
|
|
178
|
+
if (!writeFaceNormal(_worldNormal, _wa, _wb, _wc))
|
|
179
|
+
continue;
|
|
180
|
+
// Back-facing when the ray runs along the same side as the normal (dot > 0). Only the sign
|
|
181
|
+
// matters, so the un-normalized ray direction is fine here.
|
|
182
|
+
if (cullBackfaces &&
|
|
183
|
+
ray.direction.x * _worldNormal.x + ray.direction.y * _worldNormal.y + ray.direction.z * _worldNormal.z > 0) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
_hit.node = mesh;
|
|
187
|
+
_hit.distance = t;
|
|
188
|
+
_hit.triangleIndex = triangle;
|
|
189
|
+
_hit.normalX = _worldNormal.x;
|
|
190
|
+
_hit.normalY = _worldNormal.y;
|
|
191
|
+
_hit.normalZ = _worldNormal.z;
|
|
192
|
+
getRay3DPointAt(_worldPoint, ray, t);
|
|
193
|
+
_hit.pointX = _worldPoint.x;
|
|
194
|
+
_hit.pointY = _worldPoint.y;
|
|
195
|
+
_hit.pointZ = _worldPoint.z;
|
|
196
|
+
getRay3DPointAt(_localPoint, _localRay, t);
|
|
197
|
+
writeBarycentric(_hit, _localPoint, _a, _b, _c);
|
|
198
|
+
onHit(_hit);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Writes the unit geometric face normal of triangle `a,b,c` (normalized `(b-a) × (c-a)`) into `out`,
|
|
202
|
+
// returning `false` for a degenerate (zero-area) triangle, in which case `out` is left untouched.
|
|
203
|
+
function writeFaceNormal(out, a, b, c) {
|
|
204
|
+
const e1x = b.x - a.x, e1y = b.y - a.y, e1z = b.z - a.z;
|
|
205
|
+
const e2x = c.x - a.x, e2y = c.y - a.y, e2z = c.z - a.z;
|
|
206
|
+
const nx = e1y * e2z - e1z * e2y;
|
|
207
|
+
const ny = e1z * e2x - e1x * e2z;
|
|
208
|
+
const nz = e1x * e2y - e1y * e2x;
|
|
209
|
+
const lengthSquared = nx * nx + ny * ny + nz * nz;
|
|
210
|
+
if (lengthSquared === 0)
|
|
211
|
+
return false;
|
|
212
|
+
const inv = 1 / Math.sqrt(lengthSquared);
|
|
213
|
+
out.x = nx * inv;
|
|
214
|
+
out.y = ny * inv;
|
|
215
|
+
out.z = nz * inv;
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
65
218
|
// Fills `out.u/v/w` with the barycentric weights of `p` within triangle `a,b,c` (weights of a, b, c
|
|
66
219
|
// respectively; `p = u*a + v*b + w*c`). Degenerate triangles collapse to `(1, 0, 0)`.
|
|
67
220
|
function writeBarycentric(out, p, a, b, c) {
|
|
@@ -103,13 +256,20 @@ function transformDirectionByMatrix4(out, d, m) {
|
|
|
103
256
|
out.y = m[1] * x + m[5] * y + m[9] * z;
|
|
104
257
|
out.z = m[2] * x + m[6] * y + m[10] * z;
|
|
105
258
|
}
|
|
106
|
-
|
|
259
|
+
// Module-level scratch. This makes the pick pass non-reentrant (see forEachSceneRayHit) — a C/C++
|
|
260
|
+
// port moves these to per-call stack storage.
|
|
261
|
+
const _cameraRay = createRay3D();
|
|
107
262
|
const _localRay = createRay3D();
|
|
108
263
|
const _inverseWorld = createMatrix4();
|
|
109
264
|
const _worldBounds = createAabb();
|
|
110
265
|
const _a = createVector3();
|
|
111
266
|
const _b = createVector3();
|
|
112
267
|
const _c = createVector3();
|
|
268
|
+
const _wa = createVector3();
|
|
269
|
+
const _wb = createVector3();
|
|
270
|
+
const _wc = createVector3();
|
|
271
|
+
const _worldNormal = createVector3();
|
|
113
272
|
const _localPoint = createVector3();
|
|
114
273
|
const _worldPoint = createVector3();
|
|
274
|
+
const _hit = createSceneHit();
|
|
115
275
|
//# sourceMappingURL=pickScene.js.map
|
package/dist/pickScene.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pickScene.js","sourceRoot":"","sources":["../src/pickScene.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pickScene.js","sourceRoot":"","sources":["../src/pickScene.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/picking",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/flighthq/flight.git",
|
|
7
|
+
"directory": "packages/picking"
|
|
8
|
+
},
|
|
4
9
|
"type": "module",
|
|
5
10
|
"main": "dist/index.js",
|
|
6
11
|
"types": "dist/index.d.ts",
|
|
@@ -27,12 +32,12 @@
|
|
|
27
32
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
33
|
},
|
|
29
34
|
"dependencies": {
|
|
30
|
-
"@flighthq/camera": "0.
|
|
31
|
-
"@flighthq/geometry": "0.
|
|
32
|
-
"@flighthq/mesh": "0.
|
|
33
|
-
"@flighthq/node": "0.
|
|
34
|
-
"@flighthq/scene": "0.
|
|
35
|
-
"@flighthq/types": "0.
|
|
35
|
+
"@flighthq/camera": "0.2.0",
|
|
36
|
+
"@flighthq/geometry": "0.2.0",
|
|
37
|
+
"@flighthq/mesh": "0.2.0",
|
|
38
|
+
"@flighthq/node": "0.2.0",
|
|
39
|
+
"@flighthq/scene": "0.2.0",
|
|
40
|
+
"@flighthq/types": "0.2.0"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
43
|
"typescript": "^5.3.0"
|
package/src/pickScene.test.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
createCamera,
|
|
3
|
+
createOrthographicProjection,
|
|
4
|
+
createPerspectiveProjection,
|
|
5
|
+
setCameraViewMatrix4FromLookAt,
|
|
6
|
+
} from '@flighthq/camera';
|
|
7
|
+
import { createQuaternion, createRay3D, createVector3, setQuaternionFromAxisAngle, setRay3D } from '@flighthq/geometry';
|
|
8
|
+
import { createBoxMeshGeometry, createMeshGeometryFromAttributes } from '@flighthq/mesh';
|
|
4
9
|
import { addNodeChild } from '@flighthq/node';
|
|
5
10
|
import type { Scene } from '@flighthq/scene';
|
|
6
|
-
import {
|
|
7
|
-
|
|
11
|
+
import {
|
|
12
|
+
createMesh,
|
|
13
|
+
createScene,
|
|
14
|
+
createSceneNode,
|
|
15
|
+
setSceneNodePosition,
|
|
16
|
+
setSceneNodeRotationQuaternion,
|
|
17
|
+
setSceneNodeScale,
|
|
18
|
+
} from '@flighthq/scene';
|
|
19
|
+
import type { Camera, Mesh, Ray3D, SceneHit } from '@flighthq/types';
|
|
8
20
|
|
|
9
|
-
import { pickScene } from './pickScene';
|
|
21
|
+
import { createSceneHit, pickScene, pickSceneAll, pickSceneAllWithRay3D, pickSceneWithRay3D } from './pickScene';
|
|
10
22
|
|
|
11
23
|
function makeCamera(): Camera {
|
|
12
24
|
const camera = createCamera({
|
|
@@ -19,8 +31,33 @@ function makeCamera(): Camera {
|
|
|
19
31
|
return camera;
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
// Orthographic camera with a deliberately non-square view volume (halfWidth != halfHeight) to guard
|
|
35
|
+
// against a horizontal/vertical mismap. Looks down -Z from z = 5.
|
|
36
|
+
function makeOrthoCamera(halfWidth: number, halfHeight: number): Camera {
|
|
37
|
+
const camera = createCamera({
|
|
38
|
+
far: 100,
|
|
39
|
+
near: 0.1,
|
|
40
|
+
projection: createOrthographicProjection({ halfHeight, halfWidth }),
|
|
41
|
+
});
|
|
42
|
+
setCameraViewMatrix4FromLookAt(camera, createVector3(0, 0, 5), createVector3(0, 0, 0), createVector3(0, 1, 0));
|
|
43
|
+
return camera;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A world-space ray from z = 5 pointing toward the origin along -Z (mirrors makeCamera's center ray).
|
|
47
|
+
function makeCenterRay(): Ray3D {
|
|
48
|
+
const ray = createRay3D();
|
|
49
|
+
setRay3D(ray, createVector3(0, 0, 5), createVector3(0, 0, -1));
|
|
50
|
+
return ray;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// A single triangle in the z = 0 plane. `frontFacing` winds it so its geometric normal points toward
|
|
54
|
+
// +Z (toward a camera at +Z); otherwise it faces -Z (away).
|
|
55
|
+
function triangleMesh(frontFacing: boolean): Mesh {
|
|
56
|
+
const a = [-1, -1, 0];
|
|
57
|
+
const b = frontFacing ? [1, -1, 0] : [0, 1, 0];
|
|
58
|
+
const c = frontFacing ? [0, 1, 0] : [1, -1, 0];
|
|
59
|
+
const geometry = createMeshGeometryFromAttributes({ positions: [...a, ...b, ...c] });
|
|
60
|
+
return createMesh(geometry, []);
|
|
24
61
|
}
|
|
25
62
|
|
|
26
63
|
function sceneWithCenteredBox(): { scene: Scene; mesh: Mesh } {
|
|
@@ -30,11 +67,23 @@ function sceneWithCenteredBox(): { scene: Scene; mesh: Mesh } {
|
|
|
30
67
|
return { scene, mesh };
|
|
31
68
|
}
|
|
32
69
|
|
|
70
|
+
describe('createSceneHit', () => {
|
|
71
|
+
it('allocates a zeroed hit with a sentinel triangle index', () => {
|
|
72
|
+
const hit = createSceneHit();
|
|
73
|
+
|
|
74
|
+
expect(hit.triangleIndex).toBe(-1);
|
|
75
|
+
expect(hit.distance).toBe(0);
|
|
76
|
+
expect(hit.normalX).toBe(0);
|
|
77
|
+
expect(hit.normalY).toBe(0);
|
|
78
|
+
expect(hit.normalZ).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
33
82
|
describe('pickScene', () => {
|
|
34
83
|
it('fills barycentric weights that sum to one', () => {
|
|
35
84
|
const camera = makeCamera();
|
|
36
85
|
const { scene } = sceneWithCenteredBox();
|
|
37
|
-
const out =
|
|
86
|
+
const out = createSceneHit();
|
|
38
87
|
|
|
39
88
|
const hit = pickScene(scene, camera, 0, 0, out);
|
|
40
89
|
|
|
@@ -42,13 +91,27 @@ describe('pickScene', () => {
|
|
|
42
91
|
expect((hit?.u ?? 0) + (hit?.v ?? 0) + (hit?.w ?? 0)).toBeCloseTo(1, 5);
|
|
43
92
|
});
|
|
44
93
|
|
|
94
|
+
it('fills the world face normal and triangle index of the hit', () => {
|
|
95
|
+
const camera = makeCamera();
|
|
96
|
+
const { scene } = sceneWithCenteredBox();
|
|
97
|
+
const out = createSceneHit();
|
|
98
|
+
|
|
99
|
+
const hit = pickScene(scene, camera, 0, 0, out);
|
|
100
|
+
|
|
101
|
+
// Front face of the box points at +Z (toward the camera at z = 5).
|
|
102
|
+
expect(hit?.normalZ).toBeCloseTo(1, 5);
|
|
103
|
+
expect(hit?.normalX).toBeCloseTo(0, 5);
|
|
104
|
+
expect(hit?.normalY).toBeCloseTo(0, 5);
|
|
105
|
+
expect(hit?.triangleIndex).toBeGreaterThanOrEqual(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
45
108
|
it('returns null for a scene containing only non-mesh nodes', () => {
|
|
46
109
|
const camera = makeCamera();
|
|
47
110
|
const scene = createScene();
|
|
48
111
|
// A plain SceneNode is not a Mesh; isMesh() returns false for it.
|
|
49
112
|
const node = createSceneNode();
|
|
50
113
|
addNodeChild(scene, node);
|
|
51
|
-
const out =
|
|
114
|
+
const out = createSceneHit();
|
|
52
115
|
|
|
53
116
|
expect(pickScene(scene, camera, 0, 0, out)).toBeNull();
|
|
54
117
|
});
|
|
@@ -56,7 +119,7 @@ describe('pickScene', () => {
|
|
|
56
119
|
it('returns null when the ray misses every mesh', () => {
|
|
57
120
|
const camera = makeCamera();
|
|
58
121
|
const { scene } = sceneWithCenteredBox();
|
|
59
|
-
const out =
|
|
122
|
+
const out = createSceneHit();
|
|
60
123
|
|
|
61
124
|
// Far corner of the viewport points away from the small centred box.
|
|
62
125
|
expect(pickScene(scene, camera, 0.99, 0.99, out)).toBeNull();
|
|
@@ -65,7 +128,7 @@ describe('pickScene', () => {
|
|
|
65
128
|
it('returns null when the scene has no meshes', () => {
|
|
66
129
|
const camera = makeCamera();
|
|
67
130
|
const scene = createScene();
|
|
68
|
-
const out =
|
|
131
|
+
const out = createSceneHit();
|
|
69
132
|
|
|
70
133
|
expect(pickScene(scene, camera, 0, 0, out)).toBeNull();
|
|
71
134
|
});
|
|
@@ -73,7 +136,7 @@ describe('pickScene', () => {
|
|
|
73
136
|
it('returns the mesh hit by the center ray', () => {
|
|
74
137
|
const camera = makeCamera();
|
|
75
138
|
const { scene, mesh } = sceneWithCenteredBox();
|
|
76
|
-
const out =
|
|
139
|
+
const out = createSceneHit();
|
|
77
140
|
|
|
78
141
|
const hit = pickScene(scene, camera, 0, 0, out);
|
|
79
142
|
|
|
@@ -101,7 +164,7 @@ describe('pickScene', () => {
|
|
|
101
164
|
setSceneNodePosition(meshNear, 0, 0, 2);
|
|
102
165
|
addNodeChild(scene, meshNear);
|
|
103
166
|
|
|
104
|
-
const out =
|
|
167
|
+
const out = createSceneHit();
|
|
105
168
|
const hit = pickScene(scene, camera, 0, 0, out);
|
|
106
169
|
|
|
107
170
|
expect(hit).toBe(out);
|
|
@@ -111,4 +174,191 @@ describe('pickScene', () => {
|
|
|
111
174
|
expect(hit?.distance).toBeGreaterThan(0);
|
|
112
175
|
expect(hit?.distance).toBeLessThan(3); // meshFar's front face would be at distance ≈ 4
|
|
113
176
|
});
|
|
177
|
+
|
|
178
|
+
it('skips a disabled mesh so it is not pickable', () => {
|
|
179
|
+
const camera = makeCamera();
|
|
180
|
+
const { scene, mesh } = sceneWithCenteredBox();
|
|
181
|
+
mesh.enabled = false;
|
|
182
|
+
const out = createSceneHit();
|
|
183
|
+
|
|
184
|
+
expect(pickScene(scene, camera, 0, 0, out)).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('prunes the subtree of a disabled group node', () => {
|
|
188
|
+
const camera = makeCamera();
|
|
189
|
+
const scene = createScene();
|
|
190
|
+
const group = createSceneNode();
|
|
191
|
+
group.enabled = false;
|
|
192
|
+
const mesh = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
193
|
+
addNodeChild(group, mesh);
|
|
194
|
+
addNodeChild(scene, group);
|
|
195
|
+
const out = createSceneHit();
|
|
196
|
+
|
|
197
|
+
expect(pickScene(scene, camera, 0, 0, out)).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('excludes a mesh rejected by the predicate option', () => {
|
|
201
|
+
const camera = makeCamera();
|
|
202
|
+
const scene = createScene();
|
|
203
|
+
const meshFar = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
204
|
+
addNodeChild(scene, meshFar);
|
|
205
|
+
const meshNear = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
206
|
+
setSceneNodePosition(meshNear, 0, 0, 2);
|
|
207
|
+
addNodeChild(scene, meshNear);
|
|
208
|
+
const out = createSceneHit();
|
|
209
|
+
|
|
210
|
+
const hit = pickScene(scene, camera, 0, 0, out, { predicate: (m) => m === meshFar });
|
|
211
|
+
|
|
212
|
+
expect(hit?.node).toBe(meshFar);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('rejects hits beyond maxDistance', () => {
|
|
216
|
+
const camera = makeCamera();
|
|
217
|
+
const { scene } = sceneWithCenteredBox();
|
|
218
|
+
const out = createSceneHit();
|
|
219
|
+
|
|
220
|
+
// Front face is ~4 world units from the camera at z = 5.
|
|
221
|
+
expect(pickScene(scene, camera, 0, 0, out, { maxDistance: 3 })).toBeNull();
|
|
222
|
+
expect(pickScene(scene, camera, 0, 0, out, { maxDistance: 4.5 })).not.toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('maps a non-square orthographic viewport without an aspect mismap', () => {
|
|
226
|
+
// View volume is 8 wide (halfWidth 4) but only 2 tall (halfHeight 1).
|
|
227
|
+
const camera = makeOrthoCamera(4, 1);
|
|
228
|
+
const scene = createScene();
|
|
229
|
+
// A 2x2x2 box centred at x = 3 spans x in [2, 4], within the horizontal extent but well outside
|
|
230
|
+
// the vertical one — so a square (aspect = 1) mismap of screenX would miss it.
|
|
231
|
+
const mesh = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
232
|
+
setSceneNodePosition(mesh, 3, 0, 0);
|
|
233
|
+
addNodeChild(scene, mesh);
|
|
234
|
+
const out = createSceneHit();
|
|
235
|
+
|
|
236
|
+
// screenX = 0.75 → world x = 0.75 * halfWidth(4) = 3.
|
|
237
|
+
const hit = pickScene(scene, camera, 0.75, 0, out);
|
|
238
|
+
|
|
239
|
+
expect(hit?.node).toBe(mesh);
|
|
240
|
+
expect(hit?.pointX).toBeCloseTo(3, 3);
|
|
241
|
+
expect(hit?.pointZ).toBeCloseTo(1, 3);
|
|
242
|
+
// A pick past the horizontal extent misses.
|
|
243
|
+
expect(pickScene(scene, camera, 1.1, 0, out)).toBeNull();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('hits a rotated and scaled mesh through the local-space path', () => {
|
|
247
|
+
const camera = makeCamera();
|
|
248
|
+
const scene = createScene();
|
|
249
|
+
const mesh = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
250
|
+
// Non-uniform scale (z doubled) plus a rotation about the Z axis. The +Z face stays on-axis, so
|
|
251
|
+
// its world position is z = 1 * 2 = 2 while the surrounding geometry is rotated — exercising the
|
|
252
|
+
// inverse-transform narrow phase.
|
|
253
|
+
setSceneNodeScale(mesh, 1.5, 1.5, 2);
|
|
254
|
+
const q = createQuaternion();
|
|
255
|
+
setQuaternionFromAxisAngle(q, createVector3(0, 0, 1), Math.PI / 4);
|
|
256
|
+
setSceneNodeRotationQuaternion(mesh, q);
|
|
257
|
+
addNodeChild(scene, mesh);
|
|
258
|
+
const out = createSceneHit();
|
|
259
|
+
|
|
260
|
+
const hit = pickScene(scene, camera, 0, 0, out);
|
|
261
|
+
|
|
262
|
+
expect(hit?.node).toBe(mesh);
|
|
263
|
+
expect(hit?.pointZ).toBeCloseTo(2, 3);
|
|
264
|
+
expect(hit?.pointX).toBeCloseTo(0, 3);
|
|
265
|
+
expect(hit?.pointY).toBeCloseTo(0, 3);
|
|
266
|
+
expect(hit?.normalZ).toBeCloseTo(1, 3);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('pickSceneAll', () => {
|
|
271
|
+
it('collects both overlapping meshes sorted by ascending distance', () => {
|
|
272
|
+
const camera = makeCamera();
|
|
273
|
+
const scene = createScene();
|
|
274
|
+
const meshFar = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
275
|
+
addNodeChild(scene, meshFar);
|
|
276
|
+
const meshNear = createMesh(createBoxMeshGeometry(2, 2, 2), []);
|
|
277
|
+
setSceneNodePosition(meshNear, 0, 0, 2);
|
|
278
|
+
addNodeChild(scene, meshNear);
|
|
279
|
+
const outArray: SceneHit[] = [];
|
|
280
|
+
|
|
281
|
+
const hits = pickSceneAll(scene, camera, 0, 0, outArray);
|
|
282
|
+
|
|
283
|
+
expect(hits).toBe(outArray);
|
|
284
|
+
// Each box contributes a front-face hit (a 2x2x2 box's front quad is two triangles, but the
|
|
285
|
+
// center ray crosses one triangle per box). Nearest first.
|
|
286
|
+
expect(hits.length).toBeGreaterThanOrEqual(2);
|
|
287
|
+
for (let i = 1; i < hits.length; i++) {
|
|
288
|
+
expect(hits[i].distance).toBeGreaterThanOrEqual(hits[i - 1].distance);
|
|
289
|
+
}
|
|
290
|
+
expect(hits[0].node).toBe(meshNear);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('empties the array on a miss', () => {
|
|
294
|
+
const camera = makeCamera();
|
|
295
|
+
const { scene } = sceneWithCenteredBox();
|
|
296
|
+
const outArray: SceneHit[] = [createSceneHit(), createSceneHit()];
|
|
297
|
+
|
|
298
|
+
const hits = pickSceneAll(scene, camera, 0.99, 0.99, outArray);
|
|
299
|
+
|
|
300
|
+
expect(hits).toBe(outArray);
|
|
301
|
+
expect(hits.length).toBe(0);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('pickSceneAllWithRay3D', () => {
|
|
306
|
+
it('collects every triangle a world ray crosses, front and back faces', () => {
|
|
307
|
+
const { scene } = sceneWithCenteredBox();
|
|
308
|
+
const ray = makeCenterRay();
|
|
309
|
+
const outArray: SceneHit[] = [];
|
|
310
|
+
|
|
311
|
+
const hits = pickSceneAllWithRay3D(scene, ray, outArray);
|
|
312
|
+
|
|
313
|
+
// The center ray runs along the shared diagonal of each quad, so it grazes both triangles of the
|
|
314
|
+
// front face (z = 1) and both of the back face (z = -1): four hits, sorted near-to-far.
|
|
315
|
+
expect(hits.length).toBe(4);
|
|
316
|
+
for (let i = 1; i < hits.length; i++) {
|
|
317
|
+
expect(hits[i].distance).toBeGreaterThanOrEqual(hits[i - 1].distance);
|
|
318
|
+
}
|
|
319
|
+
expect(hits[0].pointZ).toBeCloseTo(1, 3);
|
|
320
|
+
expect(hits[hits.length - 1].pointZ).toBeCloseTo(-1, 3);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('keeps only front faces when cullBackfaces is set', () => {
|
|
324
|
+
const { scene } = sceneWithCenteredBox();
|
|
325
|
+
const ray = makeCenterRay();
|
|
326
|
+
const outArray: SceneHit[] = [];
|
|
327
|
+
|
|
328
|
+
const hits = pickSceneAllWithRay3D(scene, ray, outArray, { cullBackfaces: true });
|
|
329
|
+
|
|
330
|
+
// Only the two front-face triangles survive; both point at +Z (toward the ray origin).
|
|
331
|
+
expect(hits.length).toBe(2);
|
|
332
|
+
for (const hit of hits) {
|
|
333
|
+
expect(hit.pointZ).toBeCloseTo(1, 3);
|
|
334
|
+
expect(hit.normalZ).toBeCloseTo(1, 3);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('pickSceneWithRay3D', () => {
|
|
340
|
+
it('resolves the nearest hit for a world-space ray', () => {
|
|
341
|
+
const { scene, mesh } = sceneWithCenteredBox();
|
|
342
|
+
const ray = makeCenterRay();
|
|
343
|
+
const out = createSceneHit();
|
|
344
|
+
|
|
345
|
+
const hit = pickSceneWithRay3D(scene, ray, out, {});
|
|
346
|
+
|
|
347
|
+
expect(hit).toBe(out);
|
|
348
|
+
expect(hit?.node).toBe(mesh);
|
|
349
|
+
expect(hit?.pointZ).toBeCloseTo(1, 3);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('culls a back-facing triangle but keeps a front-facing one', () => {
|
|
353
|
+
const front = createScene();
|
|
354
|
+
addNodeChild(front, triangleMesh(true));
|
|
355
|
+
const back = createScene();
|
|
356
|
+
addNodeChild(back, triangleMesh(false));
|
|
357
|
+
const out = createSceneHit();
|
|
358
|
+
|
|
359
|
+
expect(pickSceneWithRay3D(front, makeCenterRay(), out, { cullBackfaces: true })).not.toBeNull();
|
|
360
|
+
expect(pickSceneWithRay3D(back, makeCenterRay(), out, { cullBackfaces: true })).toBeNull();
|
|
361
|
+
// Double-sided (default) hits either winding.
|
|
362
|
+
expect(pickSceneWithRay3D(back, makeCenterRay(), out)).not.toBeNull();
|
|
363
|
+
});
|
|
114
364
|
});
|