From 9e7ba74da3b3f1fa3910a006cab62c65cac85e7c Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 11 Oct 2025 16:36:05 +0100 Subject: [PATCH 1/5] Support specifying settings.json to configure the HTML Viewer --- README.md | 19 ++++++++++------ src/index.ts | 28 ++++++++++++++--------- src/writers/write-html.ts | 47 ++++++++++++++++++++++++++++++--------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 30a9826..e55bb1b 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ splat-transform [GLOBAL] input [ACTIONS] ... output [ACTIONS] Actions can be repeated and applied in any order: -```bash +```none -t, --translate Translate splats by (x, y, z). -r, --rotate Rotate splats by Euler angles (x, y, z), in degrees. -s, --scale Uniformly scale splats by factor. @@ -69,16 +69,18 @@ Actions can be repeated and applied in any order: ## Global Options -```bash +```none -h, --help Show this help and exit. -v, --version Show version and exit. -w, --overwrite Overwrite output file if it exists. --c, --cpu Use CPU for spherical harmonic compression. +-c, --cpu Use CPU for SOG SH compression. -i, --iterations Iterations for SOG SH compression (more = better). Default: 10. --C, --camera-pos HTML viewer camera position. Default: (2, 2, -2). --T, --camera-target HTML viewer target position. Default: (0, 0, 0). +-E, --viewer-settings HTML viewer settings JSON file. ``` +> [!NOTE] +> See the [SuperSplat Viewer Settings Schema](https://github.com/playcanvas/supersplat-viewer?tab=readme-ov-file#settings-schema) for details on how to pass data to the `-E` option. + ## Examples ### Basic Operations @@ -112,8 +114,11 @@ splat-transform scene.sog restored.ply # Convert from SOG (unbundled folder) back to PLY splat-transform output/meta.json restored.ply -# Convert to HTML viewer with target and camera location -splat-transform -C 0,0,0 -T 0,0,10 input.ply output.html +# Convert to standalone HTML viewer +splat-transform input.ply output.html + +# Convert to HTML viewer with custom settings +splat-transform -E settings.json input.ply output.html ``` ### Transformations diff --git a/src/index.ts b/src/index.ts index 6570dcb..d14556c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,8 +28,7 @@ type Options = { version: boolean, cpu: boolean, iterations: number, - cameraPos: Vec3, - cameraTarget: Vec3 + viewerSettingsPath?: string }; const fileExists = async (filename: string) => { @@ -136,7 +135,13 @@ const writeFile = async (filename: string, dataTable: DataTable, options: Option }); break; case 'html': - await writeHtml(outputFile, dataTable, options.cameraPos, options.cameraTarget, options.iterations, options.cpu ? 'cpu' : 'gpu'); + await writeHtml( + outputFile, + dataTable, + options.iterations, + options.cpu ? 'cpu' : 'gpu', + options.viewerSettingsPath + ); break; } @@ -237,8 +242,7 @@ const parseArguments = () => { version: { type: 'boolean', short: 'v' }, cpu: { type: 'boolean', short: 'c' }, iterations: { type: 'string', short: 'i' }, - 'camera-pos': { type: 'string', short: 'C' }, - 'camera-target': { type: 'string', short: 'T' }, + 'viewer-settings': { type: 'string', short: 'E' }, // file options translate: { type: 'string', short: 't', multiple: true }, @@ -291,14 +295,14 @@ const parseArguments = () => { }; const files: File[] = []; + const options: Options = { overwrite: v.overwrite ?? false, help: v.help ?? false, version: v.version ?? false, cpu: v.cpu ?? false, iterations: parseInteger(v.iterations ?? '10'), - cameraPos: parseVec3((v as any)['camera-pos'] ?? '2,2,-2'), - cameraTarget: parseVec3((v as any)['camera-target'] ?? '0,0,0') + viewerSettingsPath: (v as any)['viewer-settings'] }; for (const t of tokens) { @@ -447,8 +451,7 @@ GLOBAL OPTIONS -w, --overwrite Overwrite output file if it exists. -c, --cpu Use CPU for spherical harmonic compression. -i, --iterations Iterations for SOG SH compression (more = better). Default: 10. - -C, --camera-pos HTML viewer camera position. Default: (2, 2, -2). - -T, --camera-target HTML viewer target position. Default: (0, 0, 0). + -E, --viewer-settings HTML viewer settings JSON file. EXAMPLES # Scale then translate @@ -457,8 +460,11 @@ EXAMPLES # Merge two files with transforms splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply - # HTML viewer with custom camera - splat-transform -C 0,0,0 -T 0,0,10 bunny.ply bunny_app.html + # HTML viewer with default settings + splat-transform bunny.ply bunny_app.html + + # HTML viewer with custom settings + splat-transform -E settings.json bunny.ply bunny_app.html GENERATORS (beta) # Generate synthetic splats diff --git a/src/writers/write-html.ts b/src/writers/write-html.ts index f32f756..97fa730 100644 --- a/src/writers/write-html.ts +++ b/src/writers/write-html.ts @@ -1,30 +1,57 @@ -import { open, unlink, FileHandle } from 'node:fs/promises'; +import { open, readFile, unlink, FileHandle } from 'node:fs/promises'; import os from 'node:os'; import { html, css, js } from '@playcanvas/supersplat-viewer'; -import { Vec3 } from 'playcanvas'; import { DataTable } from '../data-table'; import { writeSog } from './write-sog'; -const writeHtml = async (fileHandle: FileHandle, dataTable: DataTable, camera: Vec3, target: Vec3, iterations: number, shMethod: 'cpu' | 'gpu') => { +type ViewerSettings = { + camera?: { + fov?: number; + position?: [number, number, number]; + target?: [number, number, number]; + startAnim?: string; + animTrack?: string; + }; + background?: { + color?: [number, number, number]; + }; + animTracks?: unknown[]; +}; + +const writeHtml = async (fileHandle: FileHandle, dataTable: DataTable, iterations: number, shMethod: 'cpu' | 'gpu', viewerSettingsPath?: string) => { const pad = (text: string, spaces: number) => { const whitespace = ' '.repeat(spaces); return text.split('\n').map(line => whitespace + line).join('\n'); }; - const experienceSettings = { + // Load viewer settings from file if provided + let viewerSettings: ViewerSettings = {}; + if (viewerSettingsPath) { + const content = await readFile(viewerSettingsPath, 'utf-8'); + try { + viewerSettings = JSON.parse(content); + } catch (e) { + throw new Error(`Failed to parse viewer settings JSON file: ${viewerSettingsPath}`); + } + } + + // Merge provided settings with defaults + const mergedSettings = { camera: { fov: 50, - position: [camera.x, camera.y, camera.z], - target: [target.x, target.y, target.z], + position: [2, 2, -2] as [number, number, number], + target: [0, 0, 0] as [number, number, number], startAnim: 'none', - animTrack: undefined as unknown as string | undefined + animTrack: undefined as string | undefined, + ...viewerSettings.camera }, background: { - color: [0.4, 0.4, 0.4] + color: [0.4, 0.4, 0.4] as [number, number, number], + ...viewerSettings.background }, - animTracks: [] as unknown[] + animTracks: viewerSettings.animTracks ?? [] }; const tempSogPath = `${os.tmpdir()}/temp.sog`; @@ -44,7 +71,7 @@ const writeHtml = async (fileHandle: FileHandle, dataTable: DataTable, camera: V const generatedHtml = html .replace(style, ``) .replace(script, ``) - .replace(settings, `settings: ${JSON.stringify(experienceSettings)}`) + .replace(settings, `settings: ${JSON.stringify(mergedSettings)}`) .replace(content, `fetch("data:application/octet-stream;base64,${sogData}")`) .replace('.compressed.ply', '.sog'); From fdfff113adb87ca08bf1819f2b77b307bf160c93 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 11 Oct 2025 16:40:38 +0100 Subject: [PATCH 2/5] Help tweaks --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index d14556c..71d7d2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -455,16 +455,16 @@ GLOBAL OPTIONS EXAMPLES # Scale then translate - splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply + splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny-scaled.ply # Merge two files with transforms splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply # HTML viewer with default settings - splat-transform bunny.ply bunny_app.html + splat-transform bunny.ply bunny-viewer.html # HTML viewer with custom settings - splat-transform -E settings.json bunny.ply bunny_app.html + splat-transform -E settings.json bunny.ply bunny-viewer.html GENERATORS (beta) # Generate synthetic splats From 9b523747a5a5da2a851d1e64f25faf7658d4da0d Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 11 Oct 2025 17:01:34 +0100 Subject: [PATCH 3/5] Reformat call to writeHtml --- src/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 71d7d2f..9f668b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -135,13 +135,7 @@ const writeFile = async (filename: string, dataTable: DataTable, options: Option }); break; case 'html': - await writeHtml( - outputFile, - dataTable, - options.iterations, - options.cpu ? 'cpu' : 'gpu', - options.viewerSettingsPath - ); + await writeHtml(outputFile, dataTable, options.iterations, options.cpu ? 'cpu' : 'gpu', options.viewerSettingsPath); break; } From 15a924fdba654b6fd51e8a9743b61e47a1517e94 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 11 Oct 2025 17:03:10 +0100 Subject: [PATCH 4/5] Remove newline --- src/writers/write-html.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/writers/write-html.ts b/src/writers/write-html.ts index 97fa730..356a20c 100644 --- a/src/writers/write-html.ts +++ b/src/writers/write-html.ts @@ -76,7 +76,6 @@ const writeHtml = async (fileHandle: FileHandle, dataTable: DataTable, iteration .replace('.compressed.ply', '.sog'); await fileHandle.write(new TextEncoder().encode(generatedHtml)); - }; export { writeHtml }; From a407018d40ff5e56deb993b07db41b977fd1899d Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Sat, 11 Oct 2025 17:07:23 +0100 Subject: [PATCH 5/5] Trim CLI help --- src/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9f668b9..2db6ce0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -454,10 +454,7 @@ EXAMPLES # Merge two files with transforms splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply - # HTML viewer with default settings - splat-transform bunny.ply bunny-viewer.html - - # HTML viewer with custom settings + # Generate HTML viewer with custom settings splat-transform -E settings.json bunny.ply bunny-viewer.html GENERATORS (beta)