1/**
2 * Minimal Gmsh MSH 2.2 ASCII writer for a triangle surface mesh — plain
3 * string building, no dependencies. Vertices not referenced by any facet
4 * (interior points of the cloud) are dropped, and indices are remapped to
5 * the dense 1-based node ids the format expects.
6 */
7export function trianglesToMsh(points: number[][], facets: number[][]): string {
8 const used = new Map<number, number>() // original index -> 1-based node id
9 for (const f of facets) {
10 for (const idx of f) {
11 if (!used.has(idx)) used.set(idx, used.size + 1)
12 }
13 }
15 const lines = ['$MeshFormat', '2.2 0 8', '$EndMeshFormat', '$Nodes', String(used.size)]
16 for (const [idx, id] of used) {
17 const [x, y, z] = points[idx]
18 lines.push(`${id} ${x} ${y} ${z}`)
19 }
20 lines.push('$EndNodes', '$Elements', String(facets.length))
21 facets.forEach((f, i) => {
22 // element type 2 = 3-node triangle, two tags
23 lines.push(`${i + 1} 2 2 1 1 ${used.get(f[0])} ${used.get(f[1])} ${used.get(f[2])}`)
24 })
25 lines.push('$EndElements')
26 return lines.join('\n') + '\n'
27}
29export function downloadText(filename: string, text: string) {
30 const blob = new Blob([text], { type: 'application/octet-stream' })
31 const a = document.createElement('a')
32 a.href = URL.createObjectURL(blob)
33 a.download = filename
34 a.click()
35 URL.revokeObjectURL(a.href)
36}