@archiyou/collada-wasm
v0.1.0
Published
COLLADA (.dae) writer compiled to WebAssembly, with a typed TypeScript wrapper.
Maintainers
Readme
@archiyou/collada-wasm
COLLADA (.dae) writer for Archiyou: a small Rust crate compiled to WebAssembly, built on a
vendored + patched copy of collada_io 0.1.0
(MIT OR Apache-2.0).
Used by packages/core/src/modeler/DAEExporter.ts, which walks the meshup scene graph and
drives this writer. See Modeler.toDAE().
Install
npm install @archiyou/collada-wasmWhy the crate is vendored
Upstream is unmaintained (published 2020), undocumented, and its writer was missing most of
what we need. Rather than depend on it, src/collada_io/ holds a patched copy — the same
approach packages/gdrr2bp-wasm takes with gdrr-2bp. Every change is marked // vendored:
and listed in the patch log at the top of src/collada_io/mod.rs:
| Patch | What |
|---|---|
| P1 | <unit> emitted unit="…"; the spec attribute is name="…" — importers read model scale from it |
| P2 | <created>/<modified> were RFC2822, not xs:dateTime. Now caller-supplied strings, which also drops the chrono dependency |
| P3 | Added library_effects + library_materials (upstream had none; its fx/ directory is unreferenced and does not compile) |
| P4 | Added <lines> for curves |
| P4b | Added <polylist> + <vcount> so n-gon faces survive |
| P4c | <mesh> can now carry triangles / polylist / lines instead of exactly one Triangles |
| P5 | bind_material was dropped on the floor (// TODO), so materials could never be bound |
| P6 | Removed unreachable/unfinished types |
| P7 | Node had no children — only a flat node list was possible |
| P8 | #[derive(Clone)] on the writer types |
API
Two phases, mirroring COLLADA's own structure: library_geometries is a flat pool, while
library_visual_scenes is a tree.
import { createColladaWriter } from '@archiyou/collada-wasm';
const writer = await createColladaWriter({ unitName: 'millimeter', meter: 0.001 });
try {
writer.addMaterial('mat_0', 'Oak', 0.8, 0.6, 0.3, 1);
writer.addPolylistGeometry('geom_0', 'Box', positions, normals, vcount);
writer.beginNode('walls', 'walls');
writer.beginNode('geom_0_node', 'Box');
writer.attachGeometry('geom_0', 'mat_0');
writer.endNode();
writer.endNode();
const dae = writer.toStringDae();
} finally {
writer.free();
}Welding
Vertex welding happens here, not in meshup. meshup already has an equivalent welder in Rust
(rust/src/mesh/mod.rs get_vertices_and_indices), but it is not exposed to WASM, and
exposing it would mean rebuilding meshup's wasm binary — which in this repo is ahead of its
csgrs submodule pin, so rebuilding regresses rings, silhouette and reconstruct_ngons.
Welding a buffer we already hold is far cheaper than that risk.
Positions and normals weld independently into separate sources with separate <input>
offsets — the standard COLLADA layout, and where most of the size win is: a cube goes from 36
loose vertices to 6 quad faces over 8 positions and 6 normals.
The welder is grid-quantized (HashMap<[i64;3], u32> keyed on (v / tol).round()), matching
what meshup's own welder does. Two points closer than the tolerance can occasionally land in
different cells and stay unwelded; it never merges points it shouldn't, which is the
direction that matters.
Build
pnpm --filter @archiyou/collada-wasm build:wasm # wasm-pack + inline base64
cargo test # native smoke teststs/wasm/ and ts/collada-wasm-binary.ts are committed, and the base64 is regenerated by
buildscripts/build-wasm.ts — never by hand. A fresh clone therefore needs no Rust toolchain,
and there is deliberately no build script, so turbo build never requires one either.
wasm-pack's built-in wasm-opt step was observed respawning in a loop here, so the build
passes --no-opt and runs wasm-opt itself, where a failure is non-fatal.
Known limitations
- Nodes are all identity transforms (meshup bakes transforms into geometry), so the hierarchy is organisational, not transformational — moving a parent node in a DCC app will not behave the way it does in Archiyou.
- No UVs or textures: meshup meshes carry no UVs at all (the glTF exporter synthesizes them),
so materials are flat
lambertdiffuse colour only. Vertexshapes are skipped — there is no point primitive here.- N-gon faces with holes are triangulated;
<polylist>cannot express a hole. Hole-free faces keep their original topology. - Face normals come from the polygon plane, not the per-vertex normals, which lose their sign in meshup (a box reports only 3 distinct vertex normals instead of 6).
