import { createHash } from 'node:crypto'; import { readFileSync,writeFileSync } from 'node:fs'; import { gzipSync,gunzipSync } from 'node:zlib'; import { createTerrainGeometry } from '../src/scene/terrainGeometry.ts'; import { applySummitTerrain } from '../src/scene/summitTerrain.ts'; import { createSummitWalking } from '../src/controls/summitWalking.ts'; import { buildSummitGeometry } from '../src/scene/summitGeometry.ts'; import { encodeSummitGeometry,decodeSummitGeometry } from '../src/scene/summitAssets.ts'; import { SUMMIT_ASSETS } from '../src/constants/summitAssets.ts'; const file=(path:string)=>new URL('../'+path,import.meta.url); const hash=(data:Uint8Array)=>createHash('sha256').update(data).digest('hex'); const inputs=['public/terrain/gwanaksan.json','public/terrain/gwanaksan.f32','src/scene/terrainGeometry.ts', 'src/scene/summitTerrain.ts','src/scene/summitGeometry.ts','src/scene/summitAssets.ts','src/scene/structures.ts', 'src/controls/summitWalking.ts','src/controls/summitCoordinates.ts','src/constants/summitAssets.ts','src/constants/summitMaterials.ts', 'src/constants/summitTerrain.ts','src/constants/summitTrails.ts','src/constants/structures.ts','src/constants/terrain.ts', 'src/constants/summitPlateau.ts','scripts/prepare-summit-assets.ts','package-lock.json']; const digest=createHash('sha256'); for(const path of inputs) {const bytes=readFileSync(file(path));digest.update(path).update('\0').update(path.endsWith('.f32')?bytes:bytes.toString('utf8').replace(/\r\n/g,'\n'));} const sourceHash=digest.digest('hex'); const manifestPath=file('public'+SUMMIT_ASSETS.manifestUrl),binaryPath=file('public'+SUMMIT_ASSETS.bufferUrl),gzipPath=file('public'+SUMMIT_ASSETS.bufferUrl+'.gz'); if(process.argv.includes('--check')) { try { const manifest=JSON.parse(readFileSync(manifestPath,'utf8')),bytes=readFileSync(binaryPath); if(manifest.version!==SUMMIT_ASSETS.version||manifest.sourceHash!==sourceHash||manifest.bufferHash!==hash(bytes)||manifest.byteLength!==bytes.length||!gunzipSync(readFileSync(gzipPath)).equals(bytes))throw new Error('stale'); console.log('Prebuilt summit is up to date.'); } catch {console.error('정상 지면 사전 생성 파일이 오래되었습니다. npm run prepare:summit 을 실행해 주세요.');process.exitCode=1;} } else { const meta=JSON.parse(readFileSync(file('public/terrain/gwanaksan.json'),'utf8')); const bytes=readFileSync(file('public/terrain/gwanaksan.f32')); const terrain=createTerrainGeometry(meta,new Float32Array(bytes.buffer,bytes.byteOffset,bytes.byteLength/4),true); applySummitTerrain(terrain,meta); const started=performance.now(); const original=buildSummitGeometry(createSummitWalking(meta.origin),terrain,true); const generateMs=performance.now()-started; const encoded=encodeSummitGeometry(original),buffer=Buffer.from(encoded),compressed=gzipSync(buffer,{level:9}); const decodedAt=performance.now(),restored=decodeSummitGeometry(encoded),decodeMs=performance.now()-decodedAt; // Verify all triangles, including every normal, UV and material distance, after indexing. for(const [name,size] of SUMMIT_ASSETS.attributes) { const a=original.getAttribute(name),b=restored.getAttribute(name),index=restored.getIndex()!; for(let i=0;in+a.array.byteLength,0),generateMs,decodeMs}; writeFileSync(binaryPath,buffer);writeFileSync(gzipPath,compressed);writeFileSync(manifestPath,JSON.stringify(manifest,null,2)+'\n'); console.log(JSON.stringify(manifest,null,2));original.dispose();restored.dispose();terrain.dispose(); }