@es3d/es3d 1.0.0 → 1.0.1
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 +86 -86
- package/docs/SPEC.md +417 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +377 -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 +92 -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.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 +579 -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 +364 -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/wasm/kernels.js
CHANGED
|
@@ -1,385 +1,385 @@
|
|
|
1
|
-
// WebAssembly versions of the reader's hottest loops: number lists, normals,
|
|
2
|
-
// sweeps, extrusions, triangulation, primitives and the instance rows of
|
|
3
|
-
// <repeat> and <scatter>. They are built from wasm/ by `npm run wasm` and
|
|
4
|
-
// compute exactly what the JavaScript versions compute (the same IEEE
|
|
5
|
-
// operations in the same order; sin, cos, atan2 and hypot come from this
|
|
6
|
-
// page's Math). Where WebAssembly is missing or blocked, for example by a
|
|
7
|
-
// content security policy without 'wasm-unsafe-eval', every caller keeps
|
|
8
|
-
// using its JavaScript version.
|
|
9
|
-
|
|
10
|
-
import BINARY, { INSTANCES } from './kernels-binary.js';
|
|
11
|
-
|
|
12
|
-
let text = ''; // the number list being read, for the rare numbers the kernel hands back to Number()
|
|
13
|
-
let material = null; // resolves an instance row's material code (see instancesWasm)
|
|
14
|
-
|
|
15
|
-
const imports = {
|
|
16
|
-
env: {
|
|
17
|
-
sin: Math.sin,
|
|
18
|
-
cos: Math.cos,
|
|
19
|
-
atan2: Math.atan2,
|
|
20
|
-
tan: Math.tan,
|
|
21
|
-
acos: Math.acos,
|
|
22
|
-
hypot2: Math.hypot,
|
|
23
|
-
hypot3: Math.hypot,
|
|
24
|
-
number: (start, end) => Number(text.slice(start, end)),
|
|
25
|
-
material: (code) => material(code),
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function decode(base64) {
|
|
30
|
-
// native decoders where there is one (newer browsers, Node); a loop otherwise
|
|
31
|
-
if (typeof Uint8Array.fromBase64 === 'function') return Uint8Array.fromBase64(base64);
|
|
32
|
-
if (typeof Buffer === 'function') return Buffer.from(base64, 'base64');
|
|
33
|
-
const s = atob(base64);
|
|
34
|
-
const bytes = new Uint8Array(s.length);
|
|
35
|
-
for (let i = 0; i < s.length; i++) bytes[i] = s.charCodeAt(i);
|
|
36
|
-
return bytes;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function instantiate(base64) {
|
|
40
|
-
try {
|
|
41
|
-
if (typeof WebAssembly !== 'object') return null;
|
|
42
|
-
const { instance } = await WebAssembly.instantiate(decode(base64), imports);
|
|
43
|
-
return instance.exports;
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// the instance kernel is a module of its own: it needs WebAssembly SIMD
|
|
50
|
-
const [wasm, simd] = await Promise.all([instantiate(BINARY), instantiate(INSTANCES)]);
|
|
51
|
-
let enabled = wasm !== null;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Whether the module's hypot (V8's algorithm) gives this engine's Math.hypot
|
|
55
|
-
* results. The special cases (infinities, NaN, zeros) are fixed by the
|
|
56
|
-
* language; the rounding of finite values is the engine's, so values over
|
|
57
|
-
* many magnitudes are compared. Where they differ the kernels keep calling
|
|
58
|
-
* Math.hypot, which is slower (every call leaves WebAssembly and allocates).
|
|
59
|
-
*/
|
|
60
|
-
function hypotMatches() {
|
|
61
|
-
const edge = [0, -0, 3, -4, 5e-324, 1e308, Infinity, NaN];
|
|
62
|
-
for (const x of edge) for (const y of edge) if (!Object.is(wasm.hypot2(x, y), Math.hypot(x, y))) return false;
|
|
63
|
-
let seed = 12345;
|
|
64
|
-
const next = () => {
|
|
65
|
-
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
|
66
|
-
return (seed / 4294967296 - 0.5) * 10 ** ((seed % 41) - 20);
|
|
67
|
-
};
|
|
68
|
-
for (let i = 0; i < 100; i++) {
|
|
69
|
-
const x = next(), y = next(), z = next();
|
|
70
|
-
if (!Object.is(wasm.hypot3(x, y, z), Math.hypot(x, y, z)) || !Object.is(wasm.hypot2(x, y), Math.hypot(x, y))) return false;
|
|
71
|
-
}
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (wasm) wasm.set_native_hypot(hypotMatches() ? 1 : 0);
|
|
75
|
-
|
|
76
|
-
/** Whether the kernels are in use. */
|
|
77
|
-
export function wasmEnabled() {
|
|
78
|
-
return enabled;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Whether the instance kernel is in use (it needs WebAssembly SIMD as well). */
|
|
82
|
-
export function instanceKernelEnabled() {
|
|
83
|
-
return enabled && simd !== null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Turn the kernels off (or back on, when they loaded) to compare with the JavaScript versions. */
|
|
87
|
-
export function setWasmEnabled(on) {
|
|
88
|
-
enabled = Boolean(on) && wasm !== null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const heap = () => wasm.memory.buffer;
|
|
92
|
-
|
|
93
|
-
/** Copy a typed array into a module's memory; returns its address (0 when memory ran out). */
|
|
94
|
-
function put(a, module = wasm) {
|
|
95
|
-
const p = module.alloc(Math.max(1, a.byteLength));
|
|
96
|
-
if (p) new Uint8Array(module.memory.buffer, p, a.byteLength).set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength));
|
|
97
|
-
return p;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const encoder = new TextEncoder();
|
|
101
|
-
const AS = { f64: [0, Float64Array], f32: [1, Float32Array], index: [2, Uint32Array] };
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* A number list read into a typed array (`as`: 'f64', 'f32' or 'index'), or
|
|
105
|
-
* null when the JavaScript reader must handle it (the kernels are off, the
|
|
106
|
-
* text is not ASCII, or it has an error, which JavaScript then reports).
|
|
107
|
-
*/
|
|
108
|
-
export function readNumbersWasm(raw, as) {
|
|
109
|
-
if (!enabled) return null;
|
|
110
|
-
const [kind, Type] = AS[as];
|
|
111
|
-
const n = raw.length;
|
|
112
|
-
wasm.reset();
|
|
113
|
-
const sp = wasm.alloc(Math.max(1, n));
|
|
114
|
-
const op = sp && wasm.alloc((Math.floor(n / 2) + 1) * Type.BYTES_PER_ELEMENT);
|
|
115
|
-
if (!op) return null;
|
|
116
|
-
const r = encoder.encodeInto(raw, new Uint8Array(heap(), sp, n));
|
|
117
|
-
if (r.read !== n || r.written !== n) return null;
|
|
118
|
-
text = raw;
|
|
119
|
-
const count = wasm.numbers(sp, n, op, kind);
|
|
120
|
-
text = '';
|
|
121
|
-
if (count < 0) return null;
|
|
122
|
-
return new Type(heap(), op, count).slice();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* computeNormals for Float32Array positions and uvs and Uint32Array indices,
|
|
127
|
-
* as {positions, normals, uvs, indices}, or null to use the JavaScript version.
|
|
128
|
-
*/
|
|
129
|
-
export function computeNormalsWasm(positions, indices, crease, cosCrease, uvs) {
|
|
130
|
-
if (!enabled || !(positions instanceof Float32Array) || !(indices instanceof Uint32Array) || (uvs && !(uvs instanceof Float32Array))) return null;
|
|
131
|
-
wasm.reset();
|
|
132
|
-
const pp = put(positions), ip = put(indices), tp = uvs ? put(uvs) : 0;
|
|
133
|
-
if (!pp || !ip || (uvs && !tp)) return null;
|
|
134
|
-
const res = wasm.normals(pp, positions.length / 3, ip, indices.length, tp, crease, cosCrease);
|
|
135
|
-
if (!res) return null;
|
|
136
|
-
const [vc, ic, p, nrm, t, i] = new Uint32Array(heap(), res, 6);
|
|
137
|
-
return {
|
|
138
|
-
positions: new Float32Array(heap(), p, vc * 3).slice(),
|
|
139
|
-
normals: new Float32Array(heap(), nrm, vc * 3).slice(),
|
|
140
|
-
uvs: new Float32Array(heap(), t, vc * 2).slice(),
|
|
141
|
-
indices: new Uint32Array(heap(), i, ic).slice(),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const f64 = (a) => (a instanceof Float64Array ? a : Float64Array.from(a));
|
|
146
|
-
const u32 = (a) => (a instanceof Uint32Array ? a : Uint32Array.from(a));
|
|
147
|
-
const u8 = (a) => (a instanceof Uint8Array ? a : Uint8Array.from(a));
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Writes contours ({points, smooth, isWall}) as {n, points, smooth, isWall}
|
|
151
|
-
* records; returns the table's address, or 0 when memory ran out.
|
|
152
|
-
*/
|
|
153
|
-
function contourTable(list) {
|
|
154
|
-
const rows = new Int32Array(list.length * 4);
|
|
155
|
-
for (let k = 0; k < list.length; k++) {
|
|
156
|
-
const c = list[k];
|
|
157
|
-
const p = put(f64(c.points));
|
|
158
|
-
const sm = c.smooth ? put(u8(c.smooth)) : 0;
|
|
159
|
-
if (!p || (c.smooth && !sm)) return 0;
|
|
160
|
-
rows[k * 4] = c.points.length / 2;
|
|
161
|
-
rows[k * 4 + 1] = p;
|
|
162
|
-
rows[k * 4 + 2] = sm;
|
|
163
|
-
rows[k * 4 + 3] = c.isWall === false ? 0 : 1;
|
|
164
|
-
}
|
|
165
|
-
return put(rows);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** {first contour, contour count} per region, appending each region's contours to `list`. */
|
|
169
|
-
function regionRows(regions, list) {
|
|
170
|
-
const rows = new Int32Array(Math.max(1, regions.length * 2));
|
|
171
|
-
regions.forEach((r, k) => {
|
|
172
|
-
rows[k * 2] = list.length;
|
|
173
|
-
rows[k * 2 + 1] = 1 + r.holes.length;
|
|
174
|
-
list.push(r.outer, ...r.holes);
|
|
175
|
-
});
|
|
176
|
-
return rows;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** A kernel's mesh, copied out of kernel memory into one buffer (one allocation instead of four). */
|
|
180
|
-
const meshOut = (res, extra) => {
|
|
181
|
-
const [vc, ic, p, nrm, t, i] = new Uint32Array(heap(), res, 6);
|
|
182
|
-
const out = new ArrayBuffer((vc * 8 + ic) * 4);
|
|
183
|
-
const positions = new Float32Array(out, 0, vc * 3);
|
|
184
|
-
const normals = new Float32Array(out, vc * 12, vc * 3);
|
|
185
|
-
const uvs = new Float32Array(out, vc * 24, vc * 2);
|
|
186
|
-
const indices = new Uint32Array(out, vc * 32, ic);
|
|
187
|
-
positions.set(new Float32Array(heap(), p, vc * 3));
|
|
188
|
-
normals.set(new Float32Array(heap(), nrm, vc * 3));
|
|
189
|
-
uvs.set(new Float32Array(heap(), t, vc * 2));
|
|
190
|
-
indices.set(new Uint32Array(heap(), i, ic));
|
|
191
|
-
return { positions, normals, uvs, indices, ...extra };
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* sweepMesh after its profile is known: spines [{points, closed}], the
|
|
196
|
-
* profile contours, the circle's points (circle profiles) or the regions to
|
|
197
|
-
* cap (shaped profiles). Returns {positions, normals, uvs, indices, open,
|
|
198
|
-
* degenerate: one flag per spine, errors: triangulation error per cap
|
|
199
|
-
* region}, or null to use the JavaScript version.
|
|
200
|
-
*/
|
|
201
|
-
export function sweepWasm(spines, contours, circle, regions, twist, taper, caps) {
|
|
202
|
-
if (!enabled) return null;
|
|
203
|
-
wasm.reset();
|
|
204
|
-
const list = [...contours];
|
|
205
|
-
const regionTab = circle ? null : regionRows(regions, list);
|
|
206
|
-
const table = contourTable(list);
|
|
207
|
-
const spineRows = new Int32Array(Math.max(1, spines.length * 3));
|
|
208
|
-
for (let k = 0; k < spines.length; k++) {
|
|
209
|
-
spineRows[k * 3] = put(f64(spines[k].points));
|
|
210
|
-
spineRows[k * 3 + 1] = spines[k].points.length / 3;
|
|
211
|
-
spineRows[k * 3 + 2] = spines[k].closed ? 1 : 0;
|
|
212
|
-
if (!spineRows[k * 3]) return null;
|
|
213
|
-
}
|
|
214
|
-
const profile = put(Int32Array.from(contours, (_, k) => k));
|
|
215
|
-
const spineTab = put(spineRows);
|
|
216
|
-
const capData = circle ? put(f64(circle)) : put(regionTab);
|
|
217
|
-
const header = put(
|
|
218
|
-
Int32Array.of(spines.length, spineTab, list.length, table, contours.length, profile, circle ? 1 : 2, circle ? circle.length / 2 : regions.length, capData, caps ? 1 : 0),
|
|
219
|
-
);
|
|
220
|
-
if (!table || !profile || !spineTab || !capData || !header) return null;
|
|
221
|
-
const res = wasm.sweep(header, twist, taper);
|
|
222
|
-
if (!res) return null;
|
|
223
|
-
const [open, deg, errs] = new Uint32Array(heap(), res + 24, 3);
|
|
224
|
-
return meshOut(res, {
|
|
225
|
-
open: open !== 0,
|
|
226
|
-
degenerate: Array.from(new Uint8Array(heap(), deg, spines.length), (d) => d !== 0),
|
|
227
|
-
errors: circle ? [] : Array.from(new Float64Array(heap(), errs, regions.length)),
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* triangulate(data, holeIndices) as an array of vertex indices, or null to
|
|
233
|
-
* use the JavaScript version (also when two holes tie for the sort order).
|
|
234
|
-
*/
|
|
235
|
-
export function triangulateWasm(data, holeIndices) {
|
|
236
|
-
if (!enabled) return null;
|
|
237
|
-
wasm.reset();
|
|
238
|
-
const dp = put(f64(data));
|
|
239
|
-
const holes = holeIndices && holeIndices.length ? Int32Array.from(holeIndices) : null;
|
|
240
|
-
const hp = holes ? put(holes) : 0;
|
|
241
|
-
if (!dp || (holes && !hp)) return null;
|
|
242
|
-
let res;
|
|
243
|
-
try {
|
|
244
|
-
res = wasm.triangulate(dp, data.length, hp, holes ? holes.length : 0);
|
|
245
|
-
} catch {
|
|
246
|
-
return null; // stack exhausted on pathological input: JavaScript has a deeper stack
|
|
247
|
-
}
|
|
248
|
-
if (!res) return null;
|
|
249
|
-
const [count, p] = new Uint32Array(heap(), res, 2);
|
|
250
|
-
return count ? Array.from(new Uint32Array(heap(), p, count)) : [];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* extrudeMesh from a shape ({regions, walls}) with the bevel profile rings
|
|
255
|
-
* {inset, z, nr, nz} and bounds from JavaScript. Returns {positions,
|
|
256
|
-
* normals, uvs, indices, errors: triangulation error per region}, or null
|
|
257
|
-
* to use the JavaScript version.
|
|
258
|
-
*/
|
|
259
|
-
export function extrudeWasm(shape, bevel, rings, z0, zTop, minX, minY, S) {
|
|
260
|
-
if (!enabled) return null;
|
|
261
|
-
wasm.reset();
|
|
262
|
-
const list = [];
|
|
263
|
-
const regionTab = put(regionRows(shape.regions, list));
|
|
264
|
-
const walls = put(Int32Array.from(shape.walls, (_, k) => list.length + k));
|
|
265
|
-
list.push(...shape.walls);
|
|
266
|
-
const table = contourTable(list);
|
|
267
|
-
const ringData = new Float64Array(Math.max(1, rings.length * 4));
|
|
268
|
-
rings.forEach((r, k) => ringData.set([r.inset, r.z, r.nr, r.nz], k * 4));
|
|
269
|
-
const ringPtr = put(ringData);
|
|
270
|
-
const header = put(Int32Array.of(list.length, table, shape.regions.length, regionTab, shape.walls.length, walls, rings.length, ringPtr));
|
|
271
|
-
if (!regionTab || !walls || !table || !ringPtr || !header) return null;
|
|
272
|
-
const res = wasm.extrude(header, bevel, z0, zTop, minX, minY, S);
|
|
273
|
-
if (!res) return null;
|
|
274
|
-
const errs = new Uint32Array(heap(), res + 24, 1)[0];
|
|
275
|
-
return meshOut(res, { errors: Array.from(new Float64Array(heap(), errs, shape.regions.length)) });
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** measureSphereDeviation(w) for w >= 3, or null to use the JavaScript version. */
|
|
279
|
-
export function sphereDeviationWasm(w) {
|
|
280
|
-
if (!enabled) return null;
|
|
281
|
-
wasm.reset();
|
|
282
|
-
const v = wasm.sphere_deviation(w);
|
|
283
|
-
return v < 0 ? null : v;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const whole = (...v) => v.every((x) => Number.isInteger(x) && x >= 0 && x < 2 ** 30);
|
|
287
|
-
const prim = (res) => (res ? meshOut(res) : null);
|
|
288
|
-
|
|
289
|
-
/** unitSphere, unitCylinder, unitCapsule, unitTorus and roundedBox ({positions, normals, uvs, indices}), or null. */
|
|
290
|
-
export const primitivesWasm = {
|
|
291
|
-
sphere: (segments) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.sphere(segments))) : null),
|
|
292
|
-
cylinder: (top, segments, caps) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.cylinder(top, segments, caps ? 1 : 0))) : null),
|
|
293
|
-
capsule: (L, segments) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.capsule(L, segments))) : null),
|
|
294
|
-
torus: (t, arcDeg, segments, tubeSegments) => (enabled && whole(segments, tubeSegments) ? (wasm.reset(), prim(wasm.torus(t, arcDeg, segments, tubeSegments))) : null),
|
|
295
|
-
roundedBox: (w, h, d, r, seg) => (enabled && whole(seg) && seg >= 1 ? (wasm.reset(), prim(wasm.rounded_box(w, h, d, r, seg))) : null),
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
/** catmullRom for two or more points (a Float64Array), or null. */
|
|
299
|
-
export function catmullRomWasm(points, closed, subdivisions) {
|
|
300
|
-
if (!enabled || !whole(subdivisions) || points.length < 6 || points.length % 3) return null;
|
|
301
|
-
wasm.reset();
|
|
302
|
-
const p = put(f64(points));
|
|
303
|
-
const res = p && wasm.catmull_rom(p, points.length / 3, closed ? 1 : 0, subdivisions);
|
|
304
|
-
if (!res) return null;
|
|
305
|
-
const [count, out] = new Uint32Array(heap(), res, 2);
|
|
306
|
-
return new Float64Array(heap(), out, count * 3).slice();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** gearOutline ({outline: {points, smooth, closed}, outerRadius, rootRadius}), or null. */
|
|
310
|
-
export function gearOutlineWasm(teeth, pressureAngleDeg, flankSamples, arcSamples) {
|
|
311
|
-
if (!enabled || !whole(teeth, flankSamples, arcSamples) || teeth < 1) return null;
|
|
312
|
-
wasm.reset();
|
|
313
|
-
const res = wasm.gear_outline(teeth, pressureAngleDeg, flankSamples, arcSamples);
|
|
314
|
-
if (!res) return null;
|
|
315
|
-
const [count, pts, smooth, radii] = new Uint32Array(heap(), res, 4);
|
|
316
|
-
const [outerRadius, rootRadius] = new Float64Array(heap(), radii, 2);
|
|
317
|
-
return {
|
|
318
|
-
outline: { points: new Float64Array(heap(), pts, count * 2).slice(), smooth: new Uint8Array(heap(), smooth, count).slice(), closed: true },
|
|
319
|
-
outerRadius,
|
|
320
|
-
rootRadius,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Header layouts of the instance kernel (the enums in wasm/simd/instances.c).
|
|
325
|
-
export const INSTANCE_HEADER = {
|
|
326
|
-
KIND: 0, FIRST: 1, COUNT: 2, STATE: 3, MODE: 4, NX: 5, NZ: 6, RANDOM: 7, CHOICES: 8,
|
|
327
|
-
OPS: 9, NOPS: 10, MATS: 11, ROWS: 12, NROWS: 13, BOUNDS: 14, MATERIALS: 15, LODS: 16, OUTER_LOD: 17, SWITCH: 18, SLOTS: 19,
|
|
328
|
-
OUT: 20, SIZE: 29,
|
|
329
|
-
};
|
|
330
|
-
export const INSTANCE_PARAMS = { WORLD: 0, ACC: 16, STEP: 32, SPACING: 48, JITTER: 50, RADIUS: 51, AREA: 52, ROTATION: 54, SCALE: 60, BOUNDS: 66, SIZE: 72 };
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Instance rows of placements [FIRST, FIRST + COUNT) of a <repeat> or
|
|
334
|
-
* <scatter> whose subtree was recorded as `template` ({ops, mats, table,
|
|
335
|
-
* bounds}, see recordTemplate in src/scene/flatten.js). The headers `h`
|
|
336
|
-
* (Int32Array) and `f` (Float64Array) and the table `materials` (material
|
|
337
|
-
* index per material code, -1 until resolveMaterial(code) has given it)
|
|
338
|
-
* carry the generator state, the accumulated repeat step, the scene bounds
|
|
339
|
-
* and the materials from one call to the next. Returns views of kernel
|
|
340
|
-
* memory, valid until the next kernel call, or null.
|
|
341
|
-
*/
|
|
342
|
-
export function instancesWasm(h, f, template, materials, resolveMaterial) {
|
|
343
|
-
if (!enabled || !simd) return null;
|
|
344
|
-
simd.reset();
|
|
345
|
-
const H = INSTANCE_HEADER;
|
|
346
|
-
h[H.OPS] = put(template.ops, simd);
|
|
347
|
-
h[H.MATS] = put(template.mats, simd);
|
|
348
|
-
h[H.ROWS] = put(template.table, simd);
|
|
349
|
-
h[H.BOUNDS] = put(template.bounds, simd);
|
|
350
|
-
h[H.MATERIALS] = put(materials, simd);
|
|
351
|
-
const hp = put(h, simd), fp = put(f, simd);
|
|
352
|
-
if (!h[H.OPS] || !h[H.MATS] || !h[H.ROWS] || !h[H.BOUNDS] || !h[H.MATERIALS] || !hp || !fp) return null;
|
|
353
|
-
material = resolveMaterial;
|
|
354
|
-
try {
|
|
355
|
-
if (!simd.instances(hp, fp)) return null;
|
|
356
|
-
} finally {
|
|
357
|
-
material = null;
|
|
358
|
-
}
|
|
359
|
-
const buf = simd.memory.buffer;
|
|
360
|
-
h.set(new Int32Array(buf, hp, H.SIZE));
|
|
361
|
-
f.set(new Float64Array(buf, fp, INSTANCE_PARAMS.SIZE));
|
|
362
|
-
materials.set(new Int32Array(buf, h[H.MATERIALS], materials.length));
|
|
363
|
-
const n = h[H.COUNT] * h[H.NROWS];
|
|
364
|
-
const o = H.OUT;
|
|
365
|
-
return {
|
|
366
|
-
matrices: new Float32Array(buf, h[o], n * 16),
|
|
367
|
-
spheres: new Float32Array(buf, h[o + 1], n * 4),
|
|
368
|
-
geom: new Uint32Array(buf, h[o + 2], n),
|
|
369
|
-
mat: new Uint32Array(buf, h[o + 3], n),
|
|
370
|
-
uid: new Uint32Array(buf, h[o + 4], n),
|
|
371
|
-
lodNode: new Int32Array(buf, h[o + 5], n),
|
|
372
|
-
lodChild: new Uint16Array(buf, h[o + 6], n),
|
|
373
|
-
flags: new Uint8Array(buf, h[o + 7], n),
|
|
374
|
-
switches: new Float64Array(buf, h[o + 8], h[H.COUNT] * h[H.LODS] * 4),
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/** es3dCos(x) and es3dSin(x) as the instance kernel computes them (for the tests), or null. */
|
|
379
|
-
export function cosSinWasm(x) {
|
|
380
|
-
if (!enabled || !simd) return null;
|
|
381
|
-
simd.reset();
|
|
382
|
-
const p = simd.alloc(16);
|
|
383
|
-
simd.cos_sin(x, p);
|
|
384
|
-
return Array.from(new Float64Array(simd.memory.buffer, p, 2));
|
|
385
|
-
}
|
|
1
|
+
// WebAssembly versions of the reader's hottest loops: number lists, normals,
|
|
2
|
+
// sweeps, extrusions, triangulation, primitives and the instance rows of
|
|
3
|
+
// <repeat> and <scatter>. They are built from wasm/ by `npm run wasm` and
|
|
4
|
+
// compute exactly what the JavaScript versions compute (the same IEEE
|
|
5
|
+
// operations in the same order; sin, cos, atan2 and hypot come from this
|
|
6
|
+
// page's Math). Where WebAssembly is missing or blocked, for example by a
|
|
7
|
+
// content security policy without 'wasm-unsafe-eval', every caller keeps
|
|
8
|
+
// using its JavaScript version.
|
|
9
|
+
|
|
10
|
+
import BINARY, { INSTANCES } from './kernels-binary.js';
|
|
11
|
+
|
|
12
|
+
let text = ''; // the number list being read, for the rare numbers the kernel hands back to Number()
|
|
13
|
+
let material = null; // resolves an instance row's material code (see instancesWasm)
|
|
14
|
+
|
|
15
|
+
const imports = {
|
|
16
|
+
env: {
|
|
17
|
+
sin: Math.sin,
|
|
18
|
+
cos: Math.cos,
|
|
19
|
+
atan2: Math.atan2,
|
|
20
|
+
tan: Math.tan,
|
|
21
|
+
acos: Math.acos,
|
|
22
|
+
hypot2: Math.hypot,
|
|
23
|
+
hypot3: Math.hypot,
|
|
24
|
+
number: (start, end) => Number(text.slice(start, end)),
|
|
25
|
+
material: (code) => material(code),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function decode(base64) {
|
|
30
|
+
// native decoders where there is one (newer browsers, Node); a loop otherwise
|
|
31
|
+
if (typeof Uint8Array.fromBase64 === 'function') return Uint8Array.fromBase64(base64);
|
|
32
|
+
if (typeof Buffer === 'function') return Buffer.from(base64, 'base64');
|
|
33
|
+
const s = atob(base64);
|
|
34
|
+
const bytes = new Uint8Array(s.length);
|
|
35
|
+
for (let i = 0; i < s.length; i++) bytes[i] = s.charCodeAt(i);
|
|
36
|
+
return bytes;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function instantiate(base64) {
|
|
40
|
+
try {
|
|
41
|
+
if (typeof WebAssembly !== 'object') return null;
|
|
42
|
+
const { instance } = await WebAssembly.instantiate(decode(base64), imports);
|
|
43
|
+
return instance.exports;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// the instance kernel is a module of its own: it needs WebAssembly SIMD
|
|
50
|
+
const [wasm, simd] = await Promise.all([instantiate(BINARY), instantiate(INSTANCES)]);
|
|
51
|
+
let enabled = wasm !== null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether the module's hypot (V8's algorithm) gives this engine's Math.hypot
|
|
55
|
+
* results. The special cases (infinities, NaN, zeros) are fixed by the
|
|
56
|
+
* language; the rounding of finite values is the engine's, so values over
|
|
57
|
+
* many magnitudes are compared. Where they differ the kernels keep calling
|
|
58
|
+
* Math.hypot, which is slower (every call leaves WebAssembly and allocates).
|
|
59
|
+
*/
|
|
60
|
+
function hypotMatches() {
|
|
61
|
+
const edge = [0, -0, 3, -4, 5e-324, 1e308, Infinity, NaN];
|
|
62
|
+
for (const x of edge) for (const y of edge) if (!Object.is(wasm.hypot2(x, y), Math.hypot(x, y))) return false;
|
|
63
|
+
let seed = 12345;
|
|
64
|
+
const next = () => {
|
|
65
|
+
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
|
66
|
+
return (seed / 4294967296 - 0.5) * 10 ** ((seed % 41) - 20);
|
|
67
|
+
};
|
|
68
|
+
for (let i = 0; i < 100; i++) {
|
|
69
|
+
const x = next(), y = next(), z = next();
|
|
70
|
+
if (!Object.is(wasm.hypot3(x, y, z), Math.hypot(x, y, z)) || !Object.is(wasm.hypot2(x, y), Math.hypot(x, y))) return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (wasm) wasm.set_native_hypot(hypotMatches() ? 1 : 0);
|
|
75
|
+
|
|
76
|
+
/** Whether the kernels are in use. */
|
|
77
|
+
export function wasmEnabled() {
|
|
78
|
+
return enabled;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Whether the instance kernel is in use (it needs WebAssembly SIMD as well). */
|
|
82
|
+
export function instanceKernelEnabled() {
|
|
83
|
+
return enabled && simd !== null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Turn the kernels off (or back on, when they loaded) to compare with the JavaScript versions. */
|
|
87
|
+
export function setWasmEnabled(on) {
|
|
88
|
+
enabled = Boolean(on) && wasm !== null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const heap = () => wasm.memory.buffer;
|
|
92
|
+
|
|
93
|
+
/** Copy a typed array into a module's memory; returns its address (0 when memory ran out). */
|
|
94
|
+
function put(a, module = wasm) {
|
|
95
|
+
const p = module.alloc(Math.max(1, a.byteLength));
|
|
96
|
+
if (p) new Uint8Array(module.memory.buffer, p, a.byteLength).set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength));
|
|
97
|
+
return p;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const encoder = new TextEncoder();
|
|
101
|
+
const AS = { f64: [0, Float64Array], f32: [1, Float32Array], index: [2, Uint32Array] };
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A number list read into a typed array (`as`: 'f64', 'f32' or 'index'), or
|
|
105
|
+
* null when the JavaScript reader must handle it (the kernels are off, the
|
|
106
|
+
* text is not ASCII, or it has an error, which JavaScript then reports).
|
|
107
|
+
*/
|
|
108
|
+
export function readNumbersWasm(raw, as) {
|
|
109
|
+
if (!enabled) return null;
|
|
110
|
+
const [kind, Type] = AS[as];
|
|
111
|
+
const n = raw.length;
|
|
112
|
+
wasm.reset();
|
|
113
|
+
const sp = wasm.alloc(Math.max(1, n));
|
|
114
|
+
const op = sp && wasm.alloc((Math.floor(n / 2) + 1) * Type.BYTES_PER_ELEMENT);
|
|
115
|
+
if (!op) return null;
|
|
116
|
+
const r = encoder.encodeInto(raw, new Uint8Array(heap(), sp, n));
|
|
117
|
+
if (r.read !== n || r.written !== n) return null;
|
|
118
|
+
text = raw;
|
|
119
|
+
const count = wasm.numbers(sp, n, op, kind);
|
|
120
|
+
text = '';
|
|
121
|
+
if (count < 0) return null;
|
|
122
|
+
return new Type(heap(), op, count).slice();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* computeNormals for Float32Array positions and uvs and Uint32Array indices,
|
|
127
|
+
* as {positions, normals, uvs, indices}, or null to use the JavaScript version.
|
|
128
|
+
*/
|
|
129
|
+
export function computeNormalsWasm(positions, indices, crease, cosCrease, uvs) {
|
|
130
|
+
if (!enabled || !(positions instanceof Float32Array) || !(indices instanceof Uint32Array) || (uvs && !(uvs instanceof Float32Array))) return null;
|
|
131
|
+
wasm.reset();
|
|
132
|
+
const pp = put(positions), ip = put(indices), tp = uvs ? put(uvs) : 0;
|
|
133
|
+
if (!pp || !ip || (uvs && !tp)) return null;
|
|
134
|
+
const res = wasm.normals(pp, positions.length / 3, ip, indices.length, tp, crease, cosCrease);
|
|
135
|
+
if (!res) return null;
|
|
136
|
+
const [vc, ic, p, nrm, t, i] = new Uint32Array(heap(), res, 6);
|
|
137
|
+
return {
|
|
138
|
+
positions: new Float32Array(heap(), p, vc * 3).slice(),
|
|
139
|
+
normals: new Float32Array(heap(), nrm, vc * 3).slice(),
|
|
140
|
+
uvs: new Float32Array(heap(), t, vc * 2).slice(),
|
|
141
|
+
indices: new Uint32Array(heap(), i, ic).slice(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const f64 = (a) => (a instanceof Float64Array ? a : Float64Array.from(a));
|
|
146
|
+
const u32 = (a) => (a instanceof Uint32Array ? a : Uint32Array.from(a));
|
|
147
|
+
const u8 = (a) => (a instanceof Uint8Array ? a : Uint8Array.from(a));
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Writes contours ({points, smooth, isWall}) as {n, points, smooth, isWall}
|
|
151
|
+
* records; returns the table's address, or 0 when memory ran out.
|
|
152
|
+
*/
|
|
153
|
+
function contourTable(list) {
|
|
154
|
+
const rows = new Int32Array(list.length * 4);
|
|
155
|
+
for (let k = 0; k < list.length; k++) {
|
|
156
|
+
const c = list[k];
|
|
157
|
+
const p = put(f64(c.points));
|
|
158
|
+
const sm = c.smooth ? put(u8(c.smooth)) : 0;
|
|
159
|
+
if (!p || (c.smooth && !sm)) return 0;
|
|
160
|
+
rows[k * 4] = c.points.length / 2;
|
|
161
|
+
rows[k * 4 + 1] = p;
|
|
162
|
+
rows[k * 4 + 2] = sm;
|
|
163
|
+
rows[k * 4 + 3] = c.isWall === false ? 0 : 1;
|
|
164
|
+
}
|
|
165
|
+
return put(rows);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** {first contour, contour count} per region, appending each region's contours to `list`. */
|
|
169
|
+
function regionRows(regions, list) {
|
|
170
|
+
const rows = new Int32Array(Math.max(1, regions.length * 2));
|
|
171
|
+
regions.forEach((r, k) => {
|
|
172
|
+
rows[k * 2] = list.length;
|
|
173
|
+
rows[k * 2 + 1] = 1 + r.holes.length;
|
|
174
|
+
list.push(r.outer, ...r.holes);
|
|
175
|
+
});
|
|
176
|
+
return rows;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A kernel's mesh, copied out of kernel memory into one buffer (one allocation instead of four). */
|
|
180
|
+
const meshOut = (res, extra) => {
|
|
181
|
+
const [vc, ic, p, nrm, t, i] = new Uint32Array(heap(), res, 6);
|
|
182
|
+
const out = new ArrayBuffer((vc * 8 + ic) * 4);
|
|
183
|
+
const positions = new Float32Array(out, 0, vc * 3);
|
|
184
|
+
const normals = new Float32Array(out, vc * 12, vc * 3);
|
|
185
|
+
const uvs = new Float32Array(out, vc * 24, vc * 2);
|
|
186
|
+
const indices = new Uint32Array(out, vc * 32, ic);
|
|
187
|
+
positions.set(new Float32Array(heap(), p, vc * 3));
|
|
188
|
+
normals.set(new Float32Array(heap(), nrm, vc * 3));
|
|
189
|
+
uvs.set(new Float32Array(heap(), t, vc * 2));
|
|
190
|
+
indices.set(new Uint32Array(heap(), i, ic));
|
|
191
|
+
return { positions, normals, uvs, indices, ...extra };
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* sweepMesh after its profile is known: spines [{points, closed}], the
|
|
196
|
+
* profile contours, the circle's points (circle profiles) or the regions to
|
|
197
|
+
* cap (shaped profiles). Returns {positions, normals, uvs, indices, open,
|
|
198
|
+
* degenerate: one flag per spine, errors: triangulation error per cap
|
|
199
|
+
* region}, or null to use the JavaScript version.
|
|
200
|
+
*/
|
|
201
|
+
export function sweepWasm(spines, contours, circle, regions, twist, taper, caps) {
|
|
202
|
+
if (!enabled) return null;
|
|
203
|
+
wasm.reset();
|
|
204
|
+
const list = [...contours];
|
|
205
|
+
const regionTab = circle ? null : regionRows(regions, list);
|
|
206
|
+
const table = contourTable(list);
|
|
207
|
+
const spineRows = new Int32Array(Math.max(1, spines.length * 3));
|
|
208
|
+
for (let k = 0; k < spines.length; k++) {
|
|
209
|
+
spineRows[k * 3] = put(f64(spines[k].points));
|
|
210
|
+
spineRows[k * 3 + 1] = spines[k].points.length / 3;
|
|
211
|
+
spineRows[k * 3 + 2] = spines[k].closed ? 1 : 0;
|
|
212
|
+
if (!spineRows[k * 3]) return null;
|
|
213
|
+
}
|
|
214
|
+
const profile = put(Int32Array.from(contours, (_, k) => k));
|
|
215
|
+
const spineTab = put(spineRows);
|
|
216
|
+
const capData = circle ? put(f64(circle)) : put(regionTab);
|
|
217
|
+
const header = put(
|
|
218
|
+
Int32Array.of(spines.length, spineTab, list.length, table, contours.length, profile, circle ? 1 : 2, circle ? circle.length / 2 : regions.length, capData, caps ? 1 : 0),
|
|
219
|
+
);
|
|
220
|
+
if (!table || !profile || !spineTab || !capData || !header) return null;
|
|
221
|
+
const res = wasm.sweep(header, twist, taper);
|
|
222
|
+
if (!res) return null;
|
|
223
|
+
const [open, deg, errs] = new Uint32Array(heap(), res + 24, 3);
|
|
224
|
+
return meshOut(res, {
|
|
225
|
+
open: open !== 0,
|
|
226
|
+
degenerate: Array.from(new Uint8Array(heap(), deg, spines.length), (d) => d !== 0),
|
|
227
|
+
errors: circle ? [] : Array.from(new Float64Array(heap(), errs, regions.length)),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* triangulate(data, holeIndices) as an array of vertex indices, or null to
|
|
233
|
+
* use the JavaScript version (also when two holes tie for the sort order).
|
|
234
|
+
*/
|
|
235
|
+
export function triangulateWasm(data, holeIndices) {
|
|
236
|
+
if (!enabled) return null;
|
|
237
|
+
wasm.reset();
|
|
238
|
+
const dp = put(f64(data));
|
|
239
|
+
const holes = holeIndices && holeIndices.length ? Int32Array.from(holeIndices) : null;
|
|
240
|
+
const hp = holes ? put(holes) : 0;
|
|
241
|
+
if (!dp || (holes && !hp)) return null;
|
|
242
|
+
let res;
|
|
243
|
+
try {
|
|
244
|
+
res = wasm.triangulate(dp, data.length, hp, holes ? holes.length : 0);
|
|
245
|
+
} catch {
|
|
246
|
+
return null; // stack exhausted on pathological input: JavaScript has a deeper stack
|
|
247
|
+
}
|
|
248
|
+
if (!res) return null;
|
|
249
|
+
const [count, p] = new Uint32Array(heap(), res, 2);
|
|
250
|
+
return count ? Array.from(new Uint32Array(heap(), p, count)) : [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* extrudeMesh from a shape ({regions, walls}) with the bevel profile rings
|
|
255
|
+
* {inset, z, nr, nz} and bounds from JavaScript. Returns {positions,
|
|
256
|
+
* normals, uvs, indices, errors: triangulation error per region}, or null
|
|
257
|
+
* to use the JavaScript version.
|
|
258
|
+
*/
|
|
259
|
+
export function extrudeWasm(shape, bevel, rings, z0, zTop, minX, minY, S) {
|
|
260
|
+
if (!enabled) return null;
|
|
261
|
+
wasm.reset();
|
|
262
|
+
const list = [];
|
|
263
|
+
const regionTab = put(regionRows(shape.regions, list));
|
|
264
|
+
const walls = put(Int32Array.from(shape.walls, (_, k) => list.length + k));
|
|
265
|
+
list.push(...shape.walls);
|
|
266
|
+
const table = contourTable(list);
|
|
267
|
+
const ringData = new Float64Array(Math.max(1, rings.length * 4));
|
|
268
|
+
rings.forEach((r, k) => ringData.set([r.inset, r.z, r.nr, r.nz], k * 4));
|
|
269
|
+
const ringPtr = put(ringData);
|
|
270
|
+
const header = put(Int32Array.of(list.length, table, shape.regions.length, regionTab, shape.walls.length, walls, rings.length, ringPtr));
|
|
271
|
+
if (!regionTab || !walls || !table || !ringPtr || !header) return null;
|
|
272
|
+
const res = wasm.extrude(header, bevel, z0, zTop, minX, minY, S);
|
|
273
|
+
if (!res) return null;
|
|
274
|
+
const errs = new Uint32Array(heap(), res + 24, 1)[0];
|
|
275
|
+
return meshOut(res, { errors: Array.from(new Float64Array(heap(), errs, shape.regions.length)) });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** measureSphereDeviation(w) for w >= 3, or null to use the JavaScript version. */
|
|
279
|
+
export function sphereDeviationWasm(w) {
|
|
280
|
+
if (!enabled) return null;
|
|
281
|
+
wasm.reset();
|
|
282
|
+
const v = wasm.sphere_deviation(w);
|
|
283
|
+
return v < 0 ? null : v;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const whole = (...v) => v.every((x) => Number.isInteger(x) && x >= 0 && x < 2 ** 30);
|
|
287
|
+
const prim = (res) => (res ? meshOut(res) : null);
|
|
288
|
+
|
|
289
|
+
/** unitSphere, unitCylinder, unitCapsule, unitTorus and roundedBox ({positions, normals, uvs, indices}), or null. */
|
|
290
|
+
export const primitivesWasm = {
|
|
291
|
+
sphere: (segments) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.sphere(segments))) : null),
|
|
292
|
+
cylinder: (top, segments, caps) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.cylinder(top, segments, caps ? 1 : 0))) : null),
|
|
293
|
+
capsule: (L, segments) => (enabled && whole(segments) ? (wasm.reset(), prim(wasm.capsule(L, segments))) : null),
|
|
294
|
+
torus: (t, arcDeg, segments, tubeSegments) => (enabled && whole(segments, tubeSegments) ? (wasm.reset(), prim(wasm.torus(t, arcDeg, segments, tubeSegments))) : null),
|
|
295
|
+
roundedBox: (w, h, d, r, seg) => (enabled && whole(seg) && seg >= 1 ? (wasm.reset(), prim(wasm.rounded_box(w, h, d, r, seg))) : null),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/** catmullRom for two or more points (a Float64Array), or null. */
|
|
299
|
+
export function catmullRomWasm(points, closed, subdivisions) {
|
|
300
|
+
if (!enabled || !whole(subdivisions) || points.length < 6 || points.length % 3) return null;
|
|
301
|
+
wasm.reset();
|
|
302
|
+
const p = put(f64(points));
|
|
303
|
+
const res = p && wasm.catmull_rom(p, points.length / 3, closed ? 1 : 0, subdivisions);
|
|
304
|
+
if (!res) return null;
|
|
305
|
+
const [count, out] = new Uint32Array(heap(), res, 2);
|
|
306
|
+
return new Float64Array(heap(), out, count * 3).slice();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** gearOutline ({outline: {points, smooth, closed}, outerRadius, rootRadius}), or null. */
|
|
310
|
+
export function gearOutlineWasm(teeth, pressureAngleDeg, flankSamples, arcSamples) {
|
|
311
|
+
if (!enabled || !whole(teeth, flankSamples, arcSamples) || teeth < 1) return null;
|
|
312
|
+
wasm.reset();
|
|
313
|
+
const res = wasm.gear_outline(teeth, pressureAngleDeg, flankSamples, arcSamples);
|
|
314
|
+
if (!res) return null;
|
|
315
|
+
const [count, pts, smooth, radii] = new Uint32Array(heap(), res, 4);
|
|
316
|
+
const [outerRadius, rootRadius] = new Float64Array(heap(), radii, 2);
|
|
317
|
+
return {
|
|
318
|
+
outline: { points: new Float64Array(heap(), pts, count * 2).slice(), smooth: new Uint8Array(heap(), smooth, count).slice(), closed: true },
|
|
319
|
+
outerRadius,
|
|
320
|
+
rootRadius,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Header layouts of the instance kernel (the enums in wasm/simd/instances.c).
|
|
325
|
+
export const INSTANCE_HEADER = {
|
|
326
|
+
KIND: 0, FIRST: 1, COUNT: 2, STATE: 3, MODE: 4, NX: 5, NZ: 6, RANDOM: 7, CHOICES: 8,
|
|
327
|
+
OPS: 9, NOPS: 10, MATS: 11, ROWS: 12, NROWS: 13, BOUNDS: 14, MATERIALS: 15, LODS: 16, OUTER_LOD: 17, SWITCH: 18, SLOTS: 19,
|
|
328
|
+
OUT: 20, SIZE: 29,
|
|
329
|
+
};
|
|
330
|
+
export const INSTANCE_PARAMS = { WORLD: 0, ACC: 16, STEP: 32, SPACING: 48, JITTER: 50, RADIUS: 51, AREA: 52, ROTATION: 54, SCALE: 60, BOUNDS: 66, SIZE: 72 };
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Instance rows of placements [FIRST, FIRST + COUNT) of a <repeat> or
|
|
334
|
+
* <scatter> whose subtree was recorded as `template` ({ops, mats, table,
|
|
335
|
+
* bounds}, see recordTemplate in src/scene/flatten.js). The headers `h`
|
|
336
|
+
* (Int32Array) and `f` (Float64Array) and the table `materials` (material
|
|
337
|
+
* index per material code, -1 until resolveMaterial(code) has given it)
|
|
338
|
+
* carry the generator state, the accumulated repeat step, the scene bounds
|
|
339
|
+
* and the materials from one call to the next. Returns views of kernel
|
|
340
|
+
* memory, valid until the next kernel call, or null.
|
|
341
|
+
*/
|
|
342
|
+
export function instancesWasm(h, f, template, materials, resolveMaterial) {
|
|
343
|
+
if (!enabled || !simd) return null;
|
|
344
|
+
simd.reset();
|
|
345
|
+
const H = INSTANCE_HEADER;
|
|
346
|
+
h[H.OPS] = put(template.ops, simd);
|
|
347
|
+
h[H.MATS] = put(template.mats, simd);
|
|
348
|
+
h[H.ROWS] = put(template.table, simd);
|
|
349
|
+
h[H.BOUNDS] = put(template.bounds, simd);
|
|
350
|
+
h[H.MATERIALS] = put(materials, simd);
|
|
351
|
+
const hp = put(h, simd), fp = put(f, simd);
|
|
352
|
+
if (!h[H.OPS] || !h[H.MATS] || !h[H.ROWS] || !h[H.BOUNDS] || !h[H.MATERIALS] || !hp || !fp) return null;
|
|
353
|
+
material = resolveMaterial;
|
|
354
|
+
try {
|
|
355
|
+
if (!simd.instances(hp, fp)) return null;
|
|
356
|
+
} finally {
|
|
357
|
+
material = null;
|
|
358
|
+
}
|
|
359
|
+
const buf = simd.memory.buffer;
|
|
360
|
+
h.set(new Int32Array(buf, hp, H.SIZE));
|
|
361
|
+
f.set(new Float64Array(buf, fp, INSTANCE_PARAMS.SIZE));
|
|
362
|
+
materials.set(new Int32Array(buf, h[H.MATERIALS], materials.length));
|
|
363
|
+
const n = h[H.COUNT] * h[H.NROWS];
|
|
364
|
+
const o = H.OUT;
|
|
365
|
+
return {
|
|
366
|
+
matrices: new Float32Array(buf, h[o], n * 16),
|
|
367
|
+
spheres: new Float32Array(buf, h[o + 1], n * 4),
|
|
368
|
+
geom: new Uint32Array(buf, h[o + 2], n),
|
|
369
|
+
mat: new Uint32Array(buf, h[o + 3], n),
|
|
370
|
+
uid: new Uint32Array(buf, h[o + 4], n),
|
|
371
|
+
lodNode: new Int32Array(buf, h[o + 5], n),
|
|
372
|
+
lodChild: new Uint16Array(buf, h[o + 6], n),
|
|
373
|
+
flags: new Uint8Array(buf, h[o + 7], n),
|
|
374
|
+
switches: new Float64Array(buf, h[o + 8], h[H.COUNT] * h[H.LODS] * 4),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** es3dCos(x) and es3dSin(x) as the instance kernel computes them (for the tests), or null. */
|
|
379
|
+
export function cosSinWasm(x) {
|
|
380
|
+
if (!enabled || !simd) return null;
|
|
381
|
+
simd.reset();
|
|
382
|
+
const p = simd.alloc(16);
|
|
383
|
+
simd.cos_sin(x, p);
|
|
384
|
+
return Array.from(new Float64Array(simd.memory.buffer, p, 2));
|
|
385
|
+
}
|