import * as THREE from 'three'; import {readFileSync} from 'node:fs'; /** Geometry-only GLB reader for deterministic Node tests; browser uses GLTFLoader. */ export function loadFrameGeometry(file=new URL('../../public/assets/window/grandma-wood-sash.glb',import.meta.url)){ const bytes=readFileSync(file); const length=bytes.readUInt32LE(12),gltf=JSON.parse(bytes.subarray(20,20+length).toString()),bin=bytes.subarray(28+length); const types={5121:Uint8Array,5126:Float32Array,5123:Uint16Array,5125:Uint32Array},counts={SCALAR:1,VEC2:2,VEC3:3,VEC4:4}; const accessor=i=>{const a=gltf.accessors[i],v=gltf.bufferViews[a.bufferView],T=types[a.componentType],n=counts[a.type],array=new T(a.count*n);for(let row=0;rownew THREE.MeshStandardMaterial({name:m.name,side:m.doubleSided?THREE.DoubleSide:THREE.FrontSide})); const nodes=gltf.nodes.map(n=>{let obj=new THREE.Group();if(n.mesh!==undefined){const primitive=gltf.meshes[n.mesh].primitives[0],geometry=new THREE.BufferGeometry();for(const [key,value] of Object.entries(primitive.attributes))geometry.setAttribute({POSITION:'position',NORMAL:'normal',TEXCOORD_0:'uv',TANGENT:'tangent',COLOR_0:'color'}[key],accessor(value));geometry.setIndex(accessor(primitive.indices));obj=new THREE.Mesh(geometry,materials[primitive.material]);}obj.name=n.name;if(n.translation)obj.position.fromArray(n.translation);return obj;}); gltf.nodes.forEach((node,i)=>(node.children??[]).forEach(c=>nodes[i].add(nodes[c]))); const scene=new THREE.Group();gltf.scenes[gltf.scene??0].nodes.forEach(i=>scene.add(nodes[i]));return scene; }