Skip to main content

fast_mesh_wasm/
lib.rs

1//! JavaScript bindings for `fast-mesh`.
2//!
3//! These functions are the raw WebAssembly exports. The npm package wraps them in a
4//! TypeScript layer (`packages/fast-mesh-wasm/src/api.ts`) that converts inputs,
5//! shapes results into plain objects and documents the public API.
6
7use wasm_bindgen::prelude::*;
8
9mod edit;
10mod error;
11#[cfg(all(feature = "threads", target_arch = "wasm32"))]
12mod pool;
13mod render;
14mod scene;
15
16/// `initThreadPool(n)` (threaded artifact only): starts rayon's pool on `n`
17/// Web Workers that share this module and its memory. Await it once, right
18/// after `init()`, before any other call.
19#[cfg(all(feature = "threads", target_arch = "wasm32"))]
20pub use wasm_bindgen_rayon::init_thread_pool;
21
22#[cfg(all(feature = "threads", target_arch = "wasm32"))]
23pub use pool::watch_pool_crashes;
24
25fn to_js_error(err: fast_mesh::MeshError) -> JsError {
26    JsError::new(&err.to_string())
27}
28
29/// Smooth, area-weighted per-vertex normals; same length as `positions`.
30#[wasm_bindgen(js_name = computeVertexNormals)]
31pub fn compute_vertex_normals(positions: &[f32], indices: &[u32]) -> Result<Vec<f32>, JsError> {
32    fast_mesh::compute_vertex_normals(positions, indices).map_err(to_js_error)
33}
34
35/// Axis-aligned bounding box as six floats: `[minX, minY, minZ, maxX, maxY, maxZ]`.
36///
37/// Returned as plain numbers so JavaScript never holds WebAssembly memory for it.
38#[wasm_bindgen(js_name = computeBounds)]
39pub fn compute_bounds(positions: &[f32]) -> Result<Vec<f32>, JsError> {
40    let aabb = fast_mesh::compute_bounds(positions).map_err(to_js_error)?;
41    Ok([aabb.min, aabb.max].concat())
42}
43
44/// Panics, so the TypeScript tests can check that a trap poisons the
45/// instance. Only in builds with the `test-panic` feature
46/// (`FAST_MESH_TEST_BUILD=1`), never in releases.
47#[cfg(feature = "test-panic")]
48#[wasm_bindgen(js_name = __test_panic)]
49pub fn test_panic() {
50    panic!("test panic");
51}
52
53/// Panics on a thread of rayon's pool, inside a parallel loop, so the browser
54/// check can confirm the Worker scene poisons (`watchPoolCrashes`). Threaded
55/// test builds only (`node scripts/build-wasm.mjs --threads --test`).
56#[cfg(all(feature = "test-panic", feature = "threads", target_arch = "wasm32"))]
57#[wasm_bindgen(js_name = __test_panic_pool)]
58pub fn test_panic_pool() {
59    use rayon::prelude::*;
60    (0..1024u32).into_par_iter().for_each(|i| {
61        if i == 1000 {
62            panic!("test panic on a pool thread");
63        }
64    });
65}
66
67/// Traps on a thread of rayon's pool (`unreachable`, no panic), inside a
68/// parallel loop: a crash the panic hook never sees. Threaded test builds only.
69#[cfg(all(feature = "test-panic", feature = "threads", target_arch = "wasm32"))]
70#[wasm_bindgen(js_name = __test_trap_pool)]
71pub fn test_trap_pool() {
72    use rayon::prelude::*;
73    (0..1024u32).into_par_iter().for_each(|i| {
74        if i == 1000 {
75            core::arch::wasm32::unreachable();
76        }
77    });
78}
79
80/// The engine's colours of `values` under the colour rule `(map, rule)`
81/// (the encoding of `WasmScene::set_coloring`), RGBA8 each, so the
82/// TypeScript tests can check the fragment shading's mapping against
83/// `color_of`. Test builds only (the `test-panic` feature).
84#[cfg(feature = "test-panic")]
85#[wasm_bindgen(js_name = __test_color_of)]
86pub fn test_color_of(map: u8, rule: &[f32], values: &[f32]) -> Result<Vec<u8>, error::Error> {
87    let rule = scene::parse_rule(map, rule)?;
88    if rule.validate().is_err() {
89        return Err(error::Error::invalid("invalid colour rule"));
90    }
91    Ok(values
92        .iter()
93        .flat_map(|&v| fast_mesh_fields::color_of(&rule, v))
94        .collect())
95}