import { normalizeProjectAssetPath } from "./asset-path"; import type { VolumeGridMetadataIR } from "./scene-ir"; export const VDB_MAX_RESOURCE_BYTES = 512 * 1024 * 1024; export const VDB_MAX_ACTIVE_VOXELS = 64_000_000; export const VDB_MAX_GRIDS = 64; export interface VDBResourceManifest { projectId: string; sourcePath: string; byteLength: number; sha256: string; grids: VolumeGridMetadataIR[]; } export interface VDBDecodeRequest extends VDBResourceManifest { data: ArrayBuffer; } export interface VDBDecodeResult { metadata: VDBResourceManifest; decodedByteLength: number; } export type VDBDecoder = (request: VDBDecodeRequest, signal: AbortSignal) => Promise; function invalid(message: string): never { throw new Error(`NON_MESH_BINARY_INVALID: ${message}`); } export function validateVDBManifest(manifest: VDBResourceManifest): VDBResourceManifest { if (!manifest.projectId || !/^[a-zA-Z0-9._-]+$/.test(manifest.projectId)) invalid("VDB projectId is invalid"); let sourcePath: string; try { sourcePath = normalizeProjectAssetPath(manifest.sourcePath); } catch { throw new Error("NON_MESH_RESOURCE_OUTSIDE_PROJECT: VDB path is outside the project asset root"); } if (!sourcePath.toLowerCase().endsWith(".vdb")) invalid("Volume resources must use the .vdb extension"); if (!Number.isSafeInteger(manifest.byteLength) || manifest.byteLength <= 0 || manifest.byteLength > VDB_MAX_RESOURCE_BYTES) throw new Error("NON_MESH_VDB_BUDGET_EXCEEDED: VDB resource size is outside the bounded range"); if (!/^[a-f0-9]{64}$/.test(manifest.sha256)) invalid("VDB SHA-256 is invalid"); if (!Array.isArray(manifest.grids) || manifest.grids.length === 0 || manifest.grids.length > VDB_MAX_GRIDS) throw new Error("NON_MESH_VDB_BUDGET_EXCEEDED: VDB grid count is outside the bounded range"); const names = new Set(); let activeVoxels = 0; for (const grid of manifest.grids) { if (!grid.name || names.has(grid.name) || !grid.valueType) invalid("VDB grid identity is missing or duplicated"); names.add(grid.name); const count = grid.activeVoxelCount ?? grid.voxelCount; if (!Number.isSafeInteger(count) || count < 0) invalid(`VDB grid ${grid.name} has an invalid active voxel count`); activeVoxels += count; if (!Number.isSafeInteger(activeVoxels) || activeVoxels > VDB_MAX_ACTIVE_VOXELS) throw new Error("NON_MESH_VDB_BUDGET_EXCEEDED: VDB active voxel budget exceeded"); if (grid.bounds && grid.bounds.min.some((value, index) => !Number.isFinite(value) || value > grid.bounds!.max[index])) invalid(`VDB grid ${grid.name} bounds are invalid`); } return { ...manifest, sourcePath }; } function hex(bytes: Uint8Array): string { return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join(""); } export async function decodeVDBResource( request: VDBDecodeRequest, decoder: VDBDecoder | undefined, signal: AbortSignal, ): Promise { const metadata = validateVDBManifest(request); if (signal.aborted) throw new DOMException("VDB decode cancelled", "AbortError"); if (request.data.byteLength !== metadata.byteLength) invalid("VDB byte length does not match its manifest"); if (!globalThis.crypto?.subtle) invalid("SHA-256 is unavailable"); const digest = hex(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", request.data))); if (digest !== metadata.sha256) invalid("VDB bytes do not match the manifest SHA-256"); if (signal.aborted) throw new DOMException("VDB decode cancelled", "AbortError"); if (!decoder) throw new Error("VOLUME_SHADER_UNAVAILABLE: no bounded OpenVDB decoder is installed"); const result = await decoder({ ...request, ...metadata }, signal); if (signal.aborted) throw new DOMException("VDB decode cancelled", "AbortError"); if (!Number.isSafeInteger(result.decodedByteLength) || result.decodedByteLength < 0 || result.decodedByteLength > VDB_MAX_RESOURCE_BYTES * 2) throw new Error("NON_MESH_VDB_BUDGET_EXCEEDED: decoded VDB memory budget exceeded"); return { ...result, metadata }; }