@francisdb/vpin-wasm 0.28.2 → 0.30.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/README.md CHANGED
@@ -11,7 +11,7 @@ npm install @francisdb/vpin-wasm
11
11
  ## Usage
12
12
 
13
13
  ```typescript
14
- import init, { extract, assemble, obj_to_mesh, mesh_to_obj } from '@francisdb/vpin-wasm';
14
+ import init, { extract, assemble, obj_to_mesh, mesh_to_obj, AxisConvention } from '@francisdb/vpin-wasm';
15
15
 
16
16
  await init();
17
17
  ```
@@ -35,7 +35,7 @@ const files = extract(vpxBytes, (message) => {
35
35
  - `data: Uint8Array` - VPX file bytes
36
36
  - `callback?: (message: string) => void` - Optional progress callback
37
37
 
38
- **Returns:** `Record<string, Uint8Array>` - Object mapping file paths to contents
38
+ **Returns:** `VpxFileMap` (`Record<string, Uint8Array>`) - file paths to contents
39
39
 
40
40
  ### assemble(files, callback?)
41
41
 
@@ -57,12 +57,47 @@ const vpxBytes = assemble(files, (message) => {
57
57
 
58
58
  **Parameters:**
59
59
 
60
- - `files: Record<string, Uint8Array>` - Object mapping file paths to contents
60
+ - `files: VpxFileMap` (`Record<string, Uint8Array>`) - file paths to contents
61
61
  - `callback?: (message: string) => void` - Optional progress callback
62
62
 
63
63
  **Returns:** `Uint8Array` - VPX file bytes
64
64
 
65
- ### obj_to_mesh(data) / mesh_to_obj(name, positions, texCoords, normals, indices)
65
+ ### export_glb(files, options?, callback?)
66
+
67
+ Exports a whole table to a GLB (binary glTF 2.0) file. Takes the same
68
+ extracted-file map `assemble` takes; call `extract` first if you only
69
+ have `.vpx` bytes.
70
+
71
+ ```typescript
72
+ const files = extract(vpxBytes);
73
+ const glb = export_glb(files, { exportInvisibleItems: false }, (message) => {
74
+ console.log(message);
75
+ });
76
+ // glb is a Uint8Array with model/gltf-binary content
77
+ ```
78
+
79
+ The output follows the glTF conventions - Y-up right-handed axes and
80
+ meters - so it opens correctly in Blender and other viewers with
81
+ default import settings. It contains generated meshes for every part
82
+ type, PBR materials from the table's material list, embedded textures,
83
+ and the three VPX view cameras.
84
+
85
+ Sound entries (`/vpx/sounds.json`, `/vpx/sounds/*`) are ignored - the
86
+ exporter never reads them, and skipping them keeps peak wasm memory
87
+ down on sound-heavy tables. A map without them works too.
88
+
89
+ **Parameters:**
90
+
91
+ - `files: VpxFileMap` (`Record<string, Uint8Array>`) - file paths to contents
92
+ - `options?: GlbExportOptions` - `{ exportInvisibleItems?: boolean }`;
93
+ when `true`, invisible items are exported with the
94
+ `KHR_node_visibility` extension instead of skipped (needs viewer
95
+ support; leave off for Blender). Default `false`.
96
+ - `callback?: (message: string) => void` - Optional progress callback
97
+
98
+ **Returns:** `Uint8Array` - GLB file bytes
99
+
100
+ ### obj_to_mesh(data, options?) / mesh_to_obj(name, positions, texCoords, normals, indices, options?)
66
101
 
67
102
  Renderer-friendly mesh I/O. `obj_to_mesh` parses any flavor of OBJ
68
103
  (n-gons fan-triangulated, mismatched `v/vt/vn` corners deduplicated)
@@ -71,7 +106,9 @@ JS-side OBJ parser needed.
71
106
 
72
107
  ```typescript
73
108
  const objBytes = files['/vpx/gameitems/Primitive.MyMesh.obj'];
74
- const mesh = obj_to_mesh(objBytes);
109
+ const mesh = obj_to_mesh(objBytes); // defaults: vpinball convention, scale 1.0
110
+ // or e.g. for a Blender-exported OBJ:
111
+ // const mesh = obj_to_mesh(objBytes, { axes: AxisConvention.YUpRightHanded });
75
112
 
76
113
  // mesh.name: string
77
114
  // mesh.positions: Float32Array (length = 3 * vertCount, x,y,z,...)
@@ -95,20 +132,52 @@ const obj = mesh_to_obj(mesh.name, mesh.positions, mesh.texCoords, mesh.normals,
95
132
  files['/vpx/gameitems/Primitive.MyMesh.obj'] = obj;
96
133
  ```
97
134
 
135
+ ### mesh_to_glb(name, positions, texCoords, normals, indices, options?)
136
+
137
+ The GLB sibling of `mesh_to_obj` - serializes a single mesh as a
138
+ binary glTF 2.0 file, for handing one primitive's mesh to a DCC tool.
139
+ There is no axis option: glTF mandates Y-up right-handed, so the
140
+ vpx-internal input is always converted the same way `export_glb`
141
+ converts the whole table (texture coordinates pass through unchanged -
142
+ glTF's UV origin matches vpx's, unlike OBJ's).
143
+
144
+ ```typescript
145
+ const glb = mesh_to_glb(mesh.name, mesh.positions, mesh.texCoords, mesh.normals, mesh.indices);
146
+ // glb is a Uint8Array with model/gltf-binary content
147
+ ```
148
+
149
+ `options.unitScale` (default `1.0`) multiplies positions. A
150
+ primitive's local mesh coordinates are arbitrary units (the table
151
+ scales them by the primitive's Size at render), so no unit conversion
152
+ is applied by default.
153
+
98
154
  The published wasm bundle is built with `wasm-bindgen --weak-refs`, so
99
155
  the Rust-owned memory backing each `mesh` is reclaimed automatically
100
156
  via `FinalizationRegistry` when the JS wrapper is garbage-collected.
101
157
  You may call `mesh.free()` explicitly for deterministic cleanup of
102
158
  large meshes, but it is not required.
103
159
 
104
- **Coordinate convention:** the mesh data is in vpx-internal form -
105
- `obj_to_mesh` applies the same transforms as `assemble`'s read path
106
- (vertex Z negated, normal Z negated, V coordinate flipped, per-triangle
107
- corner order reversed), and `mesh_to_obj` applies the inverse, matching
108
- `extract`'s write path. Round-trip
109
- `obj_to_mesh -> edit -> mesh_to_obj -> assemble` preserves vpx data by
110
- construction. If your renderer uses a different convention than
111
- vpinball's left-handed +Z up, apply a transform matrix on the JS side.
160
+ **Coordinate convention:** the mesh data is always in vpx-internal
161
+ form; `options.axes` names the convention of the OBJ side:
162
+
163
+ - `AxisConvention.ZDownRightHanded` (default) - vpinball's exported OBJ
164
+ convention (vertex/normal Z negated, V coordinate flipped,
165
+ per-triangle corner order reversed). This is what `extract` writes
166
+ and `assemble` reads, so it is the right choice for OBJs from the
167
+ extracted file map. Round-trip
168
+ `obj_to_mesh -> edit -> mesh_to_obj -> assemble` preserves vpx data
169
+ by construction.
170
+ - `AxisConvention.YUpRightHanded` - the Blender / DCC default (Y-Z
171
+ swap, V flip, winding reverse). Use this to hand a Blender-exported
172
+ OBJ straight to `obj_to_mesh`, or to write an OBJ that opens upright
173
+ in Blender with default import settings.
174
+ - `AxisConvention.ZUpLeftHanded` - vpx-internal values verbatim, no
175
+ transforms.
176
+
177
+ `options.unitScale` (default `1.0`) multiplies positions (on the vpx
178
+ side for `obj_to_mesh`, before the axis mapping for `mesh_to_obj`).
179
+ Normals and texture coordinates are never scaled. An OBJ written with
180
+ scale `k` reads back with scale `1 / k`.
112
181
 
113
182
  **Animation frames:** primitives with vertex animation extract as
114
183
  sibling files `Primitive.MyMesh_00000.obj`, `Primitive.MyMesh_00001.obj`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@francisdb/vpin-wasm",
3
- "version": "0.28.2",
3
+ "version": "0.30.0",
4
4
  "description": "WASM bindings for vpin, a rust library for the visual/virtual pinball ecosystem.",
5
5
  "homepage": "https://github.com/francisdb/vpin",
6
6
  "bugs": {
package/vpin.d.ts CHANGED
@@ -1,6 +1,182 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * An extracted table as a file map: absolute path under `/vpx` to file
6
+ * contents, e.g. `{ "/vpx/images/ball.png": Uint8Array, ... }`.
7
+ * Produced by `extract`, consumed by `assemble` and `export_glb`.
8
+ */
9
+ export type VpxFileMap = Record<string, Uint8Array>;
10
+
11
+
12
+
13
+ /**
14
+ * Options for `export_glb`. Pass a plain object literal; every field
15
+ * is optional.
16
+ */
17
+ export interface GlbExportOptions {
18
+ /**
19
+ * When `true`, items marked invisible in the table are exported
20
+ * with the `KHR_node_visibility` extension set to `visible: false`
21
+ * instead of being skipped. Preserves the full table structure but
22
+ * requires viewer support for the extension (e.g. some game
23
+ * engines); leave off for Blender. Default: `false`.
24
+ */
25
+ exportInvisibleItems?: boolean;
26
+ }
27
+
28
+
29
+
30
+ /**
31
+ * Options for `export_obj`. Pass a plain object literal; every field
32
+ * is optional.
33
+ */
34
+ export interface ObjExportOptions {
35
+ /**
36
+ * Axis convention of the written OBJ. Default:
37
+ * `AxisConvention.ZDownRightHanded`, vpinball's own OBJ export
38
+ * layout. Use `AxisConvention.YUpRightHanded` for Blender and most
39
+ * other tools with default import settings.
40
+ */
41
+ axes?: AxisConvention;
42
+ /**
43
+ * Unit of the written positions. Default: `ExportUnits.M`.
44
+ */
45
+ units?: ExportUnits;
46
+ /**
47
+ * Emit one `newmtl` block per distinct (material, texture) pair
48
+ * instead of one per `usemtl`. Default: `true`.
49
+ */
50
+ dedupMtlBlocks?: boolean;
51
+ /**
52
+ * Write the referenced textures to `images/` and reference them
53
+ * from the MTL. Default: `true`.
54
+ */
55
+ extractTextures?: boolean;
56
+ /**
57
+ * Include the plunger mesh. Default: `true`.
58
+ */
59
+ includePlunger?: boolean;
60
+ }
61
+
62
+ /**
63
+ * Result of `export_obj`: path relative to the export directory to file
64
+ * contents, e.g. `{ "table.obj": Uint8Array, "table.mtl": Uint8Array,
65
+ * "images/playfield.png": Uint8Array }`.
66
+ */
67
+ export type ObjExportFileMap = Record<string, Uint8Array>;
68
+
69
+
70
+
71
+ /**
72
+ * Options for `mesh_to_glb`. Pass a plain object literal; every field
73
+ * is optional.
74
+ */
75
+ export interface MeshGlbOptions {
76
+ /**
77
+ * Multiplier applied to positions before the axis mapping. Normals
78
+ * and texture coordinates are never scaled. Default: `1.0`.
79
+ */
80
+ unitScale?: number;
81
+ }
82
+
83
+
84
+
85
+ /**
86
+ * Options for `obj_to_mesh` / `mesh_to_obj`. Pass a plain object
87
+ * literal; every field is optional.
88
+ */
89
+ export interface MeshIoOptions {
90
+ /**
91
+ * Axis convention of the OBJ side of the conversion (the mesh side
92
+ * is always vpx-internal). Default: `AxisConvention.ZDownRightHanded`,
93
+ * matching what `extract` writes and `assemble` reads.
94
+ */
95
+ axes?: AxisConvention;
96
+ /**
97
+ * Multiplier applied to positions (on the vpx side for
98
+ * `obj_to_mesh`, before the axis mapping for `mesh_to_obj`).
99
+ * Normals and texture coordinates are never scaled. An OBJ written
100
+ * with scale `k` reads back with `1 / k`. Default: `1.0`.
101
+ */
102
+ unitScale?: number;
103
+ }
104
+
105
+
106
+
107
+ /**
108
+ * Axis convention for 3D mesh interchange (OBJ, glTF/GLB), in either
109
+ * direction: exporting VPX geometry or importing meshes back in.
110
+ *
111
+ * VPX space is left-handed with Z up: X right (across the playfield),
112
+ * Y towards the player (down the playfield), Z up (towards the glass).
113
+ * [`Self::ZUpLeftHanded`] is that internal frame verbatim; the other
114
+ * variants negate or swap exactly one axis pair, so each produces a
115
+ * right-handed frame from the left-handed one. A conversion between two
116
+ * frames of different [`Self::handedness`] must pair with a reversed
117
+ * triangle winding (matching vpinball's `ObjLoader`); one between frames
118
+ * of equal handedness keeps the winding, since such frames differ only
119
+ * by a pure rotation (the two right-handed variants: 90 degrees about
120
+ * X).
121
+ */
122
+ export enum AxisConvention {
123
+ /**
124
+ * VPX's internal coordinate system: left-handed with Z up, inherited
125
+ * from DirectX. X runs right across the playfield, Y down the
126
+ * playfield towards the player, Z up towards the glass.
127
+ *
128
+ * Converting to or from this convention leaves coordinates untouched
129
+ * (vertices keep their vpx-internal values). Only meaningful for
130
+ * tooling that works on the raw vpx data; not what any viewer or DCC
131
+ * tool expects.
132
+ */
133
+ ZUpLeftHanded = 0,
134
+ /**
135
+ * VPinball's exported OBJ convention: `(x, y, -z)`.
136
+ *
137
+ * Right-handed like [`Self::YUpRightHanded`], but keeping VPX's axis
138
+ * roles: Y still runs down the playfield and Z, now negated, points
139
+ * down through the table instead of up. This is what vpinball itself
140
+ * writes when exporting an OBJ and expects when importing one, so a
141
+ * table shows up rotated 90 degrees about X in a Y-up viewer.
142
+ */
143
+ ZDownRightHanded = 1,
144
+ /**
145
+ * Y up, right-handed: `(x, z, y)`. The glTF and Wavefront OBJ
146
+ * convention, and what Blender / Maya / 3ds Max assume with their
147
+ * default import settings (`Scale 1.0, Forward -Z, Up Y`).
148
+ */
149
+ YUpRightHanded = 2,
150
+ }
151
+
152
+ /**
153
+ * Output unit for table exports (OBJ, glTF/GLB).
154
+ *
155
+ * VPinball internally uses "VP Units" (VPU): 50 VPU = 1.0625 inches
156
+ * (one pinball diameter), so a standard table around 20 inches wide
157
+ * is roughly 950 VPU. Loaded straight into a tool that treats
158
+ * numbers as metres, that becomes a ~950 m table. Pick a metric
159
+ * variant to scale positions on the way out.
160
+ */
161
+ export enum ExportUnits {
162
+ /**
163
+ * Raw VPinball Units (no scaling).
164
+ */
165
+ Vpu = 0,
166
+ /**
167
+ * Millimetres.
168
+ */
169
+ Mm = 1,
170
+ /**
171
+ * Centimetres.
172
+ */
173
+ Cm = 2,
174
+ /**
175
+ * Metres.
176
+ */
177
+ M = 3,
178
+ }
179
+
4
180
  /**
5
181
  * Mesh data for a single primitive: positions, texture coordinates,
6
182
  * normals and triangle indices, packed as flat typed arrays for direct
@@ -45,9 +221,40 @@ export class PrimitiveMesh {
45
221
  readonly texCoords: Float32Array;
46
222
  }
47
223
 
48
- export function assemble(files: object, callback?: Function | null): Uint8Array;
224
+ export function assemble(files: VpxFileMap, callback?: Function | null): Uint8Array;
49
225
 
50
- export function extract(data: Uint8Array, callback?: Function | null): object;
226
+ /**
227
+ * Export a whole table to a GLB (binary glTF 2.0) file.
228
+ *
229
+ * Takes the same extracted-file map `assemble` takes
230
+ * (`{ "/vpx/...": Uint8Array }`), so consumers holding a table as an
231
+ * extracted tree skip a full assemble/parse round trip; consumers with
232
+ * only `.vpx` bytes can call `extract` first.
233
+ *
234
+ * The output follows the glTF conventions: Y-up right-handed axes and
235
+ * meters, so it opens correctly in Blender and other glTF viewers with
236
+ * default settings. It contains the generated meshes for every part
237
+ * type, PBR materials from the VPX material list, embedded textures,
238
+ * and the three VPX view cameras (desktop / fullscreen / FSS).
239
+ *
240
+ * Sound entries (`/vpx/sounds.json` and `/vpx/sounds/*`) are ignored -
241
+ * the exporter never reads them, and skipping them keeps peak wasm
242
+ * memory down on sound-heavy tables. Passing a map without them works
243
+ * too.
244
+ */
245
+ export function export_glb(files: VpxFileMap, options?: GlbExportOptions | null, callback?: Function | null): Uint8Array;
246
+
247
+ /**
248
+ * Export a whole table as a Wavefront OBJ with its MTL and, by
249
+ * default, the referenced textures. Takes the file map produced by
250
+ * `extract` and returns a file map relative to the export directory:
251
+ * `table.obj`, `table.mtl` and `images/*`.
252
+ *
253
+ * Sound entries are ignored like in `export_glb`.
254
+ */
255
+ export function export_obj(files: VpxFileMap, options?: ObjExportOptions | null, callback?: Function | null): ObjExportFileMap;
256
+
257
+ export function extract(data: Uint8Array, callback?: Function | null): VpxFileMap;
51
258
 
52
259
  /**
53
260
  * Generate the procedural mesh used by vpinball primitives that
@@ -72,6 +279,26 @@ export function generate_builtin_primitive(sides: number, draw_textures_inside:
72
279
 
73
280
  export function init(): void;
74
281
 
282
+ /**
283
+ * Serialize a single mesh as a GLB (binary glTF 2.0) file.
284
+ *
285
+ * The GLB sibling of [`mesh_to_obj`], for exporting one primitive's
286
+ * mesh to DCC tools. There is no axis option: glTF mandates Y-up
287
+ * right-handed, so the vpx-internal input is always converted the same
288
+ * way the whole-table exporter converts it (Y-Z swap, winding
289
+ * reversed; texture coordinates pass through - glTF's UV origin
290
+ * matches vpx's, unlike OBJ's).
291
+ *
292
+ * `name` becomes the mesh/node name; pass an empty string to use
293
+ * `"object"`. Array requirements are the same as [`mesh_to_obj`].
294
+ *
295
+ * `options.unitScale` (default `1.0`) multiplies positions. A
296
+ * primitive's local mesh coordinates are arbitrary units (the table
297
+ * scales them by the primitive's Size at render), so no unit
298
+ * conversion is applied by default.
299
+ */
300
+ export function mesh_to_glb(name: string, positions: Float32Array, tex_coords: Float32Array, normals: Float32Array, indices: Uint32Array, options?: MeshGlbOptions | null): Uint8Array;
301
+
75
302
  /**
76
303
  * Serialize a mesh as a Wavefront OBJ.
77
304
  *
@@ -81,18 +308,29 @@ export function init(): void;
81
308
  * normals.len() / 3`); index values must be valid 0-based offsets into
82
309
  * that vertex array.
83
310
  *
84
- * `convert_to_left_handed` is the symmetric inverse of the same flag
85
- * on [`obj_to_mesh`]:
311
+ * `options.axes` is the symmetric inverse of the same option on
312
+ * [`obj_to_mesh`]: the input is always vpx-internal data, `axes` names
313
+ * the convention to write the OBJ in.
86
314
  *
87
- * - `true` (matches `extract`'s write path): the input is treated as
88
- * vpx-internal data and converted out: vertex Z is negated, normal Z
89
- * is negated, V is flipped (`obj_v = 1 - vpx_tv`), and per-triangle
90
- * corner order is reversed. The result is a vpinball-format OBJ
91
- * that `assemble` (or `obj_to_mesh(.., true)`) reads back identically.
92
- * - `false`: the input vpx-internal data is written out verbatim, no
93
- * transforms applied. Round-trips with `obj_to_mesh(.., false)`.
315
+ * - [`AxisConvention::ZDownRightHanded`] (the default; matches
316
+ * `extract`'s write path, the old `convert_to_left_handed = true`):
317
+ * vertex and normal Z are negated, V is flipped (`obj_v = 1 -
318
+ * vpx_tv`) and per-triangle corner order is reversed. The result is a
319
+ * vpinball-format OBJ that `assemble` (or `obj_to_mesh` with the same
320
+ * convention) reads back identically.
321
+ * - [`AxisConvention::YUpRightHanded`]: Y and Z are swapped, V is
322
+ * flipped and winding is reversed; the OBJ opens upright in Blender
323
+ * with default import settings.
324
+ * - [`AxisConvention::ZUpLeftHanded`] (the old `convert_to_left_handed
325
+ * = false`): the vpx-internal data is written out verbatim, no
326
+ * transforms applied.
327
+ *
328
+ * `options.unitScale` (default `1.0`) multiplies positions before the
329
+ * axis mapping. Normals and texture coordinates are never scaled.
330
+ * Round-trips through `obj_to_mesh` with the same convention and scale
331
+ * `1.0 / unitScale`.
94
332
  */
95
- export function mesh_to_obj(name: string, positions: Float32Array, tex_coords: Float32Array, normals: Float32Array, indices: Uint32Array, convert_to_left_handed: boolean): Uint8Array;
333
+ export function mesh_to_obj(name: string, positions: Float32Array, tex_coords: Float32Array, normals: Float32Array, indices: Uint32Array, options?: MeshIoOptions | null): Uint8Array;
96
334
 
97
335
  /**
98
336
  * Parse a Wavefront OBJ into a [`PrimitiveMesh`].
@@ -101,21 +339,31 @@ export function mesh_to_obj(name: string, positions: Float32Array, tex_coords: F
101
339
  * anything in between): n-gons are fan-triangulated and `(position, uv,
102
340
  * normal)` corners are deduplicated so the result is renderer-ready.
103
341
  *
104
- * `convert_to_left_handed` mirrors vpinball's `ObjLoader::Load` flag of
105
- * the same name (the "Convert coordinate system" checkbox in the
106
- * vpinball mesh-import dialog):
342
+ * `options.axes` names the convention the OBJ data is in; the returned
343
+ * mesh is always in vpx-internal convention:
344
+ *
345
+ * - [`AxisConvention::ZDownRightHanded`] (the default; matches
346
+ * `assemble`'s read path and vpinball's mesh-import dialog with
347
+ * "Convert coordinate system" checked, the old `convert_to_left_handed
348
+ * = true`): vertex and normal Z are negated, V is flipped (`vpx_tv =
349
+ * 1 - obj_v`) and the per-triangle corner order is reversed. This path
350
+ * also honors the `# vpx <hex>` byte-preservation comments, so
351
+ * vpinball-format OBJs from `extract` reproduce the original vpx
352
+ * values bit-for-bit.
353
+ * - [`AxisConvention::YUpRightHanded`] (Blender / DCC default export
354
+ * settings): Y and Z are swapped, V is flipped and winding is
355
+ * reversed.
356
+ * - [`AxisConvention::ZUpLeftHanded`] (the old `convert_to_left_handed
357
+ * = false`): the input is assumed to already hold vpx-internal values
358
+ * (e.g. produced by a previous `mesh_to_obj` with the same
359
+ * convention). No transforms; values pass through verbatim.
107
360
  *
108
- * - `true` (matches `assemble`'s read path and vpinball's dialog
109
- * default): the input is treated as right-handed (Blender / standard
110
- * convention). Vertex Z is negated, normal Z is negated, V is flipped
111
- * (`vpx_tv = 1 - obj_v`), and the per-triangle corner order is
112
- * reversed. The returned mesh data ends up in vpx-internal,
113
- * left-handed convention.
114
- * - `false`: the input is assumed to already be in vpx-internal
115
- * convention (e.g. produced by a previous `mesh_to_obj` with the same
116
- * flag). The transforms are skipped and values pass through verbatim.
361
+ * `options.unitScale` (default `1.0`) multiplies positions after the
362
+ * axis mapping. Normals and texture coordinates are never scaled. A
363
+ * mesh exported through `mesh_to_obj` with scale `k` reads back with
364
+ * scale `1.0 / k`.
117
365
  */
118
- export function obj_to_mesh(data: Uint8Array, convert_to_left_handed: boolean): PrimitiveMesh;
366
+ export function obj_to_mesh(data: Uint8Array, options?: MeshIoOptions | null): PrimitiveMesh;
119
367
 
120
368
  export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
121
369
 
@@ -123,8 +371,11 @@ export interface InitOutput {
123
371
  readonly memory: WebAssembly.Memory;
124
372
  readonly __wbg_primitivemesh_free: (a: number, b: number) => void;
125
373
  readonly assemble: (a: any, b: number) => [number, number, number, number];
374
+ readonly export_glb: (a: any, b: number, c: number) => [number, number, number, number];
375
+ readonly export_obj: (a: any, b: number, c: number) => [number, number, number];
126
376
  readonly extract: (a: number, b: number, c: number) => [number, number, number];
127
377
  readonly generate_builtin_primitive: (a: number, b: number) => [number, number, number];
378
+ readonly mesh_to_glb: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
128
379
  readonly mesh_to_obj: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
129
380
  readonly obj_to_mesh: (a: number, b: number, c: number) => [number, number, number];
130
381
  readonly primitivemesh_indices: (a: number) => any;
package/vpin.js CHANGED
@@ -1,5 +1,80 @@
1
1
  /* @ts-self-types="./vpin.d.ts" */
2
2
 
3
+ /**
4
+ * Axis convention for 3D mesh interchange (OBJ, glTF/GLB), in either
5
+ * direction: exporting VPX geometry or importing meshes back in.
6
+ *
7
+ * VPX space is left-handed with Z up: X right (across the playfield),
8
+ * Y towards the player (down the playfield), Z up (towards the glass).
9
+ * [`Self::ZUpLeftHanded`] is that internal frame verbatim; the other
10
+ * variants negate or swap exactly one axis pair, so each produces a
11
+ * right-handed frame from the left-handed one. A conversion between two
12
+ * frames of different [`Self::handedness`] must pair with a reversed
13
+ * triangle winding (matching vpinball's `ObjLoader`); one between frames
14
+ * of equal handedness keeps the winding, since such frames differ only
15
+ * by a pure rotation (the two right-handed variants: 90 degrees about
16
+ * X).
17
+ * @enum {0 | 1 | 2}
18
+ */
19
+ export const AxisConvention = Object.freeze({
20
+ /**
21
+ * VPX's internal coordinate system: left-handed with Z up, inherited
22
+ * from DirectX. X runs right across the playfield, Y down the
23
+ * playfield towards the player, Z up towards the glass.
24
+ *
25
+ * Converting to or from this convention leaves coordinates untouched
26
+ * (vertices keep their vpx-internal values). Only meaningful for
27
+ * tooling that works on the raw vpx data; not what any viewer or DCC
28
+ * tool expects.
29
+ */
30
+ ZUpLeftHanded: 0, "0": "ZUpLeftHanded",
31
+ /**
32
+ * VPinball's exported OBJ convention: `(x, y, -z)`.
33
+ *
34
+ * Right-handed like [`Self::YUpRightHanded`], but keeping VPX's axis
35
+ * roles: Y still runs down the playfield and Z, now negated, points
36
+ * down through the table instead of up. This is what vpinball itself
37
+ * writes when exporting an OBJ and expects when importing one, so a
38
+ * table shows up rotated 90 degrees about X in a Y-up viewer.
39
+ */
40
+ ZDownRightHanded: 1, "1": "ZDownRightHanded",
41
+ /**
42
+ * Y up, right-handed: `(x, z, y)`. The glTF and Wavefront OBJ
43
+ * convention, and what Blender / Maya / 3ds Max assume with their
44
+ * default import settings (`Scale 1.0, Forward -Z, Up Y`).
45
+ */
46
+ YUpRightHanded: 2, "2": "YUpRightHanded",
47
+ });
48
+
49
+ /**
50
+ * Output unit for table exports (OBJ, glTF/GLB).
51
+ *
52
+ * VPinball internally uses "VP Units" (VPU): 50 VPU = 1.0625 inches
53
+ * (one pinball diameter), so a standard table around 20 inches wide
54
+ * is roughly 950 VPU. Loaded straight into a tool that treats
55
+ * numbers as metres, that becomes a ~950 m table. Pick a metric
56
+ * variant to scale positions on the way out.
57
+ * @enum {0 | 1 | 2 | 3}
58
+ */
59
+ export const ExportUnits = Object.freeze({
60
+ /**
61
+ * Raw VPinball Units (no scaling).
62
+ */
63
+ Vpu: 0, "0": "Vpu",
64
+ /**
65
+ * Millimetres.
66
+ */
67
+ Mm: 1, "1": "Mm",
68
+ /**
69
+ * Centimetres.
70
+ */
71
+ Cm: 2, "2": "Cm",
72
+ /**
73
+ * Metres.
74
+ */
75
+ M: 3, "3": "M",
76
+ });
77
+
3
78
  /**
4
79
  * Mesh data for a single primitive: positions, texture coordinates,
5
80
  * normals and triangle indices, packed as flat typed arrays for direct
@@ -101,7 +176,7 @@ export class PrimitiveMesh {
101
176
  if (Symbol.dispose) PrimitiveMesh.prototype[Symbol.dispose] = PrimitiveMesh.prototype.free;
102
177
 
103
178
  /**
104
- * @param {object} files
179
+ * @param {VpxFileMap} files
105
180
  * @param {Function | null} [callback]
106
181
  * @returns {Uint8Array}
107
182
  */
@@ -115,10 +190,63 @@ export function assemble(files, callback) {
115
190
  return v1;
116
191
  }
117
192
 
193
+ /**
194
+ * Export a whole table to a GLB (binary glTF 2.0) file.
195
+ *
196
+ * Takes the same extracted-file map `assemble` takes
197
+ * (`{ "/vpx/...": Uint8Array }`), so consumers holding a table as an
198
+ * extracted tree skip a full assemble/parse round trip; consumers with
199
+ * only `.vpx` bytes can call `extract` first.
200
+ *
201
+ * The output follows the glTF conventions: Y-up right-handed axes and
202
+ * meters, so it opens correctly in Blender and other glTF viewers with
203
+ * default settings. It contains the generated meshes for every part
204
+ * type, PBR materials from the VPX material list, embedded textures,
205
+ * and the three VPX view cameras (desktop / fullscreen / FSS).
206
+ *
207
+ * Sound entries (`/vpx/sounds.json` and `/vpx/sounds/*`) are ignored -
208
+ * the exporter never reads them, and skipping them keeps peak wasm
209
+ * memory down on sound-heavy tables. Passing a map without them works
210
+ * too.
211
+ * @param {VpxFileMap} files
212
+ * @param {GlbExportOptions | null} [options]
213
+ * @param {Function | null} [callback]
214
+ * @returns {Uint8Array}
215
+ */
216
+ export function export_glb(files, options, callback) {
217
+ const ret = wasm.export_glb(files, isLikeNone(options) ? 0 : addToExternrefTable0(options), isLikeNone(callback) ? 0 : addToExternrefTable0(callback));
218
+ if (ret[3]) {
219
+ throw takeFromExternrefTable0(ret[2]);
220
+ }
221
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
222
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
223
+ return v1;
224
+ }
225
+
226
+ /**
227
+ * Export a whole table as a Wavefront OBJ with its MTL and, by
228
+ * default, the referenced textures. Takes the file map produced by
229
+ * `extract` and returns a file map relative to the export directory:
230
+ * `table.obj`, `table.mtl` and `images/*`.
231
+ *
232
+ * Sound entries are ignored like in `export_glb`.
233
+ * @param {VpxFileMap} files
234
+ * @param {ObjExportOptions | null} [options]
235
+ * @param {Function | null} [callback]
236
+ * @returns {ObjExportFileMap}
237
+ */
238
+ export function export_obj(files, options, callback) {
239
+ const ret = wasm.export_obj(files, isLikeNone(options) ? 0 : addToExternrefTable0(options), isLikeNone(callback) ? 0 : addToExternrefTable0(callback));
240
+ if (ret[2]) {
241
+ throw takeFromExternrefTable0(ret[1]);
242
+ }
243
+ return takeFromExternrefTable0(ret[0]);
244
+ }
245
+
118
246
  /**
119
247
  * @param {Uint8Array} data
120
248
  * @param {Function | null} [callback]
121
- * @returns {object}
249
+ * @returns {VpxFileMap}
122
250
  */
123
251
  export function extract(data, callback) {
124
252
  const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
@@ -164,6 +292,51 @@ export function init() {
164
292
  wasm.init();
165
293
  }
166
294
 
295
+ /**
296
+ * Serialize a single mesh as a GLB (binary glTF 2.0) file.
297
+ *
298
+ * The GLB sibling of [`mesh_to_obj`], for exporting one primitive's
299
+ * mesh to DCC tools. There is no axis option: glTF mandates Y-up
300
+ * right-handed, so the vpx-internal input is always converted the same
301
+ * way the whole-table exporter converts it (Y-Z swap, winding
302
+ * reversed; texture coordinates pass through - glTF's UV origin
303
+ * matches vpx's, unlike OBJ's).
304
+ *
305
+ * `name` becomes the mesh/node name; pass an empty string to use
306
+ * `"object"`. Array requirements are the same as [`mesh_to_obj`].
307
+ *
308
+ * `options.unitScale` (default `1.0`) multiplies positions. A
309
+ * primitive's local mesh coordinates are arbitrary units (the table
310
+ * scales them by the primitive's Size at render), so no unit
311
+ * conversion is applied by default.
312
+ * @param {string} name
313
+ * @param {Float32Array} positions
314
+ * @param {Float32Array} tex_coords
315
+ * @param {Float32Array} normals
316
+ * @param {Uint32Array} indices
317
+ * @param {MeshGlbOptions | null} [options]
318
+ * @returns {Uint8Array}
319
+ */
320
+ export function mesh_to_glb(name, positions, tex_coords, normals, indices, options) {
321
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
322
+ const len0 = WASM_VECTOR_LEN;
323
+ const ptr1 = passArrayF32ToWasm0(positions, wasm.__wbindgen_malloc);
324
+ const len1 = WASM_VECTOR_LEN;
325
+ const ptr2 = passArrayF32ToWasm0(tex_coords, wasm.__wbindgen_malloc);
326
+ const len2 = WASM_VECTOR_LEN;
327
+ const ptr3 = passArrayF32ToWasm0(normals, wasm.__wbindgen_malloc);
328
+ const len3 = WASM_VECTOR_LEN;
329
+ const ptr4 = passArray32ToWasm0(indices, wasm.__wbindgen_malloc);
330
+ const len4 = WASM_VECTOR_LEN;
331
+ const ret = wasm.mesh_to_glb(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, isLikeNone(options) ? 0 : addToExternrefTable0(options));
332
+ if (ret[3]) {
333
+ throw takeFromExternrefTable0(ret[2]);
334
+ }
335
+ var v6 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
336
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
337
+ return v6;
338
+ }
339
+
167
340
  /**
168
341
  * Serialize a mesh as a Wavefront OBJ.
169
342
  *
@@ -173,25 +346,36 @@ export function init() {
173
346
  * normals.len() / 3`); index values must be valid 0-based offsets into
174
347
  * that vertex array.
175
348
  *
176
- * `convert_to_left_handed` is the symmetric inverse of the same flag
177
- * on [`obj_to_mesh`]:
349
+ * `options.axes` is the symmetric inverse of the same option on
350
+ * [`obj_to_mesh`]: the input is always vpx-internal data, `axes` names
351
+ * the convention to write the OBJ in.
352
+ *
353
+ * - [`AxisConvention::ZDownRightHanded`] (the default; matches
354
+ * `extract`'s write path, the old `convert_to_left_handed = true`):
355
+ * vertex and normal Z are negated, V is flipped (`obj_v = 1 -
356
+ * vpx_tv`) and per-triangle corner order is reversed. The result is a
357
+ * vpinball-format OBJ that `assemble` (or `obj_to_mesh` with the same
358
+ * convention) reads back identically.
359
+ * - [`AxisConvention::YUpRightHanded`]: Y and Z are swapped, V is
360
+ * flipped and winding is reversed; the OBJ opens upright in Blender
361
+ * with default import settings.
362
+ * - [`AxisConvention::ZUpLeftHanded`] (the old `convert_to_left_handed
363
+ * = false`): the vpx-internal data is written out verbatim, no
364
+ * transforms applied.
178
365
  *
179
- * - `true` (matches `extract`'s write path): the input is treated as
180
- * vpx-internal data and converted out: vertex Z is negated, normal Z
181
- * is negated, V is flipped (`obj_v = 1 - vpx_tv`), and per-triangle
182
- * corner order is reversed. The result is a vpinball-format OBJ
183
- * that `assemble` (or `obj_to_mesh(.., true)`) reads back identically.
184
- * - `false`: the input vpx-internal data is written out verbatim, no
185
- * transforms applied. Round-trips with `obj_to_mesh(.., false)`.
366
+ * `options.unitScale` (default `1.0`) multiplies positions before the
367
+ * axis mapping. Normals and texture coordinates are never scaled.
368
+ * Round-trips through `obj_to_mesh` with the same convention and scale
369
+ * `1.0 / unitScale`.
186
370
  * @param {string} name
187
371
  * @param {Float32Array} positions
188
372
  * @param {Float32Array} tex_coords
189
373
  * @param {Float32Array} normals
190
374
  * @param {Uint32Array} indices
191
- * @param {boolean} convert_to_left_handed
375
+ * @param {MeshIoOptions | null} [options]
192
376
  * @returns {Uint8Array}
193
377
  */
194
- export function mesh_to_obj(name, positions, tex_coords, normals, indices, convert_to_left_handed) {
378
+ export function mesh_to_obj(name, positions, tex_coords, normals, indices, options) {
195
379
  const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
196
380
  const len0 = WASM_VECTOR_LEN;
197
381
  const ptr1 = passArrayF32ToWasm0(positions, wasm.__wbindgen_malloc);
@@ -202,7 +386,7 @@ export function mesh_to_obj(name, positions, tex_coords, normals, indices, conve
202
386
  const len3 = WASM_VECTOR_LEN;
203
387
  const ptr4 = passArray32ToWasm0(indices, wasm.__wbindgen_malloc);
204
388
  const len4 = WASM_VECTOR_LEN;
205
- const ret = wasm.mesh_to_obj(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, convert_to_left_handed);
389
+ const ret = wasm.mesh_to_obj(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, isLikeNone(options) ? 0 : addToExternrefTable0(options));
206
390
  if (ret[3]) {
207
391
  throw takeFromExternrefTable0(ret[2]);
208
392
  }
@@ -218,27 +402,37 @@ export function mesh_to_obj(name, positions, tex_coords, normals, indices, conve
218
402
  * anything in between): n-gons are fan-triangulated and `(position, uv,
219
403
  * normal)` corners are deduplicated so the result is renderer-ready.
220
404
  *
221
- * `convert_to_left_handed` mirrors vpinball's `ObjLoader::Load` flag of
222
- * the same name (the "Convert coordinate system" checkbox in the
223
- * vpinball mesh-import dialog):
405
+ * `options.axes` names the convention the OBJ data is in; the returned
406
+ * mesh is always in vpx-internal convention:
224
407
  *
225
- * - `true` (matches `assemble`'s read path and vpinball's dialog
226
- * default): the input is treated as right-handed (Blender / standard
227
- * convention). Vertex Z is negated, normal Z is negated, V is flipped
228
- * (`vpx_tv = 1 - obj_v`), and the per-triangle corner order is
229
- * reversed. The returned mesh data ends up in vpx-internal,
230
- * left-handed convention.
231
- * - `false`: the input is assumed to already be in vpx-internal
232
- * convention (e.g. produced by a previous `mesh_to_obj` with the same
233
- * flag). The transforms are skipped and values pass through verbatim.
408
+ * - [`AxisConvention::ZDownRightHanded`] (the default; matches
409
+ * `assemble`'s read path and vpinball's mesh-import dialog with
410
+ * "Convert coordinate system" checked, the old `convert_to_left_handed
411
+ * = true`): vertex and normal Z are negated, V is flipped (`vpx_tv =
412
+ * 1 - obj_v`) and the per-triangle corner order is reversed. This path
413
+ * also honors the `# vpx <hex>` byte-preservation comments, so
414
+ * vpinball-format OBJs from `extract` reproduce the original vpx
415
+ * values bit-for-bit.
416
+ * - [`AxisConvention::YUpRightHanded`] (Blender / DCC default export
417
+ * settings): Y and Z are swapped, V is flipped and winding is
418
+ * reversed.
419
+ * - [`AxisConvention::ZUpLeftHanded`] (the old `convert_to_left_handed
420
+ * = false`): the input is assumed to already hold vpx-internal values
421
+ * (e.g. produced by a previous `mesh_to_obj` with the same
422
+ * convention). No transforms; values pass through verbatim.
423
+ *
424
+ * `options.unitScale` (default `1.0`) multiplies positions after the
425
+ * axis mapping. Normals and texture coordinates are never scaled. A
426
+ * mesh exported through `mesh_to_obj` with scale `k` reads back with
427
+ * scale `1.0 / k`.
234
428
  * @param {Uint8Array} data
235
- * @param {boolean} convert_to_left_handed
429
+ * @param {MeshIoOptions | null} [options]
236
430
  * @returns {PrimitiveMesh}
237
431
  */
238
- export function obj_to_mesh(data, convert_to_left_handed) {
432
+ export function obj_to_mesh(data, options) {
239
433
  const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
240
434
  const len0 = WASM_VECTOR_LEN;
241
- const ret = wasm.obj_to_mesh(ptr0, len0, convert_to_left_handed);
435
+ const ret = wasm.obj_to_mesh(ptr0, len0, isLikeNone(options) ? 0 : addToExternrefTable0(options));
242
436
  if (ret[2]) {
243
437
  throw takeFromExternrefTable0(ret[1]);
244
438
  }
@@ -251,6 +445,22 @@ function __wbg_get_imports() {
251
445
  const ret = Error(getStringFromWasm0(arg0, arg1));
252
446
  return ret;
253
447
  },
448
+ __wbg_Number_3890faa6d3ff057d: function(arg0) {
449
+ const ret = Number(arg0);
450
+ return ret;
451
+ },
452
+ __wbg_String_8564e559799eccda: function(arg0, arg1) {
453
+ const ret = String(arg1);
454
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
455
+ const len1 = WASM_VECTOR_LEN;
456
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
457
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
458
+ },
459
+ __wbg___wbindgen_boolean_get_c9c83ebd41b34df3: function(arg0) {
460
+ const v = arg0;
461
+ const ret = typeof(v) === 'boolean' ? v : undefined;
462
+ return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
463
+ },
254
464
  __wbg___wbindgen_debug_string_a57024b9c6e4a48b: function(arg0, arg1) {
255
465
  const ret = debugString(arg1);
256
466
  const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -258,6 +468,33 @@ function __wbg_get_imports() {
258
468
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
259
469
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
260
470
  },
471
+ __wbg___wbindgen_in_ac983077f137f2e6: function(arg0, arg1) {
472
+ const ret = arg0 in arg1;
473
+ return ret;
474
+ },
475
+ __wbg___wbindgen_is_null_7d13f41e1a2d5140: function(arg0) {
476
+ const ret = arg0 === null;
477
+ return ret;
478
+ },
479
+ __wbg___wbindgen_is_object_a2790eb24c211ea0: function(arg0) {
480
+ const val = arg0;
481
+ const ret = typeof(val) === 'object' && val !== null;
482
+ return ret;
483
+ },
484
+ __wbg___wbindgen_is_undefined_6cff064c44e0d823: function(arg0) {
485
+ const ret = arg0 === undefined;
486
+ return ret;
487
+ },
488
+ __wbg___wbindgen_jsval_loose_eq_acf2776254a8d832: function(arg0, arg1) {
489
+ const ret = arg0 == arg1;
490
+ return ret;
491
+ },
492
+ __wbg___wbindgen_number_get_136b9679cab35cfb: function(arg0, arg1) {
493
+ const obj = arg1;
494
+ const ret = typeof(obj) === 'number' ? obj : undefined;
495
+ getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
496
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
497
+ },
261
498
  __wbg___wbindgen_string_get_d154f1e671052120: function(arg0, arg1) {
262
499
  const obj = arg1;
263
500
  const ret = typeof(obj) === 'string' ? obj : undefined;
@@ -292,6 +529,34 @@ function __wbg_get_imports() {
292
529
  const ret = arg0[arg1 >>> 0];
293
530
  return ret;
294
531
  },
532
+ __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
533
+ const ret = arg0[arg1];
534
+ return ret;
535
+ },
536
+ __wbg_instanceof_ArrayBuffer_993d02d2d254cad1: function(arg0) {
537
+ let result;
538
+ try {
539
+ result = arg0 instanceof ArrayBuffer;
540
+ } catch (_) {
541
+ result = false;
542
+ }
543
+ const ret = result;
544
+ return ret;
545
+ },
546
+ __wbg_instanceof_Uint8Array_f935dbb0aa7cdeed: function(arg0) {
547
+ let result;
548
+ try {
549
+ result = arg0 instanceof Uint8Array;
550
+ } catch (_) {
551
+ result = false;
552
+ }
553
+ const ret = result;
554
+ return ret;
555
+ },
556
+ __wbg_isSafeInteger_f3d6cd19ccfe4512: function(arg0) {
557
+ const ret = Number.isSafeInteger(arg0);
558
+ return ret;
559
+ },
295
560
  __wbg_keys_ec7f8c0c2370d91d: function(arg0) {
296
561
  const ret = Object.keys(arg0);
297
562
  return ret;
@@ -308,6 +573,10 @@ function __wbg_get_imports() {
308
573
  const ret = new Error();
309
574
  return ret;
310
575
  },
576
+ __wbg_new_77cc4f4f472aeb81: function(arg0) {
577
+ const ret = new Uint8Array(arg0);
578
+ return ret;
579
+ },
311
580
  __wbg_new_ebe3e0f6837f0879: function() {
312
581
  const ret = new Object();
313
582
  return ret;
package/vpin_bg.wasm CHANGED
Binary file
package/vpin_bg.wasm.d.ts CHANGED
@@ -3,8 +3,11 @@
3
3
  export const memory: WebAssembly.Memory;
4
4
  export const __wbg_primitivemesh_free: (a: number, b: number) => void;
5
5
  export const assemble: (a: any, b: number) => [number, number, number, number];
6
+ export const export_glb: (a: any, b: number, c: number) => [number, number, number, number];
7
+ export const export_obj: (a: any, b: number, c: number) => [number, number, number];
6
8
  export const extract: (a: number, b: number, c: number) => [number, number, number];
7
9
  export const generate_builtin_primitive: (a: number, b: number) => [number, number, number];
10
+ export const mesh_to_glb: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
8
11
  export const mesh_to_obj: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
9
12
  export const obj_to_mesh: (a: number, b: number, c: number) => [number, number, number];
10
13
  export const primitivemesh_indices: (a: number) => any;