(text: string)
| 62 | |
| 63 | // OBJ parser (pure TypeScript) |
| 64 | function parseOBJ(text: string): { vertices: number[]; indices: number[] } | null { |
| 65 | const positions: number[][] = []; |
| 66 | const normals: number[][] = []; |
| 67 | const texcoords: number[][] = []; |
| 68 | const vertexMap = new Map<string, number>(); |
| 69 | const vertices: number[] = []; |
| 70 | const indices: number[] = []; |
| 71 | let vertexCount = 0; |
| 72 | |
| 73 | const lines = text.split('\n'); |
| 74 | for (let i = 0; i < lines.length; i++) { |
| 75 | const line = lines[i].trim(); |
| 76 | if (line.length === 0 || line[0] === '#') continue; |
| 77 | |
| 78 | const parts = line.split(/\s+/); |
| 79 | const cmd = parts[0]; |
| 80 | |
| 81 | if (cmd === 'v' && parts.length >= 4) { |
| 82 | positions.push([parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])]); |
| 83 | } else if (cmd === 'vn' && parts.length >= 4) { |
| 84 | normals.push([parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])]); |
| 85 | } else if (cmd === 'vt' && parts.length >= 3) { |
| 86 | texcoords.push([parseFloat(parts[1]), parseFloat(parts[2])]); |
| 87 | } else if (cmd === 'f') { |
| 88 | // Triangulate face (fan from first vertex) |
| 89 | const faceIndices: number[] = []; |
| 90 | for (let j = 1; j < parts.length; j++) { |
| 91 | const key = parts[j]; |
| 92 | if (vertexMap.has(key)) { |
| 93 | faceIndices.push(vertexMap.get(key)!); |
| 94 | } else { |
| 95 | const segs = key.split('/'); |
| 96 | const pi = parseInt(segs[0]) - 1; |
| 97 | const ti = segs.length > 1 && segs[1] !== '' ? parseInt(segs[1]) - 1 : -1; |
| 98 | const ni = segs.length > 2 ? parseInt(segs[2]) - 1 : -1; |
| 99 | |
| 100 | const pos = pi >= 0 && pi < positions.length ? positions[pi] : [0, 0, 0]; |
| 101 | const norm = ni >= 0 && ni < normals.length ? normals[ni] : [0, 1, 0]; |
| 102 | const uv = ti >= 0 && ti < texcoords.length ? texcoords[ti] : [0, 0]; |
| 103 | |
| 104 | // Format: x,y,z, nx,ny,nz, r,g,b,a, u,v (12 floats per vertex) |
| 105 | vertices.push(pos[0], pos[1], pos[2]); |
| 106 | vertices.push(norm[0], norm[1], norm[2]); |
| 107 | vertices.push(1, 1, 1, 1); // white color |
| 108 | vertices.push(uv[0], uv[1]); |
| 109 | |
| 110 | const idx = vertexCount; |
| 111 | vertexCount++; |
| 112 | vertexMap.set(key, idx); |
| 113 | faceIndices.push(idx); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // Fan triangulation |
| 118 | for (let j = 2; j < faceIndices.length; j++) { |
| 119 | indices.push(faceIndices[0], faceIndices[j - 1], faceIndices[j]); |
| 120 | } |
| 121 | } |
no test coverage detected