import { Document, NodeIO } from "@gltf-transform/core"; import { readFileSync, mkdirSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { buildGeometry } from "./geometry.mjs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const manifestPath = path.resolve( __dirname, process.argv[2] ?? "../seed/manifest-tegumentar.json", ); const outDir = path.resolve(__dirname, "../seed/models"); mkdirSync(outDir, { recursive: true }); const { structures } = JSON.parse(readFileSync(manifestPath, "utf-8")); function eulerToQuat([x, y, z]) { const cx = Math.cos(x / 2), sx = Math.sin(x / 2); const cy = Math.cos(y / 2), sy = Math.sin(y / 2); const cz = Math.cos(z / 2), sz = Math.sin(z / 2); return [ sx * cy * cz - cx * sy * sz, cx * sy * cz + sx * cy * sz, cx * cy * sz - sx * sy * cz, cx * cy * cz + sx * sy * sz, ]; } function hexToRgb(hex) { const n = parseInt(hex.replace("#", ""), 16); return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1]; } const bySystem = new Map(); for (const s of structures) { if (!bySystem.has(s.system)) bySystem.set(s.system, []); bySystem.get(s.system).push(s); } const systemColors = { esqueletico: "#e8e2d0", muscular: "#b5433a", nervoso: "#f2c14e", cardiovascular: "#c1121f", respiratorio: "#4f7cff", tegumentar: "#e0a96d", }; for (const [systemSlug, items] of bySystem) { const doc = new Document(); const buffer = doc.createBuffer(); const scene = doc.createScene(systemSlug); doc.getRoot().setDefaultScene(scene); // Cada item pode pedir uma cor própria (opts.color) pra se distinguir dos // demais (ex: glândula amarela, vaso vermelho/azul, pelo castanho) em vez // de todo o sistema sair de uma cor só — cache por hex evita recriar // material repetido. const materialCache = new Map(); function materialFor(hex) { if (materialCache.has(hex)) return materialCache.get(hex); const mat = doc .createMaterial(hex) .setBaseColorFactor(hexToRgb(hex)) .setRoughnessFactor(0.6) .setMetallicFactor(0.05); materialCache.set(hex, mat); return mat; } const defaultHex = systemColors[systemSlug] ?? "#cccccc"; for (const item of items) { const material = materialFor(item.color ?? defaultHex); const geo = buildGeometry(item.shape); const posAccessor = doc .createAccessor() .setType("VEC3") .setArray(geo.positions) .setBuffer(buffer); const normAccessor = doc .createAccessor() .setType("VEC3") .setArray(geo.normals) .setBuffer(buffer); const idxAccessor = doc .createAccessor() .setType("SCALAR") .setArray(geo.indices) .setBuffer(buffer); const prim = doc .createPrimitive() .setAttribute("POSITION", posAccessor) .setAttribute("NORMAL", normAccessor) .setIndices(idxAccessor) .setMaterial(material); const mesh = doc.createMesh(item.meshName).addPrimitive(prim); const node = doc .createNode(item.meshName) .setMesh(mesh) .setTranslation(item.position) .setRotation(eulerToQuat(item.rotation ?? [0, 0, 0])) .setExtras({ slug: item.slug }); scene.addChild(node); } const io = new NodeIO(); const outFile = path.join(outDir, `${systemSlug}.glb`); await io.write(outFile, doc); console.log(`✓ ${outFile} (${items.length} estruturas)`); }