From d4ccff6342434e63b4244b00bd7a144192df2aa2 Mon Sep 17 00:00:00 2001 From: Andreas Wilms Date: Mon, 9 Mar 2026 14:18:49 +0100 Subject: [PATCH] lod export --- app/page.tsx | 14 +++-- app/workers/converter.worker.ts | 103 +++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 2586607..52ba79b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,8 +8,6 @@ export default function XgridsWizard() { const workerRef = useRef(null); useEffect(() => { - // 1. Point to the NEW location in the /app folder - // Next.js recognizes the 'new URL' pattern and bundles it as a separate worker const worker = new Worker( new URL("./workers/converter.worker.ts", import.meta.url), ); @@ -18,11 +16,17 @@ export default function XgridsWizard() { worker.onmessage = (e) => { const { type, message, data } = e.data; if (type === "LOG") setStatus(message); + if (type === "DONE") { setIsProcessing(false); - setStatus("Conversion Complete!"); - downloadFile(data.sog, `${data.fileName}.sog`); - // Removed: downloadFile(data.ply, ...) — worker doesn't produce a PLY + setStatus( + `Conversion Complete! Downloading ${data.files.length} files...`, + ); + + // Loop through all generated files and trigger downloads + data.files.forEach((file: { name: string; blob: Blob }) => { + downloadFile(file.blob, file.name); + }); } }; diff --git a/app/workers/converter.worker.ts b/app/workers/converter.worker.ts index 0452bd2..b8bb865 100644 --- a/app/workers/converter.worker.ts +++ b/app/workers/converter.worker.ts @@ -41,13 +41,16 @@ self.onmessage = async (e: MessageEvent) => { try { self.postMessage({ type: "LOG", message: "Initialisiere..." }); - // Fetch the WASM binary ourselves and inject it - const wasmResponse = await fetch("/workers/webp.wasm"); - const wasmBinary = await wasmResponse.arrayBuffer(); - - // Inject before the module loads + // Emscripten's native locateFile hook // @ts-ignore - globalThis.Module = { wasmBinary }; + globalThis.Module = globalThis.Module || {}; + // @ts-ignore + globalThis.Module.locateFile = function (path: string) { + if (path.endsWith(".wasm")) { + return new URL("/webp.wasm", self.location.origin).href; + } + return path; + }; const { readFile, @@ -55,7 +58,6 @@ self.onmessage = async (e: MessageEvent) => { MemoryReadFileSystem, MemoryFileSystem, getInputFormat, - getOutputFormat, } = await import("@playcanvas/splat-transform"); const readFs = new MemoryReadFileSystem(); @@ -69,9 +71,9 @@ self.onmessage = async (e: MessageEvent) => { readFs.set(file.name, new Uint8Array(file.buffer)); } - const fullOptions = { + const readOptions = { iterations: 0, - lodSelect: [0], + lodSelect: [0, 1, 2, 3, 4], // we have captured a total level of 5 unbundled: false, lodChunkCount: 0, lodChunkExtent: 0, @@ -84,37 +86,92 @@ self.onmessage = async (e: MessageEvent) => { fileSystem: readFs, inputFormat: getInputFormat(mainLccName), params: [], - options: fullOptions, + options: readOptions, }); const mainTable = tables[0]; if (!mainTable) throw new Error("Keine Splat-Daten gefunden."); - self.postMessage({ type: "LOG", message: "Kompiliere SOG..." }); + const generatedFiles: { name: string; blob: Blob }[] = []; - const writeFs = new MemoryFileSystem(); - const outputName = `${fileName}.sog`; + // PASS 1: Generate Single High-Quality SOG + self.postMessage({ type: "LOG", message: "Kompiliere Single SOG..." }); + + const writeFsSingle = new MemoryFileSystem(); + const singleOutputName = `${fileName}.sog`; + const singleOptions = { + ...readOptions, + iterations: 10, + unbundled: false, + }; await writeFile( { - filename: outputName, - outputFormat: getOutputFormat(outputName, fullOptions), + filename: singleOutputName, + outputFormat: "sog", dataTable: mainTable, - options: { ...fullOptions, iterations: 8 }, + options: singleOptions, }, - writeFs, + writeFsSingle, ); - const sogData = writeFs.results.get(outputName); - if (!sogData) throw new Error("SOG-Erstellung fehlgeschlagen."); + const singleSogData = writeFsSingle.results.get(singleOutputName); + if (singleSogData) { + generatedFiles.push({ + name: singleOutputName, + blob: new Blob([new Uint8Array(singleSogData).slice().buffer], { + type: "application/octet-stream", + }), + }); + } + // ========================================== + // PASS 2: Generate Unbundled LOD SOGs + JSON + // ========================================== + self.postMessage({ type: "LOG", message: "Kompiliere LOD Chunks..." }); + + const writeFsLods = new MemoryFileSystem(); + + // MUST be exactly "meta.json" for unbundled SOG format + const lodsOutputName = "meta.json"; + + const lodOptions = { + ...readOptions, + iterations: 10, + unbundled: true, + lodChunkCount: 512, + lodChunkExtent: 16, + }; + + await writeFile( + { + filename: lodsOutputName, + outputFormat: "sog", + dataTable: mainTable, + options: lodOptions, + }, + writeFsLods, + ); + + // Jetzt iterieren wir über alle generierten Dateien im System + for (const [generatedName, data] of writeFsLods.results.entries()) { + const mimeType = generatedName.endsWith(".json") + ? "application/json" + : "application/octet-stream"; + + generatedFiles.push({ + name: generatedName, + blob: new Blob([new Uint8Array(data).slice().buffer], { + type: mimeType, + }), + }); + } + + // Send all Data to Frontend self.postMessage({ type: "DONE", data: { - sog: new Blob([new Uint8Array(sogData).slice().buffer], { - type: "application/octet-stream", - }), - fileName, + files: generatedFiles, }, }); } catch (err: any) {