import assert from 'node:assert/strict'; import {readFileSync,writeFileSync} from 'node:fs'; import {Box3,Mesh,MeshStandardMaterial,Vector3} from 'three'; import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js'; import {createSummitRelief} from '../src/scene/summitRelief.ts'; const bytes=readFileSync('public/models/summit/radar-rocks.glb'); const json=JSON.parse(bytes.toString('utf8',20,20+bytes.readUInt32LE(12))); assert.equal(json.images?.length??0,0,'reference photos must not be embedded'); assert.equal(json.cameras?.length??0,0,'study camera is not exported'); const asset=(await new GLTFLoader().parseAsync(bytes.buffer.slice(bytes.byteOffset,bytes.byteOffset+bytes.byteLength),'')).scene; const bounds=new Box3().setFromObject(asset); assert.ok(bounds.min.x<-25&&bounds.max.x>5,'west outcrop and radar wall are both present'); assert.ok(bounds.min.y<-25&&bounds.max.y>7&&bounds.max.y<12,'metres and Y-up conversion'); let triangles=0,meshes=0,disposed=0; asset.traverse(o=>{if(o instanceof Mesh){ meshes++;assert.ok(o.geometry.hasAttribute('uv'));assert.ok(o.geometry.hasAttribute('normal')); for(const a of Object.values(o.geometry.attributes))assert.ok(a.array.every(Number.isFinite)); triangles+=(o.geometry.index?.count??o.geometry.getAttribute('position').count)/3; o.geometry.addEventListener('dispose',()=>disposed++); }}); assert.ok(triangles>1000&&triangles<100000); const material=new MeshStandardMaterial(),anchor=new Vector3(20,30,50); const relief=createSummitRelief(anchor,()=>0,material,asset); assert.deepEqual(asset.position.toArray(),anchor.toArray()); assert.equal(relief.group.children.filter(o=>o===asset).length,1,'authored model placed once'); relief.dispose();material.dispose();assert.equal(disposed,meshes); const report={passed:true,bytes:bytes.length,meshes,triangles,localBounds:{min:bounds.min.toArray(),max:bounds.max.toArray()},embeddedPhotos:false}; writeFileSync('assets/summit/blender-rock-study/verification.json',JSON.stringify(report,null,2)); console.log(report);