import { Box3, BufferAttribute, BufferGeometry, Group, Sphere, Vector3, Vector4 } from 'three'; import { BUILDINGS } from '../constants/buildings.ts'; import { createBuildingMesh, finishBuildingScene } from './buildingScene.ts'; import type { BuildingCounts, BuildingHeightSource } from './buildingScene.ts'; import { restoreBuildingLightSpill } from './buildingLightSpill.ts'; import type { TerrainMetadata } from './terrainGeometry.ts'; export interface BuildingAssetManifest { version: number; sourceHash: string; bufferHash: string; byteLength: number; terrain: Pick; counts: BuildingCounts; target: [number, number, number]; meshes: { source: BuildingHeightSource; vertices: number; position: number; normal: number; lightHeight: number; index: number; indexCount: number; indexType: 'uint16' | 'uint32'; box: [number, number, number, number, number, number]; sphere: [number, number, number, number]; }[]; spill: { offset: number; size: number; bounds: [number, number, number, number] }; } export interface BuildingAssets { manifest: BuildingAssetManifest; buffer: ArrayBuffer } export async function loadBuildingAssets(): Promise { const response = await fetch(BUILDINGS.assetManifestUrl); if (!response.ok) throw new Error('건물 메타데이터를 불러오지 못했습니다.'); const manifest = await response.json() as BuildingAssetManifest; if (manifest.version !== BUILDINGS.assetVersion) throw new Error('건물 파일 버전이 다릅니다. 건물을 다시 생성해 주세요.'); const binary = await fetch(`${BUILDINGS.assetBufferUrl}?v=${manifest.bufferHash}`); if (!binary.ok) throw new Error('건물 버퍼를 불러오지 못했습니다.'); const buffer = await binary.arrayBuffer(); if (buffer.byteLength !== manifest.byteLength) throw new Error('건물 버퍼 크기가 올바르지 않습니다.'); return { manifest, buffer }; } export function createBakedBuildings({ manifest, buffer }: BuildingAssets, terrain: TerrainMetadata) { if (manifest.version !== BUILDINGS.assetVersion || buffer.byteLength !== manifest.byteLength) { throw new Error('건물 파일 버전 또는 버퍼 크기가 올바르지 않습니다.'); } if (manifest.terrain.origin.latitude !== terrain.origin.latitude || manifest.terrain.origin.longitude !== terrain.origin.longitude || manifest.terrain.observerSurfaceElevation !== terrain.observerSurfaceElevation) { throw new Error('건물과 지형 기준점이 다릅니다. 건물을 다시 생성해 주세요.'); } const group = new Group(); for (const mesh of manifest.meshes) { const geometry = new BufferGeometry(); // Views of the downloaded buffer: no extrusion, deduplication or array copies. geometry.setAttribute('position', new BufferAttribute(new Float32Array(buffer, mesh.position, mesh.vertices * 3), 3)); geometry.setAttribute('normal', new BufferAttribute(new Float32Array(buffer, mesh.normal, mesh.vertices * 3), 3)); geometry.setAttribute('buildingLightHeight', new BufferAttribute(new Float32Array(buffer, mesh.lightHeight, mesh.vertices), 1)); const indices = mesh.indexType === 'uint32' ? new Uint32Array(buffer, mesh.index, mesh.indexCount) : new Uint16Array(buffer, mesh.index, mesh.indexCount); geometry.setIndex(new BufferAttribute(indices, 1)); geometry.boundingBox = new Box3(new Vector3().fromArray(mesh.box), new Vector3().fromArray(mesh.box, 3)); geometry.boundingSphere = new Sphere(new Vector3().fromArray(mesh.sphere), mesh.sphere[3]); group.add(createBuildingMesh(mesh.source, geometry)); } const spill = restoreBuildingLightSpill(new Uint8Array(buffer, manifest.spill.offset, manifest.spill.size ** 2), manifest.spill.size, new Vector4().fromArray(manifest.spill.bounds)); return finishBuildingScene(group, manifest.counts, new Vector3().fromArray(manifest.target), spill); }