diff --git a/examples/file-loader/src/main.ts b/examples/file-loader/src/main.ts index 444d053..b7c9881 100644 --- a/examples/file-loader/src/main.ts +++ b/examples/file-loader/src/main.ts @@ -16,9 +16,16 @@ async function selectFile(file: File) { console.log("Loading SPLAT file: " + progress); }); } else if (file.name.endsWith(".ply")) { - await SPLAT.PLYLoader.LoadFromFileAsync(file, scene, (progress: number) => { - console.log("Loading PLY file: " + progress); - }); + const format = ""; + // const format = "polycam"; // Uncomment to load a Polycam PLY file + await SPLAT.PLYLoader.LoadFromFileAsync( + file, + scene, + (progress: number) => { + console.log("Loading PLY file: " + progress); + }, + format, + ); } loading = false; } diff --git a/examples/ply-converter/src/main.ts b/examples/ply-converter/src/main.ts index 37a0e5e..49619fe 100644 --- a/examples/ply-converter/src/main.ts +++ b/examples/ply-converter/src/main.ts @@ -9,11 +9,14 @@ const scene = new SPLAT.Scene(); const camera = new SPLAT.Camera(); const controls = new SPLAT.OrbitControls(camera, canvas); +const format = ""; +// const format = "polycam"; // Uncomment to use polycam format + async function main() { // Load and convert ply from url const url = "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bonsai/point_cloud/iteration_7000/point_cloud.ply"; - await SPLAT.PLYLoader.LoadAsync(url, scene, (progress) => (progressIndicator.value = progress * 100)); + await SPLAT.PLYLoader.LoadAsync(url, scene, (progress) => (progressIndicator.value = progress * 100), format); progressDialog.close(); scene.saveToFile("bonsai.splat"); @@ -37,9 +40,14 @@ async function main() { progressIndicator.value = progress * 100; }); } else if (file.name.endsWith(".ply")) { - await SPLAT.PLYLoader.LoadFromFileAsync(file, scene, (progress: number) => { - progressIndicator.value = progress * 100; - }); + await SPLAT.PLYLoader.LoadFromFileAsync( + file, + scene, + (progress: number) => { + progressIndicator.value = progress * 100; + }, + format, + ); } scene.saveToFile(file.name.replace(".ply", ".splat")); loading = false; diff --git a/package.json b/package.json index ab4e1ae..21d5ed9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gsplat", - "version": "0.2.9", + "version": "0.2.10", "description": "JavaScript Gaussian Splatting library", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/loaders/PLYLoader.ts b/src/loaders/PLYLoader.ts index 784c13c..083daeb 100644 --- a/src/loaders/PLYLoader.ts +++ b/src/loaders/PLYLoader.ts @@ -1,9 +1,16 @@ import { Scene } from "../core/Scene"; +import { Vector3 } from "../math/Vector3"; +import { Quaternion } from "../math/Quaternion"; class PLYLoader { static SH_C0 = 0.28209479177387814; - static async LoadAsync(url: string, scene: Scene, onProgress?: (progress: number) => void): Promise { + static async LoadAsync( + url: string, + scene: Scene, + onProgress?: (progress: number) => void, + format: string = "", + ): Promise { const req = await fetch(url, { mode: "cors", credentials: "omit", @@ -34,14 +41,19 @@ class PLYLoader { throw new Error("Invalid PLY file"); } - const data = new Uint8Array(this._ParsePLYBuffer(plyData.buffer)); + const data = new Uint8Array(this._ParsePLYBuffer(plyData.buffer, format)); scene.setData(data); } - static async LoadFromFileAsync(file: File, scene: Scene, onProgress?: (progress: number) => void): Promise { + static async LoadFromFileAsync( + file: File, + scene: Scene, + onProgress?: (progress: number) => void, + format: string = "", + ): Promise { const reader = new FileReader(); reader.onload = (e) => { - const data = new Uint8Array(this._ParsePLYBuffer(e.target!.result as ArrayBuffer)); + const data = new Uint8Array(this._ParsePLYBuffer(e.target!.result as ArrayBuffer, format)); scene.setData(data); }; reader.onprogress = (e) => { @@ -55,7 +67,7 @@ class PLYLoader { }); } - private static _ParsePLYBuffer(inputBuffer: ArrayBuffer): ArrayBuffer { + private static _ParsePLYBuffer(inputBuffer: ArrayBuffer, format: string): ArrayBuffer { type PlyProperty = { name: string; type: string; @@ -96,6 +108,8 @@ class PLYLoader { const dataView = new DataView(inputBuffer, header_end_index + header_end.length); const buffer = new ArrayBuffer(Scene.RowLength * vertexCount); + const q_polycam = Quaternion.FromEuler(new Vector3(Math.PI / 2, 0, 0)); + for (let i = 0; i < vertexCount; i++) { const position = new Float32Array(buffer, i * Scene.RowLength, 3); const scale = new Float32Array(buffer, i * Scene.RowLength + 12, 3); @@ -178,11 +192,27 @@ class PLYLoader { } }); - const qlen = r0 * r0 + r1 * r1 + r2 * r2 + r3 * r3; - rot[0] = (r0 / qlen) * 128 + 128; - rot[1] = (r1 / qlen) * 128 + 128; - rot[2] = (r2 / qlen) * 128 + 128; - rot[3] = (r3 / qlen) * 128 + 128; + let q = new Quaternion(r1, r2, r3, r0); + + switch (format) { + case "polycam": { + const temp = position[1]; + position[1] = -position[2]; + position[2] = temp; + q = q_polycam.multiply(q); + break; + } + case "": + break; + default: + throw new Error(`Unsupported format: ${format}`); + } + + q = q.normalize(); + rot[0] = q.w * 128 + 128; + rot[1] = q.x * 128 + 128; + rot[2] = q.y * 128 + 128; + rot[3] = q.z * 128 + 128; } return buffer;