@es3d/es3d 1.0.0 → 1.0.2
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/LICENSE +201 -201
- package/README.md +95 -86
- package/docs/SPEC.md +426 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +390 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +132 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf-pack.js +1461 -0
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +604 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +365 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- package/src/wasm/kernels.js +385 -385
package/src/scene/picking.js
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
// CPU ray picking against the instance table.
|
|
2
|
-
//
|
|
3
|
-
// Each instance is tested with a world-space bounding-sphere check, then the
|
|
4
|
-
// ray is transformed into the instance's canonical mesh space (inverse world
|
|
5
|
-
// matrix) and tested against the canonical bounds and finally the triangles
|
|
6
|
-
// (Möller–Trumbore). Because instancing keeps one canonical mesh per
|
|
7
|
-
// geometry, picking cost scales with visible instances, not with triangles.
|
|
8
|
-
|
|
9
|
-
import * as mat4 from '../math/mat4.js';
|
|
10
|
-
|
|
11
|
-
function raySphere(ox, oy, oz, dx, dy, dz, cx, cy, cz, r) {
|
|
12
|
-
const lx = cx - ox, ly = cy - oy, lz = cz - oz;
|
|
13
|
-
const tca = lx * dx + ly * dy + lz * dz;
|
|
14
|
-
const d2 = lx * lx + ly * ly + lz * lz - tca * tca;
|
|
15
|
-
if (d2 > r * r) return Infinity;
|
|
16
|
-
const thc = Math.sqrt(r * r - d2);
|
|
17
|
-
const t0 = tca - thc;
|
|
18
|
-
const t1 = tca + thc;
|
|
19
|
-
if (t1 < 0) return Infinity;
|
|
20
|
-
return t0 < 0 ? 0 : t0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function rayBox(o, d, b) {
|
|
24
|
-
let tmin = -Infinity, tmax = Infinity;
|
|
25
|
-
for (let k = 0; k < 3; k++) {
|
|
26
|
-
const pad = 1e-6 + (b.max[k] - b.min[k]) * 1e-4;
|
|
27
|
-
const lo = b.min[k] - pad, hi = b.max[k] + pad;
|
|
28
|
-
if (Math.abs(d[k]) < 1e-20) {
|
|
29
|
-
if (o[k] < lo || o[k] > hi) return false;
|
|
30
|
-
} else {
|
|
31
|
-
let t1 = (lo - o[k]) / d[k];
|
|
32
|
-
let t2 = (hi - o[k]) / d[k];
|
|
33
|
-
if (t1 > t2) [t1, t2] = [t2, t1];
|
|
34
|
-
tmin = Math.max(tmin, t1);
|
|
35
|
-
tmax = Math.min(tmax, t2);
|
|
36
|
-
if (tmin > tmax) return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return tmax >= 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let hitTriangle = -1; // first index of the triangle found by the last rayTriangles call
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Nearest triangle hit closer than `best`. With `facing` (+1 or -1) only
|
|
46
|
-
* triangles whose front side faces the ray origin count: det > 0 means the
|
|
47
|
-
* ray meets the counter-clockwise side, and `facing` is -1 for mirrored instances.
|
|
48
|
-
*/
|
|
49
|
-
function rayTriangles(o, d, mesh, best, facing = 0) {
|
|
50
|
-
const p = mesh.positions;
|
|
51
|
-
const idx = mesh.indices;
|
|
52
|
-
let tBest = best;
|
|
53
|
-
hitTriangle = -1;
|
|
54
|
-
for (let i = 0; i < idx.length; i += 3) {
|
|
55
|
-
const a = idx[i] * 3, b = idx[i + 1] * 3, c = idx[i + 2] * 3;
|
|
56
|
-
const e1x = p[b] - p[a], e1y = p[b + 1] - p[a + 1], e1z = p[b + 2] - p[a + 2];
|
|
57
|
-
const e2x = p[c] - p[a], e2y = p[c + 1] - p[a + 1], e2z = p[c + 2] - p[a + 2];
|
|
58
|
-
const px = d[1] * e2z - d[2] * e2y, py = d[2] * e2x - d[0] * e2z, pz = d[0] * e2y - d[1] * e2x;
|
|
59
|
-
const det = e1x * px + e1y * py + e1z * pz;
|
|
60
|
-
if (Math.abs(det) < 1e-18) continue;
|
|
61
|
-
if (facing && det * facing < 0) continue;
|
|
62
|
-
const inv = 1 / det;
|
|
63
|
-
const tx = o[0] - p[a], ty = o[1] - p[a + 1], tz = o[2] - p[a + 2];
|
|
64
|
-
const u = (tx * px + ty * py + tz * pz) * inv;
|
|
65
|
-
if (u < 0 || u > 1) continue;
|
|
66
|
-
const qx = ty * e1z - tz * e1y, qy = tz * e1x - tx * e1z, qz = tx * e1y - ty * e1x;
|
|
67
|
-
const v = (d[0] * qx + d[1] * qy + d[2] * qz) * inv;
|
|
68
|
-
if (v < 0 || u + v > 1) continue;
|
|
69
|
-
const t = (e2x * qx + e2y * qy + e2z * qz) * inv;
|
|
70
|
-
if (t > 1e-9 && t < tBest) {
|
|
71
|
-
tBest = t;
|
|
72
|
-
hitTriangle = i;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return tBest;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Nearest surface along a ray.
|
|
80
|
-
* @param {import('./flatten.js').RenderScene} scene
|
|
81
|
-
* @param {(entry:object, lod:number)=>import('../geometry/mesh.js').Mesh} meshOf
|
|
82
|
-
* @param {number[]} origin world ray origin
|
|
83
|
-
* @param {number[]} dir world ray direction (normalized)
|
|
84
|
-
* @param {{isVisible?: (i:number)=>boolean, maxDistance?: number, skipFlags?: number, facing?: boolean}} [opts]
|
|
85
|
-
* skipFlags: instances with any of these flag bits are ignored. facing: only
|
|
86
|
-
* surfaces whose front side faces the origin (double-sided geometry always counts).
|
|
87
|
-
* @returns {{index:number, uid:number, distance:number, point:number[], normal:number[]}|null}
|
|
88
|
-
* normal is the world-space surface normal turned against the ray.
|
|
89
|
-
*/
|
|
90
|
-
export function raycast(scene, meshOf, origin, dir, opts = {}) {
|
|
91
|
-
const { isVisible = null, maxDistance = Infinity, skipFlags = 0, facing = false } = opts;
|
|
92
|
-
const cands = [];
|
|
93
|
-
const S = scene.spheres;
|
|
94
|
-
for (let i = 0; i < scene.count; i++) {
|
|
95
|
-
if (skipFlags && scene.flags[i] & skipFlags) continue;
|
|
96
|
-
if (isVisible && !isVisible(i)) continue;
|
|
97
|
-
const entry = scene.geometries[scene.geom[i]];
|
|
98
|
-
if (entry.kind !== 'triangles') continue;
|
|
99
|
-
const t = raySphere(origin[0], origin[1], origin[2], dir[0], dir[1], dir[2], S[i * 4], S[i * 4 + 1], S[i * 4 + 2], S[i * 4 + 3]);
|
|
100
|
-
if (t < maxDistance) cands.push([t, i]);
|
|
101
|
-
}
|
|
102
|
-
cands.sort((a, b) => a[0] - b[0]);
|
|
103
|
-
let best = maxDistance;
|
|
104
|
-
let bestIndex = -1;
|
|
105
|
-
let bestTriangle = -1;
|
|
106
|
-
const normal = [0, 0, 0];
|
|
107
|
-
const m = new Float64Array(16);
|
|
108
|
-
const inv = new Float64Array(16);
|
|
109
|
-
const lo = [0, 0, 0];
|
|
110
|
-
const ld = [0, 0, 0];
|
|
111
|
-
for (const [tSphere, i] of cands) {
|
|
112
|
-
if (tSphere > best) break;
|
|
113
|
-
for (let k = 0; k < 16; k++) m[k] = scene.matrices[i * 16 + k];
|
|
114
|
-
if (!mat4.invert(inv, m)) continue;
|
|
115
|
-
mat4.transformPoint(lo, inv, origin[0], origin[1], origin[2]);
|
|
116
|
-
mat4.transformDir(ld, inv, dir[0], dir[1], dir[2]);
|
|
117
|
-
const entry = scene.geometries[scene.geom[i]];
|
|
118
|
-
if (entry.bounds && !rayBox(lo, ld, entry.bounds)) continue;
|
|
119
|
-
const mesh = meshOf(entry, 0);
|
|
120
|
-
const material = scene.materials[scene.mat[i]];
|
|
121
|
-
const twoSided = entry.doubleSided || (material && material.doubleSided);
|
|
122
|
-
const front = facing && !twoSided ? (scene.flags[i] & 1 ? -1 : 1) : 0;
|
|
123
|
-
// t is in world units because the direction was transformed linearly (not renormalized)
|
|
124
|
-
const t = rayTriangles(lo, ld, mesh, best, front);
|
|
125
|
-
if (t < best) {
|
|
126
|
-
best = t;
|
|
127
|
-
bestIndex = i;
|
|
128
|
-
bestTriangle = hitTriangle;
|
|
129
|
-
// triangle normal to world space with the inverse transpose
|
|
130
|
-
const p = mesh.positions, idx = mesh.indices;
|
|
131
|
-
const a = idx[bestTriangle] * 3, b = idx[bestTriangle + 1] * 3, c = idx[bestTriangle + 2] * 3;
|
|
132
|
-
const e1 = [p[b] - p[a], p[b + 1] - p[a + 1], p[b + 2] - p[a + 2]];
|
|
133
|
-
const e2 = [p[c] - p[a], p[c + 1] - p[a + 1], p[c + 2] - p[a + 2]];
|
|
134
|
-
const n = [e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]];
|
|
135
|
-
for (let k = 0; k < 3; k++) normal[k] = inv[k * 4] * n[0] + inv[k * 4 + 1] * n[1] + inv[k * 4 + 2] * n[2];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (bestIndex < 0) return null;
|
|
139
|
-
const nl = Math.hypot(normal[0], normal[1], normal[2]) || 1;
|
|
140
|
-
const sign = normal[0] * dir[0] + normal[1] * dir[1] + normal[2] * dir[2] > 0 ? -1 : 1;
|
|
141
|
-
return {
|
|
142
|
-
index: bestIndex,
|
|
143
|
-
uid: scene.uid[bestIndex],
|
|
144
|
-
distance: best,
|
|
145
|
-
point: [origin[0] + dir[0] * best, origin[1] + dir[1] * best, origin[2] + dir[2] * best],
|
|
146
|
-
normal: [(normal[0] / nl) * sign, (normal[1] / nl) * sign, (normal[2] / nl) * sign],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Element under a ray (for selection).
|
|
152
|
-
* @param {import('./flatten.js').RenderScene} scene
|
|
153
|
-
* @param {(entry:object, lod:number)=>import('../geometry/mesh.js').Mesh} meshOf
|
|
154
|
-
* @param {number[]} origin world ray origin
|
|
155
|
-
* @param {number[]} dir world ray direction (normalized)
|
|
156
|
-
* @param {(i:number)=>boolean} [isVisible]
|
|
157
|
-
* @returns {{index:number, uid:number, distance:number, point:number[], normal:number[]}|null}
|
|
158
|
-
*/
|
|
159
|
-
export function pick(scene, meshOf, origin, dir, isVisible = null) {
|
|
160
|
-
return raycast(scene, meshOf, origin, dir, { isVisible });
|
|
161
|
-
}
|
|
1
|
+
// CPU ray picking against the instance table.
|
|
2
|
+
//
|
|
3
|
+
// Each instance is tested with a world-space bounding-sphere check, then the
|
|
4
|
+
// ray is transformed into the instance's canonical mesh space (inverse world
|
|
5
|
+
// matrix) and tested against the canonical bounds and finally the triangles
|
|
6
|
+
// (Möller–Trumbore). Because instancing keeps one canonical mesh per
|
|
7
|
+
// geometry, picking cost scales with visible instances, not with triangles.
|
|
8
|
+
|
|
9
|
+
import * as mat4 from '../math/mat4.js';
|
|
10
|
+
|
|
11
|
+
function raySphere(ox, oy, oz, dx, dy, dz, cx, cy, cz, r) {
|
|
12
|
+
const lx = cx - ox, ly = cy - oy, lz = cz - oz;
|
|
13
|
+
const tca = lx * dx + ly * dy + lz * dz;
|
|
14
|
+
const d2 = lx * lx + ly * ly + lz * lz - tca * tca;
|
|
15
|
+
if (d2 > r * r) return Infinity;
|
|
16
|
+
const thc = Math.sqrt(r * r - d2);
|
|
17
|
+
const t0 = tca - thc;
|
|
18
|
+
const t1 = tca + thc;
|
|
19
|
+
if (t1 < 0) return Infinity;
|
|
20
|
+
return t0 < 0 ? 0 : t0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function rayBox(o, d, b) {
|
|
24
|
+
let tmin = -Infinity, tmax = Infinity;
|
|
25
|
+
for (let k = 0; k < 3; k++) {
|
|
26
|
+
const pad = 1e-6 + (b.max[k] - b.min[k]) * 1e-4;
|
|
27
|
+
const lo = b.min[k] - pad, hi = b.max[k] + pad;
|
|
28
|
+
if (Math.abs(d[k]) < 1e-20) {
|
|
29
|
+
if (o[k] < lo || o[k] > hi) return false;
|
|
30
|
+
} else {
|
|
31
|
+
let t1 = (lo - o[k]) / d[k];
|
|
32
|
+
let t2 = (hi - o[k]) / d[k];
|
|
33
|
+
if (t1 > t2) [t1, t2] = [t2, t1];
|
|
34
|
+
tmin = Math.max(tmin, t1);
|
|
35
|
+
tmax = Math.min(tmax, t2);
|
|
36
|
+
if (tmin > tmax) return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return tmax >= 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let hitTriangle = -1; // first index of the triangle found by the last rayTriangles call
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Nearest triangle hit closer than `best`. With `facing` (+1 or -1) only
|
|
46
|
+
* triangles whose front side faces the ray origin count: det > 0 means the
|
|
47
|
+
* ray meets the counter-clockwise side, and `facing` is -1 for mirrored instances.
|
|
48
|
+
*/
|
|
49
|
+
function rayTriangles(o, d, mesh, best, facing = 0) {
|
|
50
|
+
const p = mesh.positions;
|
|
51
|
+
const idx = mesh.indices;
|
|
52
|
+
let tBest = best;
|
|
53
|
+
hitTriangle = -1;
|
|
54
|
+
for (let i = 0; i < idx.length; i += 3) {
|
|
55
|
+
const a = idx[i] * 3, b = idx[i + 1] * 3, c = idx[i + 2] * 3;
|
|
56
|
+
const e1x = p[b] - p[a], e1y = p[b + 1] - p[a + 1], e1z = p[b + 2] - p[a + 2];
|
|
57
|
+
const e2x = p[c] - p[a], e2y = p[c + 1] - p[a + 1], e2z = p[c + 2] - p[a + 2];
|
|
58
|
+
const px = d[1] * e2z - d[2] * e2y, py = d[2] * e2x - d[0] * e2z, pz = d[0] * e2y - d[1] * e2x;
|
|
59
|
+
const det = e1x * px + e1y * py + e1z * pz;
|
|
60
|
+
if (Math.abs(det) < 1e-18) continue;
|
|
61
|
+
if (facing && det * facing < 0) continue;
|
|
62
|
+
const inv = 1 / det;
|
|
63
|
+
const tx = o[0] - p[a], ty = o[1] - p[a + 1], tz = o[2] - p[a + 2];
|
|
64
|
+
const u = (tx * px + ty * py + tz * pz) * inv;
|
|
65
|
+
if (u < 0 || u > 1) continue;
|
|
66
|
+
const qx = ty * e1z - tz * e1y, qy = tz * e1x - tx * e1z, qz = tx * e1y - ty * e1x;
|
|
67
|
+
const v = (d[0] * qx + d[1] * qy + d[2] * qz) * inv;
|
|
68
|
+
if (v < 0 || u + v > 1) continue;
|
|
69
|
+
const t = (e2x * qx + e2y * qy + e2z * qz) * inv;
|
|
70
|
+
if (t > 1e-9 && t < tBest) {
|
|
71
|
+
tBest = t;
|
|
72
|
+
hitTriangle = i;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return tBest;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Nearest surface along a ray.
|
|
80
|
+
* @param {import('./flatten.js').RenderScene} scene
|
|
81
|
+
* @param {(entry:object, lod:number)=>import('../geometry/mesh.js').Mesh} meshOf
|
|
82
|
+
* @param {number[]} origin world ray origin
|
|
83
|
+
* @param {number[]} dir world ray direction (normalized)
|
|
84
|
+
* @param {{isVisible?: (i:number)=>boolean, maxDistance?: number, skipFlags?: number, facing?: boolean}} [opts]
|
|
85
|
+
* skipFlags: instances with any of these flag bits are ignored. facing: only
|
|
86
|
+
* surfaces whose front side faces the origin (double-sided geometry always counts).
|
|
87
|
+
* @returns {{index:number, uid:number, distance:number, point:number[], normal:number[]}|null}
|
|
88
|
+
* normal is the world-space surface normal turned against the ray.
|
|
89
|
+
*/
|
|
90
|
+
export function raycast(scene, meshOf, origin, dir, opts = {}) {
|
|
91
|
+
const { isVisible = null, maxDistance = Infinity, skipFlags = 0, facing = false } = opts;
|
|
92
|
+
const cands = [];
|
|
93
|
+
const S = scene.spheres;
|
|
94
|
+
for (let i = 0; i < scene.count; i++) {
|
|
95
|
+
if (skipFlags && scene.flags[i] & skipFlags) continue;
|
|
96
|
+
if (isVisible && !isVisible(i)) continue;
|
|
97
|
+
const entry = scene.geometries[scene.geom[i]];
|
|
98
|
+
if (entry.kind !== 'triangles') continue;
|
|
99
|
+
const t = raySphere(origin[0], origin[1], origin[2], dir[0], dir[1], dir[2], S[i * 4], S[i * 4 + 1], S[i * 4 + 2], S[i * 4 + 3]);
|
|
100
|
+
if (t < maxDistance) cands.push([t, i]);
|
|
101
|
+
}
|
|
102
|
+
cands.sort((a, b) => a[0] - b[0]);
|
|
103
|
+
let best = maxDistance;
|
|
104
|
+
let bestIndex = -1;
|
|
105
|
+
let bestTriangle = -1;
|
|
106
|
+
const normal = [0, 0, 0];
|
|
107
|
+
const m = new Float64Array(16);
|
|
108
|
+
const inv = new Float64Array(16);
|
|
109
|
+
const lo = [0, 0, 0];
|
|
110
|
+
const ld = [0, 0, 0];
|
|
111
|
+
for (const [tSphere, i] of cands) {
|
|
112
|
+
if (tSphere > best) break;
|
|
113
|
+
for (let k = 0; k < 16; k++) m[k] = scene.matrices[i * 16 + k];
|
|
114
|
+
if (!mat4.invert(inv, m)) continue;
|
|
115
|
+
mat4.transformPoint(lo, inv, origin[0], origin[1], origin[2]);
|
|
116
|
+
mat4.transformDir(ld, inv, dir[0], dir[1], dir[2]);
|
|
117
|
+
const entry = scene.geometries[scene.geom[i]];
|
|
118
|
+
if (entry.bounds && !rayBox(lo, ld, entry.bounds)) continue;
|
|
119
|
+
const mesh = meshOf(entry, 0);
|
|
120
|
+
const material = scene.materials[scene.mat[i]];
|
|
121
|
+
const twoSided = entry.doubleSided || (material && material.doubleSided);
|
|
122
|
+
const front = facing && !twoSided ? (scene.flags[i] & 1 ? -1 : 1) : 0;
|
|
123
|
+
// t is in world units because the direction was transformed linearly (not renormalized)
|
|
124
|
+
const t = rayTriangles(lo, ld, mesh, best, front);
|
|
125
|
+
if (t < best) {
|
|
126
|
+
best = t;
|
|
127
|
+
bestIndex = i;
|
|
128
|
+
bestTriangle = hitTriangle;
|
|
129
|
+
// triangle normal to world space with the inverse transpose
|
|
130
|
+
const p = mesh.positions, idx = mesh.indices;
|
|
131
|
+
const a = idx[bestTriangle] * 3, b = idx[bestTriangle + 1] * 3, c = idx[bestTriangle + 2] * 3;
|
|
132
|
+
const e1 = [p[b] - p[a], p[b + 1] - p[a + 1], p[b + 2] - p[a + 2]];
|
|
133
|
+
const e2 = [p[c] - p[a], p[c + 1] - p[a + 1], p[c + 2] - p[a + 2]];
|
|
134
|
+
const n = [e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]];
|
|
135
|
+
for (let k = 0; k < 3; k++) normal[k] = inv[k * 4] * n[0] + inv[k * 4 + 1] * n[1] + inv[k * 4 + 2] * n[2];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (bestIndex < 0) return null;
|
|
139
|
+
const nl = Math.hypot(normal[0], normal[1], normal[2]) || 1;
|
|
140
|
+
const sign = normal[0] * dir[0] + normal[1] * dir[1] + normal[2] * dir[2] > 0 ? -1 : 1;
|
|
141
|
+
return {
|
|
142
|
+
index: bestIndex,
|
|
143
|
+
uid: scene.uid[bestIndex],
|
|
144
|
+
distance: best,
|
|
145
|
+
point: [origin[0] + dir[0] * best, origin[1] + dir[1] * best, origin[2] + dir[2] * best],
|
|
146
|
+
normal: [(normal[0] / nl) * sign, (normal[1] / nl) * sign, (normal[2] / nl) * sign],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Element under a ray (for selection).
|
|
152
|
+
* @param {import('./flatten.js').RenderScene} scene
|
|
153
|
+
* @param {(entry:object, lod:number)=>import('../geometry/mesh.js').Mesh} meshOf
|
|
154
|
+
* @param {number[]} origin world ray origin
|
|
155
|
+
* @param {number[]} dir world ray direction (normalized)
|
|
156
|
+
* @param {(i:number)=>boolean} [isVisible]
|
|
157
|
+
* @returns {{index:number, uid:number, distance:number, point:number[], normal:number[]}|null}
|
|
158
|
+
*/
|
|
159
|
+
export function pick(scene, meshOf, origin, dir, isVisible = null) {
|
|
160
|
+
return raycast(scene, meshOf, origin, dir, { isVisible });
|
|
161
|
+
}
|