From 3c4b99ed840ecc78a2c9253a70b7d2cb67a298c0 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Sat, 20 Dec 2025 16:04:16 -0500 Subject: [PATCH] 3dgs & ply support (#7602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary integrated sparkjs https://sparkjs.dev/, built by [world labs ](https://www.worldlabs.ai/) to support 3dgs. - Add 3D Gaussian Splatting (3DGS) support using @sparkjsdev/spark library - Add PLY file format support with multiple rendering engines - Support new file formats: `.ply`, `.spz`, `.splat`, `.ksplat` - Add PLY Engine setting with three options: `threejs` (mesh), `fastply` (optimized ASCII point clouds), `sparkjs` (3DGS) - Add `FastPLYLoader` for 4-5x faster ASCII PLY parsing - Add `original(Advanced)` material mode for point cloud rendering with THREE.Points 3dgs generated by https://marble.worldlabs.ai/ test ply file from: 1. made by https://github.com/PozzettiAndrea/ComfyUI-DepthAnythingV3 2. threejs offically repo ## Screenshots https://github.com/user-attachments/assets/44e64d3e-b58d-4341-9a70-a9aa64801220 https://github.com/user-attachments/assets/76b0dfba-0c12-4f64-91cb-bfc5d672294d https://github.com/user-attachments/assets/2a8bfe81-1fb2-44c4-8787-dff325369c61 https://github.com/user-attachments/assets/e4beecee-d7a2-40c9-97f7-79b09c60312d ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7602-3dgs-ply-support-2cd6d73d3650814098fcea86cfaf747d) by [Unito](https://www.unito.io) --- package.json | 1 + pnpm-lock.yaml | 13 + pnpm-workspace.yaml | 1 + src/components/load3d/Load3D.vue | 4 + src/components/load3d/Load3DControls.vue | 11 + src/components/load3d/Load3dViewerContent.vue | 6 +- .../load3d/controls/ModelControls.vue | 12 +- .../controls/viewer/ViewerModelControls.vue | 25 +- src/composables/useLoad3d.ts | 6 + src/composables/useLoad3dViewer.ts | 9 + src/extensions/core/load3d.ts | 20 +- src/extensions/core/load3d/Load3d.ts | 8 + src/extensions/core/load3d/LoaderManager.ts | 151 +++++- .../core/load3d/SceneModelManager.ts | 171 +++++++ src/extensions/core/load3d/interfaces.ts | 13 +- .../core/load3d/loader/FastPLYLoader.ts | 33 ++ src/extensions/core/saveMesh.ts | 2 + src/locales/en/main.json | 1 + src/schemas/apiSchema.ts | 1 + src/scripts/metadata/ply.ts | 153 ++++++ src/services/load3dService.ts | 37 +- tests-ui/tests/composables/useLoad3d.test.ts | 2 + tests-ui/tests/scripts/metadata/ply.test.ts | 448 ++++++++++++++++++ vite.config.mts | 2 +- vitest.setup.ts | 7 + 25 files changed, 1110 insertions(+), 27 deletions(-) create mode 100644 src/extensions/core/load3d/loader/FastPLYLoader.ts create mode 100644 src/scripts/metadata/ply.ts create mode 100644 tests-ui/tests/scripts/metadata/ply.test.ts diff --git a/package.json b/package.json index 8f87090ec..22c2d81fc 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "@primevue/icons": "catalog:", "@primevue/themes": "catalog:", "@sentry/vue": "catalog:", + "@sparkjsdev/spark": "catalog:", "@tiptap/core": "^2.10.4", "@tiptap/extension-link": "^2.10.4", "@tiptap/extension-table": "^2.10.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fec5ae471..2188bf0e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ catalogs: '@sentry/vue': specifier: ^8.48.0 version: 8.48.0 + '@sparkjsdev/spark': + specifier: ^0.1.10 + version: 0.1.10 '@storybook/addon-docs': specifier: ^10.1.9 version: 10.1.9 @@ -374,6 +377,9 @@ importers: '@sentry/vue': specifier: 'catalog:' version: 8.48.0(pinia@2.2.2(typescript@5.9.3)(vue@3.5.13(typescript@5.9.3)))(vue@3.5.13(typescript@5.9.3)) + '@sparkjsdev/spark': + specifier: 'catalog:' + version: 0.1.10 '@tiptap/core': specifier: ^2.10.4 version: 2.10.4(@tiptap/pm@2.10.4) @@ -3112,6 +3118,9 @@ packages: '@sinclair/typebox@0.34.40': resolution: {integrity: sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==} + '@sparkjsdev/spark@0.1.10': + resolution: {integrity: sha512-CiijdZQuj7KPDUqIZPiEqyUkJCYo1JqR05vq/V+ElxMwqR7L70ZuZDyIKcasjZHSiPB8pGRMH8HZGqUKO9aRPQ==} + '@storybook/addon-docs@10.1.9': resolution: {integrity: sha512-SvwEZ32lyk5p3PRmE3pmfAhs4HMiVo5zxjTBVmK9kgz9zGgWCTlikb56tJ998hVe52CFyCvt3I9rkHeYMCKPww==} peerDependencies: @@ -10857,6 +10866,10 @@ snapshots: '@sinclair/typebox@0.34.40': {} + '@sparkjsdev/spark@0.1.10': + dependencies: + fflate: 0.8.2 + '@storybook/addon-docs@10.1.9(@types/react@19.1.9)(esbuild@0.27.1)(rollup@4.53.5)(storybook@10.1.9(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.1.9)(react@19.2.3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fb28dcf63..589020fe0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,6 +27,7 @@ catalog: '@primevue/themes': ^4.2.5 '@sentry/vite-plugin': ^4.6.0 '@sentry/vue': ^8.48.0 + '@sparkjsdev/spark': ^0.1.10 '@storybook/addon-docs': ^10.1.9 '@storybook/vue3': ^10.1.9 '@storybook/vue3-vite': ^10.1.9 diff --git a/src/components/load3d/Load3D.vue b/src/components/load3d/Load3D.vue index e71024cb1..befc1f322 100644 --- a/src/components/load3d/Load3D.vue +++ b/src/components/load3d/Load3D.vue @@ -22,6 +22,8 @@ v-model:model-config="modelConfig" v-model:camera-config="cameraConfig" v-model:light-config="lightConfig" + :is-splat-model="isSplatModel" + :is-ply-model="isPlyModel" @update-background-image="handleBackgroundImageUpdate" @export-model="handleExportModel" /> @@ -109,6 +111,8 @@ const { // other state isRecording, isPreview, + isSplatModel, + isPlyModel, hasRecording, recordingDuration, animations, diff --git a/src/components/load3d/Load3DControls.vue b/src/components/load3d/Load3DControls.vue index 41853f03f..7a199da2a 100644 --- a/src/components/load3d/Load3DControls.vue +++ b/src/components/load3d/Load3DControls.vue @@ -47,6 +47,8 @@ v-if="showModelControls" v-model:material-mode="modelConfig!.materialMode" v-model:up-direction="modelConfig!.upDirection" + :hide-material-mode="isSplatModel" + :is-ply-model="isPlyModel" /> () + const sceneConfig = defineModel('sceneConfig') const modelConfig = defineModel('modelConfig') const cameraConfig = defineModel('cameraConfig') @@ -101,6 +108,10 @@ const categoryLabels: Record = { } const availableCategories = computed(() => { + if (isSplatModel) { + return ['scene', 'model', 'camera'] + } + return ['scene', 'model', 'camera', 'light', 'export'] }) diff --git a/src/components/load3d/Load3dViewerContent.vue b/src/components/load3d/Load3dViewerContent.vue index 1f794a946..8365c7b7f 100644 --- a/src/components/load3d/Load3dViewerContent.vue +++ b/src/components/load3d/Load3dViewerContent.vue @@ -46,6 +46,8 @@ @@ -56,13 +58,13 @@ /> -
+
-
+
diff --git a/src/components/load3d/controls/ModelControls.vue b/src/components/load3d/controls/ModelControls.vue index eae0092e9..f2836691f 100644 --- a/src/components/load3d/controls/ModelControls.vue +++ b/src/components/load3d/controls/ModelControls.vue @@ -28,7 +28,7 @@
-
+
-
+