28f8ec1mesh-pde-solver: upload a quad mesh, solve PDEs on the surface in-browserJeremy Magland 1/** The app's internal quad-mesh representation and connectivity helpers. */
3export interface QuadMeshData {
4 /** xyz triples, one per vertex */
5 positions: Float32Array
6 /** 4 vertex indices per quad, Gmsh corner order (counterclockwise) */
7 quads: Uint32Array
8 /** the canonical Gmsh MSH 2.2 file the solver reads */
9 mshBytes: Uint8Array
10}
12export interface QuadMeshInfo {
13 numVertices: number
14 numQuads: number
15 /** every edge shared by exactly two quads */
16 closed: boolean
17 /** some edge shared by more than two quads */
18 nonManifold: boolean
19 warnings: string[]
20}
22/**
23 * Classify the mesh from its edge incidence: closed (all edges shared by 2
24 * quads), open (some boundary edges), or non-manifold (an edge on >2 quads).
25 */
26export function edgeClassification(quads: Uint32Array): {
27 closed: boolean
28 nonManifold: boolean
29} {
30 const counts = new Map<number, number>()
31 const nq = quads.length / 4
32 for (let k = 0; k < nq; k++) {
33 for (let e = 0; e < 4; e++) {
34 const a = quads[k * 4 + e]
35 const b = quads[k * 4 + ((e + 1) % 4)]
36 // 2^26 > any vertex count we accept; safe integer key for the pair
37 const key = a < b ? a * 67108864 + b : b * 67108864 + a
38 counts.set(key, (counts.get(key) ?? 0) + 1)
39 }
40 }
41 let closed = true
42 let nonManifold = false
43 for (const c of counts.values()) {
44 if (c !== 2) closed = false
45 if (c > 2) nonManifold = true
46 }
47 return { closed, nonManifold }
48}
50/** Axis-aligned bounding box diagonal, for camera framing. */
51export function meshBounds(positions: Float32Array): {
52 center: [number, number, number]
53 size: number
54} {
55 let minX = Infinity, minY = Infinity, minZ = Infinity
56 let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity
57 for (let i = 0; i < positions.length; i += 3) {
58 const x = positions[i], y = positions[i + 1], z = positions[i + 2]
59 if (x < minX) minX = x
60 if (y < minY) minY = y
61 if (z < minZ) minZ = z
62 if (x > maxX) maxX = x
63 if (y > maxY) maxY = y
64 if (z > maxZ) maxZ = z
65 }
66 const size = Math.max(maxX - minX, maxY - minY, maxZ - minZ) || 1
67 return { center: [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2], size }
68}