/ concept-collection / mesh-converter
Sign in
concept-collection / mesh-converter
mesh-converter / src / mesh / formats / bmsh.ts
60 lines · 2.1 KBCodeBlameHistory
edd4e73Mesh converter: upload, view, and export meshes across formatsJeremy Magland 1import type { MeshData, MeshFormat } from '../types'
2import { parseNumbers, validateIndices } from '../types'
4/**
5 * BMSH — "BareMesh" (invented, for illustration).
6 * The minimal format: raw geometry only. No normals, colors, or name.
7 *
8 * BMSH
9 * <nVertices> <nFaces>
10 * <x> <y> <z> (nVertices lines)
11 * <a> <b> <c> (nFaces lines)
12 */
13export const bmshFormat: MeshFormat = {
14 id: 'bmsh',
15 label: 'BMSH (BareMesh)',
16 extension: '.bmsh',
17 blurb: 'Bare geometry only: positions and faces, nothing else.',
18 capabilities: { normals: false, colors: false, name: false },
20 parse(text: string): MeshData {
21 const tokens = text.split(/\s+/).filter((t) => t.length > 0)
22 if (tokens[0] !== 'BMSH') {
23 throw new Error('BMSH: file must start with "BMSH"')
24 }
25 const nums = parseNumbers(tokens.slice(1), 'BMSH')
26 const nVertices = nums[0]
27 const nFaces = nums[1]
28 if (!Number.isInteger(nVertices) || !Number.isInteger(nFaces) || nVertices <= 0 || nFaces < 0) {
29 throw new Error('BMSH: invalid vertex/face counts')
30 }
31 const expected = 2 + nVertices * 3 + nFaces * 3
32 if (nums.length !== expected) {
33 throw new Error(`BMSH: expected ${expected - 2} numbers after counts, got ${nums.length - 2}`)
34 }
35 const positions = nums.slice(2, 2 + nVertices * 3)
36 const indices = nums.slice(2 + nVertices * 3)
37 validateIndices(indices, nVertices, 'BMSH')
39 return { name: null, positions, indices, normals: null, colors: null }
40 },
42 serialize(mesh: MeshData): string {
43 const nVertices = mesh.positions.length / 3
44 const nFaces = mesh.indices.length / 3
45 const out: string[] = ['BMSH', `${nVertices} ${nFaces}`]
46 for (let i = 0; i < nVertices; i++) {
47 out.push(
48 `${round6(mesh.positions[3 * i])} ${round6(mesh.positions[3 * i + 1])} ${round6(mesh.positions[3 * i + 2])}`,
49 )
50 }
51 for (let i = 0; i < nFaces; i++) {
52 out.push(`${mesh.indices[3 * i]} ${mesh.indices[3 * i + 1]} ${mesh.indices[3 * i + 2]}`)
53 }
54 return out.join('\n') + '\n'
55 },
58function round6(x: number): number {
59 return Math.round(x * 1e6) / 1e6
moveopenescclose