1/**
2 * Internal mesh representation. Every format parses into this and
3 * serializes out of it. Optional attributes are null when absent.
4 */
5export interface MeshData {
6 /** Human-readable mesh name (not all formats can store one) */
7 name: string | null
8 /** Flat xyz triples, 3 numbers per vertex */
9 positions: number[]
10 /** Flat triangle indices (0-based), 3 numbers per face */
11 indices: number[]
12 /** Flat xyz triples, 3 numbers per vertex, or null */
13 normals: number[] | null
14 /** Flat rgb triples in [0,1], 3 numbers per vertex, or null */
15 colors: number[] | null
16}
18export interface MeshCapabilities {
19 normals: boolean
20 colors: boolean
21 name: boolean
22}
24export interface MeshFormat {
25 id: string
26 label: string
27 /** File extension including the dot, e.g. ".mopf" */
28 extension: string
29 blurb: string
30 capabilities: MeshCapabilities
31 /** Parse file text; throws Error with a user-facing message on bad input */
32 parse(text: string): MeshData
33 /** Serialize, silently dropping attributes the format cannot hold */
34 serialize(mesh: MeshData): string
35}
37export function vertexCount(mesh: MeshData): number {
38 return mesh.positions.length / 3
39}
41export function faceCount(mesh: MeshData): number {
42 return mesh.indices.length / 3
43}
45export function parseNumbers(tokens: string[], context: string): number[] {
46 return tokens.map((t) => {
47 const x = Number(t)
48 if (!Number.isFinite(x)) {
49 throw new Error(`${context}: "${t}" is not a number`)
50 }
51 return x
52 })
53}
55export function validateIndices(indices: number[], nVertices: number, context: string): void {
56 for (const i of indices) {
57 if (!Number.isInteger(i) || i < 0 || i >= nVertices) {
58 throw new Error(`${context}: face index ${i} out of range (0..${nVertices - 1})`)
59 }
60 }
61}