├── .eslintrc.cjs
├── .gitignore
├── LICENSE
├── README.md
├── demo
├── assets
│ └── images
│ │ ├── bonsai.png
│ │ ├── dynamic_scenes.png
│ │ ├── garden.png
│ │ ├── stump.png
│ │ └── truck.png
├── bonsai.html
├── dropin.html
├── dynamic_dropin.html
├── dynamic_scenes.html
├── garden.html
├── index.html
├── js
│ └── util.js
├── stump.html
├── truck.html
└── vr.html
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── AbortablePromise.js
├── ArrowHelper.js
├── Constants.js
├── DropInViewer.js
├── LogLevel.js
├── OrbitControls.js
├── RenderMode.js
├── SceneHelper.js
├── SceneRevealMode.js
├── SplatRenderMode.js
├── Util.js
├── Viewer.js
├── index.js
├── loaders
│ ├── Compression.js
│ ├── DirectLoadError.js
│ ├── InternalLoadType.js
│ ├── LoaderStatus.js
│ ├── SceneFormat.js
│ ├── SplatBuffer.js
│ ├── SplatBufferGenerator.js
│ ├── SplatPartitioner.js
│ ├── UncompressedSplatArray.js
│ ├── Utils.js
│ ├── ksplat
│ │ └── KSplatLoader.js
│ ├── ply
│ │ ├── INRIAV1PlyParser.js
│ │ ├── INRIAV2PlyParser.js
│ │ ├── PlayCanvasCompressedPlyParser.js
│ │ ├── PlyFormat.js
│ │ ├── PlyLoader.js
│ │ ├── PlyParser.js
│ │ └── PlyParserUtils.js
│ ├── splat
│ │ ├── SplatLoader.js
│ │ └── SplatParser.js
│ └── spz
│ │ └── SpzLoader.js
├── raycaster
│ ├── Hit.js
│ ├── Ray.js
│ └── Raycaster.js
├── splatmesh
│ ├── SplatGeometry.js
│ ├── SplatMaterial.js
│ ├── SplatMaterial2D.js
│ ├── SplatMaterial3D.js
│ ├── SplatMesh.js
│ └── SplatScene.js
├── splattree
│ └── SplatTree.js
├── three-shim
│ ├── WebGLCapabilities.js
│ └── WebGLExtensions.js
├── ui
│ ├── InfoPanel.js
│ ├── LoadingProgressBar.js
│ ├── LoadingSpinner.js
│ └── Util.js
├── webxr
│ ├── ARButton.js
│ ├── VRButton.js
│ └── WebXRMode.js
└── worker
│ ├── SortWorker.js
│ ├── compile_wasm.sh
│ ├── compile_wasm_no_simd.sh
│ ├── compile_wasm_no_simd_non_shared.sh
│ ├── compile_wasm_non_shared.sh
│ ├── sorter.cpp
│ ├── sorter.wasm
│ ├── sorter_no_simd.cpp
│ ├── sorter_no_simd.wasm
│ ├── sorter_no_simd_non_shared.wasm
│ └── sorter_non_shared.wasm
├── stylelintrc.json
└── util
├── create-ksplat.js
├── import-base-64.js
└── server.js
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'es2021': true,
5 | },
6 | 'extends': 'google',
7 | 'overrides': [
8 | {
9 | 'env': {
10 | 'node': true,
11 | },
12 | 'files': [
13 | '.eslintrc.{js,cjs}',
14 | ],
15 | 'parserOptions': {
16 | 'sourceType': 'script',
17 | },
18 | },
19 | ],
20 | 'parserOptions': {
21 | 'ecmaVersion': 'latest',
22 | 'sourceType': 'module',
23 | },
24 | 'rules': {
25 | "indent": ["error", 4],
26 | "max-len": ["error", 140],
27 | "object-curly-spacing": ["off"],
28 | "comma-dangle": ["off"],
29 | "prefer-const": ["off"],
30 | 'require-jsdoc': ["off"],
31 | "padded-blocks": ["off"],
32 | "indent": ["off"],
33 | "arrow-parens": ["off"],
34 | "no-unused-vars": ["error"],
35 | "valid-jsdoc" : ["off"]
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | build
3 | node_modules
4 | **/*.DS_Store
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Mark Kellogg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/demo/assets/images/bonsai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkkellogg/GaussianSplats3D/2dfc83e497bd76e558fe970c54464b17b5f5c689/demo/assets/images/bonsai.png
--------------------------------------------------------------------------------
/demo/assets/images/dynamic_scenes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkkellogg/GaussianSplats3D/2dfc83e497bd76e558fe970c54464b17b5f5c689/demo/assets/images/dynamic_scenes.png
--------------------------------------------------------------------------------
/demo/assets/images/garden.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkkellogg/GaussianSplats3D/2dfc83e497bd76e558fe970c54464b17b5f5c689/demo/assets/images/garden.png
--------------------------------------------------------------------------------
/demo/assets/images/stump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkkellogg/GaussianSplats3D/2dfc83e497bd76e558fe970c54464b17b5f5c689/demo/assets/images/stump.png
--------------------------------------------------------------------------------
/demo/assets/images/truck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkkellogg/GaussianSplats3D/2dfc83e497bd76e558fe970c54464b17b5f5c689/demo/assets/images/truck.png
--------------------------------------------------------------------------------
/demo/bonsai.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splat Demo - Truck
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/dropin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splats - Drop-in example
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/demo/dynamic_dropin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splats - Drop-in example
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/demo/dynamic_scenes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splats - Dynamic scenes example
9 |
17 |
26 |
27 |
28 |
29 |
30 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/demo/garden.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splat Demo - Garden
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/demo/js/util.js:
--------------------------------------------------------------------------------
1 | function isMobile() {
2 | return navigator.userAgent.includes("Mobi");
3 | }
--------------------------------------------------------------------------------
/demo/stump.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splat Demo - Stump
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/demo/truck.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splat Demo - Truck
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/demo/vr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 3D Gaussian Splat Demo - VR Garden
9 |
10 |
18 |
27 |
28 |
29 |
30 |
31 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mkkellogg/gaussian-splats-3d",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/mkkellogg/GaussianSplats3D"
6 | },
7 | "version": "0.4.7",
8 | "description": "Three.js-based 3D Gaussian splat viewer",
9 | "module": "build/gaussian-splats-3d.module.js",
10 | "main": "build/gaussian-splats-3d.umd.cjs",
11 | "author": "Mark Kellogg",
12 | "license": "MIT",
13 | "type": "module",
14 | "scripts": {
15 | "build-demo": "mkdir -p ./build/demo && cp -r ./demo ./build/ && cp ./node_modules/three/build/three.module.js ./build/demo/lib/three.module.js",
16 | "build-library": "npx rollup -c && mkdir -p ./build/demo/lib && cp ./build/gaussian-splats-3d.module.* ./build/demo/lib/",
17 | "build": "npm run build-library && npm run build-demo",
18 | "build-demo-windows": "(if not exist \".\\build\\demo\" mkdir .\\build\\demo) && xcopy /E .\\demo .\\build\\demo && xcopy .\\node_modules\\three\\build\\three.module.js .\\build\\demo\\lib\\",
19 | "build-library-windows": "npx rollup -c && (if not exist \".\\build\\demo\\lib\" mkdir .\\build\\demo\\lib) && copy .\\build\\gaussian-splats-3d* .\\build\\demo\\lib\\",
20 | "build-windows": "npm run build-library-windows && npm run build-demo-windows",
21 | "watch": "npx npm-watch ",
22 | "demo": "node util/server.js -d ./build/demo",
23 | "fix-styling": "npx stylelint **/*.scss --fix",
24 | "fix-js": "npx eslint src --fix",
25 | "lint": "npx eslint 'src/**/*.js' || true",
26 | "prettify": "npx prettier --write 'src/**/*.js'"
27 | },
28 | "watch": {
29 | "build-library": {
30 | "patterns": [
31 | "src/**/*.js"
32 | ]
33 | },
34 | "build-demo": {
35 | "patterns": [
36 | "demo/**/*.*"
37 | ]
38 | }
39 | },
40 | "babel": {},
41 | "keywords": [
42 | "three",
43 | "threejs",
44 | "three.js",
45 | "splatting",
46 | "3D",
47 | "gaussian",
48 | "webgl",
49 | "javascript"
50 | ],
51 | "devDependencies": {
52 | "@babel/core": "7.22.0",
53 | "@babel/eslint-parser": "7.22.11",
54 | "@babel/plugin-proposal-class-properties": "7.18.6",
55 | "@babel/preset-env": "7.22.10",
56 | "babel-loader": "9.1.3",
57 | "@rollup/plugin-terser": "0.4.4",
58 | "@rollup/pluginutils": "5.0.5",
59 | "eslint": "8.47.0",
60 | "eslint-config-google": "0.14.0",
61 | "file-loader": "6.2.0",
62 | "http-server": "14.1.1",
63 | "npm-watch": "0.11.0",
64 | "prettier": "3.0.2",
65 | "prettier-eslint": "15.0.1",
66 | "rollup": "3.28.1",
67 | "url-loader": "4.1.1"
68 | },
69 | "peerDependencies": {
70 | "three": ">=0.160.0"
71 | },
72 | "files": [
73 | "build/gaussian-splats-3d.umd.cjs",
74 | "build/gaussian-splats-3d.umd.cjs.map",
75 | "build/gaussian-splats-3d.module.js",
76 | "build/gaussian-splats-3d.module.js.map"
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { base64 } from "./util/import-base-64.js";
2 | import terser from '@rollup/plugin-terser';
3 |
4 | const globals = {
5 | 'three': 'THREE'
6 | };
7 |
8 | export default [
9 | {
10 | input: './src/index.js',
11 | treeshake: false,
12 | external: [
13 | 'three'
14 | ],
15 | output: [
16 | {
17 | name: 'Gaussian Splats 3D',
18 | extend: true,
19 | format: 'umd',
20 | file: './build/gaussian-splats-3d.umd.cjs',
21 | globals: globals,
22 | sourcemap: true
23 | },
24 | {
25 | name: 'Gaussian Splats 3D',
26 | extend: true,
27 | format: 'umd',
28 | file: './build/gaussian-splats-3d.umd.min.cjs',
29 | globals: globals,
30 | sourcemap: true,
31 | plugins: [terser()]
32 | }
33 | ],
34 | plugins: [
35 | base64({ include: "**/*.wasm" })
36 | ]
37 | },
38 | {
39 | input: './src/index.js',
40 | treeshake: false,
41 | external: [
42 | 'three'
43 | ],
44 | output: [
45 | {
46 | name: 'Gaussian Splats 3D',
47 | format: 'esm',
48 | file: './build/gaussian-splats-3d.module.js',
49 | sourcemap: true
50 | },
51 | {
52 | name: 'Gaussian Splats 3D',
53 | format: 'esm',
54 | file: './build/gaussian-splats-3d.module.min.js',
55 | sourcemap: true,
56 | plugins: [terser()]
57 | }
58 | ],
59 | plugins: [
60 | base64({
61 | include: "**/*.wasm",
62 | sourceMap: false
63 | })
64 | ]
65 | }
66 | ];
--------------------------------------------------------------------------------
/src/AbortablePromise.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AbortablePromise: A quick & dirty wrapper for JavaScript's Promise class that allows the underlying
3 | * asynchronous operation to be cancelled. It is only meant for simple situations where no complex promise
4 | * chaining or merging occurs. It needs a significant amount of work to truly replicate the full
5 | * functionality of JavaScript's Promise class. Look at Util.fetchWithProgress() for example usage.
6 | *
7 | * This class was primarily added to allow splat scene downloads to be cancelled. It has not been tested
8 | * very thoroughly and the implementation is kinda janky. If you can at all help it, please avoid using it :)
9 | */
10 | export class AbortablePromise {
11 |
12 | static idGen = 0;
13 |
14 | constructor(promiseFunc, abortHandler) {
15 |
16 | let resolver;
17 | let rejecter;
18 | this.promise = new Promise((resolve, reject) => {
19 | resolver = resolve;
20 | rejecter = reject;
21 | });
22 |
23 | const promiseResolve = resolver.bind(this);
24 | const promiseReject = rejecter.bind(this);
25 |
26 | const resolve = (...args) => {
27 | promiseResolve(...args);
28 | };
29 |
30 | const reject = (error) => {
31 | promiseReject(error);
32 | };
33 |
34 | promiseFunc(resolve.bind(this), reject.bind(this));
35 | this.abortHandler = abortHandler;
36 | this.id = AbortablePromise.idGen++;
37 | }
38 |
39 | then(onResolve) {
40 | return new AbortablePromise((resolve, reject) => {
41 | this.promise = this.promise
42 | .then((...args) => {
43 | const onResolveResult = onResolve(...args);
44 | if (onResolveResult instanceof Promise || onResolveResult instanceof AbortablePromise) {
45 | onResolveResult.then((...args2) => {
46 | resolve(...args2);
47 | });
48 | } else {
49 | resolve(onResolveResult);
50 | }
51 | })
52 | .catch((error) => {
53 | reject(error);
54 | });
55 | }, this.abortHandler);
56 | }
57 |
58 | catch(onFail) {
59 | return new AbortablePromise((resolve) => {
60 | this.promise = this.promise.then((...args) => {
61 | resolve(...args);
62 | })
63 | .catch(onFail);
64 | }, this.abortHandler);
65 | }
66 |
67 | abort(reason) {
68 | if (this.abortHandler) this.abortHandler(reason);
69 | }
70 |
71 | }
72 |
73 | export class AbortedPromiseError extends Error {
74 |
75 | constructor(msg) {
76 | super(msg);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/ArrowHelper.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | const _axis = new THREE.Vector3();
4 |
5 | export class ArrowHelper extends THREE.Object3D {
6 |
7 | constructor(dir = new THREE.Vector3(0, 0, 1), origin = new THREE.Vector3(0, 0, 0), length = 1,
8 | radius = 0.1, color = 0xffff00, headLength = length * 0.2, headRadius = headLength * 0.2) {
9 | super();
10 |
11 | this.type = 'ArrowHelper';
12 |
13 | const lineGeometry = new THREE.CylinderGeometry(radius, radius, length, 32);
14 | lineGeometry.translate(0, length / 2.0, 0);
15 | const coneGeometry = new THREE.CylinderGeometry( 0, headRadius, headLength, 32);
16 | coneGeometry.translate(0, length, 0);
17 |
18 | this.position.copy( origin );
19 |
20 | this.line = new THREE.Mesh(lineGeometry, new THREE.MeshBasicMaterial({color: color, toneMapped: false}));
21 | this.line.matrixAutoUpdate = false;
22 | this.add(this.line);
23 |
24 | this.cone = new THREE.Mesh(coneGeometry, new THREE.MeshBasicMaterial({color: color, toneMapped: false}));
25 | this.cone.matrixAutoUpdate = false;
26 | this.add(this.cone);
27 |
28 | this.setDirection(dir);
29 | }
30 |
31 | setDirection( dir ) {
32 | if (dir.y > 0.99999) {
33 | this.quaternion.set(0, 0, 0, 1);
34 | } else if (dir.y < - 0.99999) {
35 | this.quaternion.set(1, 0, 0, 0);
36 | } else {
37 | _axis.set(dir.z, 0, -dir.x).normalize();
38 | const radians = Math.acos(dir.y);
39 | this.quaternion.setFromAxisAngle(_axis, radians);
40 | }
41 | }
42 |
43 | setColor( color ) {
44 | this.line.material.color.set(color);
45 | this.cone.material.color.set(color);
46 | }
47 |
48 | copy(source) {
49 | super.copy(source, false);
50 | this.line.copy(source.line);
51 | this.cone.copy(source.cone);
52 | return this;
53 | }
54 |
55 | dispose() {
56 | this.line.geometry.dispose();
57 | this.line.material.dispose();
58 | this.cone.geometry.dispose();
59 | this.cone.material.dispose();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Constants.js:
--------------------------------------------------------------------------------
1 | export class Constants {
2 |
3 | static DefaultSplatSortDistanceMapPrecision = 16;
4 | static MemoryPageSize = 65536;
5 | static BytesPerFloat = 4;
6 | static BytesPerInt = 4;
7 | static MaxScenes = 32;
8 | static ProgressiveLoadSectionSize = 262144;
9 | static ProgressiveLoadSectionDelayDuration = 15;
10 | static SphericalHarmonics8BitCompressionRange = 3;
11 | }
12 |
--------------------------------------------------------------------------------
/src/DropInViewer.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { Viewer } from './Viewer.js';
3 |
4 | /**
5 | * DropInViewer: Wrapper for a Viewer instance that enables it to be added to a Three.js scene like
6 | * any other Three.js scene object (Mesh, Object3D, etc.)
7 | */
8 | export class DropInViewer extends THREE.Group {
9 |
10 | constructor(options = {}) {
11 | super();
12 |
13 | options.selfDrivenMode = false;
14 | options.useBuiltInControls = false;
15 | options.rootElement = null;
16 | options.dropInMode = true;
17 | options.camera = undefined;
18 | options.renderer = undefined;
19 |
20 | this.viewer = new Viewer(options);
21 | this.splatMesh = null;
22 | this.updateSplatMesh();
23 |
24 | this.callbackMesh = DropInViewer.createCallbackMesh();
25 | this.add(this.callbackMesh);
26 | this.callbackMesh.onBeforeRender = DropInViewer.onBeforeRender.bind(this, this.viewer);
27 |
28 | this.viewer.onSplatMeshChanged(() => {
29 | this.updateSplatMesh();
30 | });
31 |
32 | }
33 |
34 | updateSplatMesh() {
35 | if (this.splatMesh !== this.viewer.splatMesh) {
36 | if (this.splatMesh) {
37 | this.remove(this.splatMesh);
38 | }
39 | this.splatMesh = this.viewer.splatMesh;
40 | this.add(this.viewer.splatMesh);
41 | }
42 | }
43 |
44 | /**
45 | * Add a single splat scene to the viewer.
46 | * @param {string} path Path to splat scene to be loaded
47 | * @param {object} options {
48 | *
49 | * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
50 | * value (valid range: 0 - 255), defaults to 1
51 | *
52 | * showLoadingUI: Display a loading spinner while the scene is loading, defaults to true
53 | *
54 | * position (Array): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
55 | *
56 | * rotation (Array): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
57 | *
58 | * scale (Array): Scene's scale, defaults to [1, 1, 1]
59 | *
60 | * onProgress: Function to be called as file data are received
61 | *
62 | * }
63 | * @return {AbortablePromise}
64 | */
65 | addSplatScene(path, options = {}) {
66 | if (options.showLoadingUI !== false) options.showLoadingUI = true;
67 | return this.viewer.addSplatScene(path, options);
68 | }
69 |
70 | /**
71 | * Add multiple splat scenes to the viewer.
72 | * @param {Array