gaussian-splat-lite 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -2
- package/CONTRIBUTING.md +14 -2
- package/NOTICE +11 -3
- package/README.md +5 -7
- package/THIRD_PARTY_LICENSES.md +64 -6
- package/bin/build-lod.js +241 -0
- package/bin/decoder/decoder.js +340 -0
- package/bin/decoder/decoder_bg.wasm +0 -0
- package/bin/rad/grid.js +620 -0
- package/bin/rad/heap.js +270 -0
- package/bin/rad/input.js +87 -0
- package/bin/rad/lod.js +657 -0
- package/bin/rad/math.js +166 -0
- package/bin/rad/parallel.js +578 -0
- package/bin/rad/progress.js +50 -0
- package/bin/rad/rad.js +612 -0
- package/bin/rad/splats.js +432 -0
- package/bin/rad/storage.js +776 -0
- package/dist/gaussian-splat-lite.cjs +392 -794
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +246 -308
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +394 -796
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +2518 -2802
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/addons/NeuralDenoiseNode.d.cts +1 -2
- package/dist/types/addons/NeuralDenoiseNode.d.ts +1 -2
- package/dist/types/addons/NeuralDenoiseXR.d.cts +31 -0
- package/dist/types/addons/NeuralDenoiseXR.d.ts +31 -0
- package/dist/types/addons/neuralDenoiseState.d.cts +22 -0
- package/dist/types/addons/neuralDenoiseState.d.ts +22 -0
- package/dist/types/addons/neuralDenoiseWeights.d.cts +41 -2
- package/dist/types/addons/neuralDenoiseWeights.d.ts +41 -2
- package/dist/types/data/RadPagedSplats.d.cts +1 -1
- package/dist/types/data/RadPagedSplats.d.ts +1 -1
- package/dist/types/index.d.cts +1 -2
- package/dist/types/index.d.ts +1 -2
- package/dist/types/loaders/rad/radFormat.d.cts +2 -2
- package/dist/types/loaders/rad/radFormat.d.ts +2 -2
- package/docs/BuildRad.md +91 -0
- package/docs/GaussianSplatRenderer.md +2 -2
- package/docs/NeuralDenoiseNode.md +32 -13
- package/docs/RadStreamScheduler.md +3 -1
- package/docs/StochasticRendering.md +20 -42
- package/package.json +9 -3
- package/dist/types/addons/TAANode.d.cts +0 -49
- package/dist/types/addons/TAANode.d.ts +0 -49
- package/dist/types/addons/TAAPass.d.cts +0 -57
- package/dist/types/addons/TAAPass.d.ts +0 -57
- package/dist/types/addons/taaShared.d.cts +0 -74
- package/dist/types/addons/taaShared.d.ts +0 -74
- package/docs/TAAPass.md +0 -184
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.1] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added experimental `NeuralDenoiseXR` for per-eye neural denoising in WebXR with `WebGLRenderer` and `WebGPURenderer`, including its WebGL2 fallback. See [Neural denoising](docs/NeuralDenoiseNode.md#webxr).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Neural denoising reduces noise and flicker on moving objects, including moving Splats. Both modes also use less GPU time while the camera moves. During camera movement, `"quality"` adds smoothing with slightly softer detail, while `"performance"` preserves more fine detail at slow speeds.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Removed `TAANode`, `TAAPass`, and the viewer's TAA option. Use `NeuralDenoiseNode` with `WebGPURenderer`, `NeuralDenoisePass` with `WebGLRenderer`, or `NeuralDenoiseXR` for WebXR. See [Neural denoising](docs/NeuralDenoiseNode.md).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- On `WebGPURenderer`'s WebGL2 backend, the neural denoiser's `depthTexture` stopped updating after a resize.
|
|
27
|
+
|
|
28
|
+
## [1.4.0] - 2026-10-21
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Added `gsl-build-lod` to convert PLY and SPZ models into streamable RAD files with LOD, without compilation. Supports batch conversion, split files, up to SH3, and configurable memory and temporary storage. See [Building RAD files](docs/BuildRad.md).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- RAD files and gzip-compressed SPZ files (v1–v3) decode about 10% faster.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Fixed incorrect orientation and unstable widths of thin Splats on WebGL, WebGPU, and the WebGL2 fallback.
|
|
41
|
+
|
|
10
42
|
## [1.3.2] - 2026-10-18
|
|
11
43
|
|
|
12
44
|
### Added
|
|
@@ -105,7 +137,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
105
137
|
### Changed
|
|
106
138
|
|
|
107
139
|
- The default `minAlpha` is now `1 / 255` instead of `0.5 / 255`. Set `minAlpha: 0.5 / 255` to keep the previous look.
|
|
108
|
-
- `TAAPass` is now used through `EffectComposer` with a final Three.js `OutputPass`; calling `taa.render(renderer)` on its own is no longer supported. Bloom and other effects go between the two passes without extra settings. Splat colors still match direct canvas rendering. `OutputPass` applies the renderer's tone mapping to the whole image, Splats included.
|
|
140
|
+
- `TAAPass` is now used through `EffectComposer` with a final Three.js `OutputPass`; calling `taa.render(renderer)` on its own is no longer supported. Bloom and other effects go between the two passes without extra settings. Splat colors still match direct canvas rendering. `OutputPass` applies the renderer's tone mapping to the whole image, Splats included.
|
|
109
141
|
|
|
110
142
|
### Removed
|
|
111
143
|
|
|
@@ -621,7 +653,9 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
621
653
|
|
|
622
654
|
- Initial public release of the Three.js Gaussian Splatting renderer.
|
|
623
655
|
|
|
624
|
-
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.
|
|
656
|
+
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.1...HEAD
|
|
657
|
+
[1.4.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.0...v1.4.1
|
|
658
|
+
[1.4.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.2...v1.4.0
|
|
625
659
|
[1.3.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.1...v1.3.2
|
|
626
660
|
[1.3.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.0...v1.3.1
|
|
627
661
|
[1.3.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.2.2...v1.3.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Development setup
|
|
4
4
|
|
|
5
|
-
Requires Node.js 20.19+ or 22.12
|
|
5
|
+
Requires Node.js 20.19+ or 22.12+ and Rust via `rustup`. `npm run build:wasm` installs the `wasm32-unknown-unknown` target if needed.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm ci
|
|
@@ -14,6 +14,18 @@ Check WebGPU, WebGL2, and WebGPU · WebGL2 in the viewer. For rendering changes,
|
|
|
14
14
|
|
|
15
15
|
Implementations and build scripts use JavaScript. Keep TypeScript declarations in adjacent `.d.ts` files and update them whenever the corresponding API changes. `src/index.js` and `src/index.d.ts` define the package's runtime and type exports.
|
|
16
16
|
|
|
17
|
+
### RAD command-line tool
|
|
18
|
+
|
|
19
|
+
See [Building RAD files](docs/BuildRad.md) for installation, conversion commands, and options.
|
|
20
|
+
|
|
21
|
+
Before running the CLI from a checkout, and after changing the decoder, its dependencies, or its build settings, rebuild the decoder:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run build:wasm:lod
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`npm run build:wasm` also builds the CLI decoder. The generated `bin/decoder/` files are ignored by Git and included in the npm package, so npm users can run the command without compiling anything. The release workflow builds them before packaging.
|
|
28
|
+
|
|
17
29
|
## Validation
|
|
18
30
|
|
|
19
31
|
Before opening a pull request:
|
|
@@ -35,4 +47,4 @@ Review new or changed dependency install scripts and update `allowScripts` with
|
|
|
35
47
|
- Add or update tests for behavior that can be exercised without WebGL.
|
|
36
48
|
- Include a small reproduction and before/after images for rendering changes where possible.
|
|
37
49
|
- Add user-visible changes to the `Unreleased` section of `CHANGELOG.md`; leave published version sections unchanged.
|
|
38
|
-
- Do not commit generated `dist/`, `site-dist/`, Rust `target/`, or wasm-pack `pkg/` directories.
|
|
50
|
+
- Do not commit generated `dist/`, `site-dist/`, `bin/decoder/`, Rust `target/`, or wasm-pack `pkg/` directories.
|
package/NOTICE
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
Gaussian Splat Lite
|
|
2
2
|
Copyright 2026 William Liu <lyz15972107087@gmail.com>
|
|
3
3
|
|
|
4
|
-
This product includes software derived from SparkJS
|
|
4
|
+
This product includes software derived from SparkJS,
|
|
5
5
|
Copyright © 2025 WORLD LABS TECHNOLOGIES, INC., licensed under the MIT
|
|
6
6
|
License. See THIRD_PARTY_LICENSES.md for the complete license notice.
|
|
7
7
|
|
|
8
|
-
This product includes software adapted from SuperSplat
|
|
8
|
+
This product includes software adapted from SuperSplat,
|
|
9
9
|
Copyright (c) 2011-2026 PlayCanvas Ltd., licensed under the MIT License.
|
|
10
10
|
See THIRD_PARTY_LICENSES.md for the complete license notice.
|
|
11
11
|
|
|
12
|
-
This product includes ruzstd
|
|
12
|
+
This product includes ruzstd,
|
|
13
13
|
Copyright © 2019 Moritz Borcherding, licensed under the MIT License.
|
|
14
14
|
See THIRD_PARTY_LICENSES.md for the complete license notice.
|
|
15
|
+
|
|
16
|
+
This product includes flate2,
|
|
17
|
+
Copyright (c) 2014-2026 Alex Crichton, licensed under the MIT License.
|
|
18
|
+
See THIRD_PARTY_LICENSES.md for the complete license notice.
|
|
19
|
+
|
|
20
|
+
This product includes zlib-rs,
|
|
21
|
+
(C) 2024 Trifecta Tech Foundation, licensed under the zlib License.
|
|
22
|
+
See THIRD_PARTY_LICENSES.md for the complete license notice.
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ High performance 3D Gaussian Splatting (3DGS) renderer for **Three.js** with **W
|
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| **WebGPU / WebGL2** | Use the same Three.js scene API with either renderer |
|
|
32
32
|
| **Large-scene streaming** | Load RAD and SOG detail as the camera moves, with smooth transitions |
|
|
33
|
-
| **Stochastic rendering** | Optional transparency mode, with
|
|
33
|
+
| **Stochastic rendering** | Optional transparency mode, with an experimental neural denoiser to smooth it |
|
|
34
34
|
| **Offscreen capture** | Render Splats to images, cube maps, and environment maps for reflections |
|
|
35
35
|
| **SDF edits** | Recolor or hide parts of a model without moving Splats |
|
|
36
36
|
| **Data and precision** | Load URLs, files, or bytes; place local models in large GIS/ECEF scenes |
|
|
@@ -108,7 +108,7 @@ Sorted alpha blending is the default. To enable stochastic rendering:
|
|
|
108
108
|
splatRenderer.stochastic = true;
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
This mode produces visible noise. Smooth it with
|
|
111
|
+
This mode produces visible noise. Smooth it with the [experimental neural denoiser](docs/NeuralDenoiseNode.md), which supports WebGPU and WebGL2, including WebXR. See [Stochastic rendering](docs/StochasticRendering.md) for setup and requirements.
|
|
112
112
|
|
|
113
113
|
## Streaming large scenes
|
|
114
114
|
|
|
@@ -160,8 +160,7 @@ const streaming = new SogStreamScheduler({
|
|
|
160
160
|
|
|
161
161
|
- [GaussianSplatRenderer](docs/GaussianSplatRenderer.md) — Rendering options, sorting, and on-demand rendering.
|
|
162
162
|
- [Stochastic rendering](docs/StochasticRendering.md) — The optional transparency mode and how to smooth its noise.
|
|
163
|
-
- [
|
|
164
|
-
- [NeuralDenoiseNode and NeuralDenoisePass (Experimental)](docs/NeuralDenoiseNode.md) — Neural denoising on WebGPU and WebGL2, for scenes with moving objects.
|
|
163
|
+
- [Neural denoising (Experimental)](docs/NeuralDenoiseNode.md) — Neural denoising on WebGPU and WebGL2, including per-eye WebXR rendering with `NeuralDenoiseXR`.
|
|
165
164
|
- [SplatCapture](docs/SplatCapture.md) — Offscreen images, pixel readback, cube maps, and environment maps.
|
|
166
165
|
|
|
167
166
|
**Models**
|
|
@@ -175,6 +174,7 @@ const streaming = new SogStreamScheduler({
|
|
|
175
174
|
|
|
176
175
|
**Large scenes**
|
|
177
176
|
|
|
177
|
+
- [Building RAD files](docs/BuildRad.md) — Convert models to streamable RAD with quality LOD.
|
|
178
178
|
- [RadStreamScheduler](docs/RadStreamScheduler.md) — Large RAD scenes with adaptive detail.
|
|
179
179
|
- [SogStreamScheduler](docs/SogStreamScheduler.md) — Large SOG scenes with adaptive detail.
|
|
180
180
|
|
|
@@ -194,9 +194,7 @@ See [Contributing](CONTRIBUTING.md#validation) for validation commands. `npm run
|
|
|
194
194
|
|
|
195
195
|
## Acknowledgements
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Example model adapted from [shehabmekky](https://superspl.at/scene/c1e6297e) ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)).
|
|
197
|
+
Gaussian Splat Lite uses [Spark](https://github.com/sparkjsdev/spark) and [SuperSplat](https://github.com/playcanvas/supersplat) as reference implementations.
|
|
200
198
|
|
|
201
199
|
## License
|
|
202
200
|
|
package/THIRD_PARTY_LICENSES.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Third-party licenses
|
|
2
2
|
|
|
3
|
-
## SparkJS
|
|
3
|
+
## SparkJS
|
|
4
4
|
|
|
5
|
-
Gaussian Splat Lite includes
|
|
5
|
+
Gaussian Splat Lite includes code derived from SparkJS.
|
|
6
6
|
|
|
7
7
|
The MIT License
|
|
8
8
|
|
|
@@ -26,9 +26,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
26
26
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
27
|
THE SOFTWARE.
|
|
28
28
|
|
|
29
|
-
## SuperSplat
|
|
29
|
+
## SuperSplat
|
|
30
30
|
|
|
31
|
-
Gaussian Splat Lite includes software adapted from SuperSplat
|
|
31
|
+
Gaussian Splat Lite includes software adapted from SuperSplat.
|
|
32
32
|
|
|
33
33
|
MIT License
|
|
34
34
|
|
|
@@ -52,9 +52,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
52
52
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
53
53
|
THE SOFTWARE.
|
|
54
54
|
|
|
55
|
-
## ruzstd
|
|
55
|
+
## ruzstd
|
|
56
56
|
|
|
57
|
-
Gaussian Splat Lite includes ruzstd
|
|
57
|
+
Gaussian Splat Lite includes ruzstd in its WebAssembly build.
|
|
58
58
|
|
|
59
59
|
MIT License
|
|
60
60
|
|
|
@@ -110,3 +110,61 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
110
110
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
111
111
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
112
112
|
DEALINGS IN THE SOFTWARE.
|
|
113
|
+
|
|
114
|
+
## flate2
|
|
115
|
+
|
|
116
|
+
Gaussian Splat Lite includes flate2 in its WebAssembly build.
|
|
117
|
+
|
|
118
|
+
MIT License
|
|
119
|
+
|
|
120
|
+
Copyright (c) 2014-2026 Alex Crichton
|
|
121
|
+
|
|
122
|
+
Permission is hereby granted, free of charge, to any
|
|
123
|
+
person obtaining a copy of this software and associated
|
|
124
|
+
documentation files (the "Software"), to deal in the
|
|
125
|
+
Software without restriction, including without
|
|
126
|
+
limitation the rights to use, copy, modify, merge,
|
|
127
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
128
|
+
the Software, and to permit persons to whom the Software
|
|
129
|
+
is furnished to do so, subject to the following
|
|
130
|
+
conditions:
|
|
131
|
+
|
|
132
|
+
The above copyright notice and this permission notice
|
|
133
|
+
shall be included in all copies or substantial portions
|
|
134
|
+
of the Software.
|
|
135
|
+
|
|
136
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
137
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
138
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
139
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
140
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
141
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
142
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
143
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
144
|
+
DEALINGS IN THE SOFTWARE.
|
|
145
|
+
|
|
146
|
+
## zlib-rs
|
|
147
|
+
|
|
148
|
+
Gaussian Splat Lite includes zlib-rs in its WebAssembly build.
|
|
149
|
+
|
|
150
|
+
zlib License
|
|
151
|
+
|
|
152
|
+
(C) 2024 Trifecta Tech Foundation
|
|
153
|
+
|
|
154
|
+
This software is provided 'as-is', without any express or implied
|
|
155
|
+
warranty. In no event will the authors be held liable for any damages
|
|
156
|
+
arising from the use of this software.
|
|
157
|
+
|
|
158
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
159
|
+
including commercial applications, and to alter it and redistribute it
|
|
160
|
+
freely, subject to the following restrictions:
|
|
161
|
+
|
|
162
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
163
|
+
claim that you wrote the original software. If you use this software
|
|
164
|
+
in a product, an acknowledgment in the product documentation would be
|
|
165
|
+
appreciated but is not required.
|
|
166
|
+
|
|
167
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
168
|
+
misrepresented as being the original software.
|
|
169
|
+
|
|
170
|
+
3. This notice may not be removed or altered from any source distribution.
|
package/bin/build-lod.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
Worker,
|
|
6
|
+
isMainThread,
|
|
7
|
+
parentPort,
|
|
8
|
+
workerData,
|
|
9
|
+
} from "node:worker_threads";
|
|
10
|
+
import { readInput } from "./rad/input.js";
|
|
11
|
+
import { buildLod } from "./rad/lod.js";
|
|
12
|
+
import { Pool } from "./rad/parallel.js";
|
|
13
|
+
import { checkOutput, writeRad } from "./rad/rad.js";
|
|
14
|
+
import { GIB, Workspace } from "./rad/storage.js";
|
|
15
|
+
|
|
16
|
+
const help = `Convert PLY or SPZ files to RAD with levels of detail (LOD).
|
|
17
|
+
|
|
18
|
+
Usage: gsl-build-lod <input> [more inputs...] [options]
|
|
19
|
+
npm run build-lod -- <input> [more inputs...] [options]
|
|
20
|
+
|
|
21
|
+
Inputs: PLY, SPZ.
|
|
22
|
+
Output: <input-stem>-lod.rad. Existing files are not overwritten.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--lod-base <base> LOD spacing (default: 1.75, range: 1.1..16)
|
|
26
|
+
--max-sh <0..3> Maximum SH degree (default: 3)
|
|
27
|
+
--rad-chunked Write a .rad header and separate .radc files
|
|
28
|
+
--memory-budget <GiB> Target memory in GiB (default: 4, minimum: 1)
|
|
29
|
+
--temp-dir <path> Temporary file location (default: system temp)
|
|
30
|
+
-o, --output <path> Output .rad path (one input only)
|
|
31
|
+
-h, --help Show this help
|
|
32
|
+
|
|
33
|
+
SH4 inputs are accepted; coefficients above --max-sh are discarded.
|
|
34
|
+
The memory target covers the whole conversion, worker threads included.
|
|
35
|
+
No compilation is required.`;
|
|
36
|
+
|
|
37
|
+
function parse(args) {
|
|
38
|
+
const options = {
|
|
39
|
+
inputs: [],
|
|
40
|
+
lodBase: 1.75,
|
|
41
|
+
maxSh: 3,
|
|
42
|
+
budget: 4 * GIB,
|
|
43
|
+
chunked: false,
|
|
44
|
+
};
|
|
45
|
+
for (let i = 0; i < args.length; i++) {
|
|
46
|
+
const arg = args[i];
|
|
47
|
+
if (arg === "--help" || arg === "-h") return;
|
|
48
|
+
if (arg === "--") {
|
|
49
|
+
options.inputs.push(...args.slice(i + 1));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
if (arg === "--rad-chunked") {
|
|
53
|
+
options.chunked = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!arg.startsWith("-")) {
|
|
57
|
+
options.inputs.push(arg);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const equal = arg.indexOf("=");
|
|
61
|
+
const name = equal < 0 ? arg : arg.slice(0, equal);
|
|
62
|
+
const key = {
|
|
63
|
+
"--lod-base": "lodBase",
|
|
64
|
+
"--max-sh": "maxSh",
|
|
65
|
+
"--memory-budget": "budget",
|
|
66
|
+
"--temp-dir": "tempDir",
|
|
67
|
+
"-o": "output",
|
|
68
|
+
"--output": "output",
|
|
69
|
+
}[name];
|
|
70
|
+
if (!key) throw new Error(`Unknown option: ${arg}`);
|
|
71
|
+
const value = equal < 0 ? args[++i] : arg.slice(equal + 1);
|
|
72
|
+
if (!value) throw new Error(`${name} requires a value`);
|
|
73
|
+
options[key] = ["lodBase", "maxSh", "budget"].includes(key)
|
|
74
|
+
? Number(value) * (key === "budget" ? GIB : 1)
|
|
75
|
+
: value;
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
!Number.isFinite(options.lodBase) ||
|
|
79
|
+
options.lodBase < 1.1 ||
|
|
80
|
+
options.lodBase > 16
|
|
81
|
+
)
|
|
82
|
+
throw new Error("--lod-base must be between 1.1 and 16");
|
|
83
|
+
if (
|
|
84
|
+
!Number.isInteger(options.maxSh) ||
|
|
85
|
+
options.maxSh < 0 ||
|
|
86
|
+
options.maxSh > 3
|
|
87
|
+
)
|
|
88
|
+
throw new Error("--max-sh must be an integer between 0 and 3");
|
|
89
|
+
if (
|
|
90
|
+
!Number.isFinite(options.budget) ||
|
|
91
|
+
options.budget < GIB ||
|
|
92
|
+
options.budget > Number.MAX_SAFE_INTEGER
|
|
93
|
+
)
|
|
94
|
+
throw new Error(
|
|
95
|
+
"--memory-budget must be a finite GiB number of at least 1 within the platform limit",
|
|
96
|
+
);
|
|
97
|
+
if (!options.inputs.length) throw new Error(help);
|
|
98
|
+
if (options.output && options.inputs.length !== 1)
|
|
99
|
+
throw new Error("--output requires exactly one input");
|
|
100
|
+
return options;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function convert(input, options, cancelled) {
|
|
104
|
+
const output =
|
|
105
|
+
options.output ??
|
|
106
|
+
path.join(
|
|
107
|
+
path.dirname(input),
|
|
108
|
+
`${path.basename(input, path.extname(input))}-lod.rad`,
|
|
109
|
+
);
|
|
110
|
+
checkOutput(output, options.chunked);
|
|
111
|
+
const workspace = new Workspace(options.budget, options.tempDir, cancelled);
|
|
112
|
+
const start = performance.now();
|
|
113
|
+
console.log(
|
|
114
|
+
`${input} -> ${output}\nMemory target: ${(options.budget / GIB).toFixed(3)} GiB`,
|
|
115
|
+
);
|
|
116
|
+
// Worker threads for the LOD, closed before writing starts its own.
|
|
117
|
+
let pool;
|
|
118
|
+
try {
|
|
119
|
+
workspace.check();
|
|
120
|
+
const splats = await readInput(input, options.maxSh, workspace);
|
|
121
|
+
const count = splats.length;
|
|
122
|
+
pool = new Pool(workspace, count);
|
|
123
|
+
splats.retain(pool);
|
|
124
|
+
console.log(
|
|
125
|
+
`Read ${count} splats, SH${splats.inputSh}, in ${((performance.now() - start) / 1000).toFixed(2)}s`,
|
|
126
|
+
);
|
|
127
|
+
if (splats.inputSh > splats.maxSh)
|
|
128
|
+
console.log(
|
|
129
|
+
`Keeping SH0–SH${splats.maxSh}; higher coefficients are discarded for RAD output.`,
|
|
130
|
+
);
|
|
131
|
+
if (splats.invalid)
|
|
132
|
+
console.log(
|
|
133
|
+
`Removed ${splats.invalid} splats with NaN or infinite values or a zero rotation`,
|
|
134
|
+
);
|
|
135
|
+
const empty = count - splats.invalid - splats.length;
|
|
136
|
+
if (empty) console.log(`Removed ${empty} empty splats`);
|
|
137
|
+
if (!splats.length) throw new Error("Input contains no usable splats");
|
|
138
|
+
const leaves = splats.length;
|
|
139
|
+
const lodStart = performance.now();
|
|
140
|
+
buildLod(splats, options.lodBase, pool);
|
|
141
|
+
await pool.close();
|
|
142
|
+
console.log(
|
|
143
|
+
`Built ${splats.length} LOD splats from ${leaves} leaves in ${((performance.now() - lodStart) / 1000).toFixed(2)}s`,
|
|
144
|
+
);
|
|
145
|
+
const bytes = await writeRad(
|
|
146
|
+
splats,
|
|
147
|
+
output,
|
|
148
|
+
options.chunked,
|
|
149
|
+
options.lodBase,
|
|
150
|
+
);
|
|
151
|
+
console.log(
|
|
152
|
+
`Wrote ${(bytes / 1048576).toFixed(2)} MiB in ${((performance.now() - start) / 1000).toFixed(2)}s total`,
|
|
153
|
+
);
|
|
154
|
+
} finally {
|
|
155
|
+
await pool?.close();
|
|
156
|
+
workspace.dispose();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function run(options, cancelled) {
|
|
161
|
+
let failed = 0;
|
|
162
|
+
for (const input of options.inputs) {
|
|
163
|
+
if (Atomics.load(cancelled, 0)) return;
|
|
164
|
+
try {
|
|
165
|
+
await convert(input, options, cancelled);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (Atomics.load(cancelled, 0)) return;
|
|
168
|
+
console.error(`${input}: ${error.message ?? error}`);
|
|
169
|
+
failed++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (failed) throw new Error(`${failed} conversion(s) failed`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Keep signal handling responsive while the conversion runs synchronous code.
|
|
176
|
+
// The worker observes cancellation and unwinds its existing catch/finally blocks.
|
|
177
|
+
async function start(options) {
|
|
178
|
+
const cancelled = new Int32Array(new SharedArrayBuffer(4));
|
|
179
|
+
let progressLine = "";
|
|
180
|
+
const endProgress = () => {
|
|
181
|
+
if (progressLine) process.stdout.write("\n");
|
|
182
|
+
progressLine = "";
|
|
183
|
+
};
|
|
184
|
+
const print = ({ method, args }) => {
|
|
185
|
+
if (method === "progress") {
|
|
186
|
+
const [line, done] = args;
|
|
187
|
+
if (process.stdout.isTTY) {
|
|
188
|
+
const width = process.stdout.columns;
|
|
189
|
+
const text = !done && width ? line.slice(0, width - 1) : line;
|
|
190
|
+
process.stdout.write(`\r\x1b[2K${text}${done ? "\n" : ""}`);
|
|
191
|
+
progressLine = done ? "" : text;
|
|
192
|
+
} else console.log(line);
|
|
193
|
+
} else {
|
|
194
|
+
// Insert logs above the live progress instead of leaving a stale row.
|
|
195
|
+
if (progressLine) process.stdout.write("\r\x1b[2K");
|
|
196
|
+
console[method](...args);
|
|
197
|
+
if (progressLine) process.stdout.write(progressLine);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const interrupt = (signal) => {
|
|
201
|
+
const code = signal === "SIGINT" ? 130 : 143;
|
|
202
|
+
if (Atomics.compareExchange(cancelled, 0, 0, code) === 0) {
|
|
203
|
+
endProgress();
|
|
204
|
+
console.error(`${signal}: cancelling conversion and cleaning up...`);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
process.on("SIGINT", interrupt);
|
|
208
|
+
process.on("SIGTERM", interrupt);
|
|
209
|
+
try {
|
|
210
|
+
const code = await new Promise((resolve, reject) => {
|
|
211
|
+
const worker = new Worker(new URL(import.meta.url), {
|
|
212
|
+
workerData: { options, cancelled },
|
|
213
|
+
});
|
|
214
|
+
worker.on("message", print);
|
|
215
|
+
worker.once("error", reject);
|
|
216
|
+
worker.once("exit", resolve);
|
|
217
|
+
});
|
|
218
|
+
process.exitCode = Atomics.load(cancelled, 0) || code;
|
|
219
|
+
} finally {
|
|
220
|
+
endProgress();
|
|
221
|
+
process.off("SIGINT", interrupt);
|
|
222
|
+
process.off("SIGTERM", interrupt);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
if (!isMainThread) {
|
|
228
|
+
// Worker stdout buffers later writes until its event loop yields.
|
|
229
|
+
// Forward logs directly so synchronous conversion keeps showing progress.
|
|
230
|
+
for (const method of ["log", "warn", "error"])
|
|
231
|
+
console[method] = (...args) => parentPort.postMessage({ method, args });
|
|
232
|
+
await run(workerData.options, workerData.cancelled);
|
|
233
|
+
} else {
|
|
234
|
+
const options = parse(process.argv.slice(2));
|
|
235
|
+
if (!options) console.log(help);
|
|
236
|
+
else await start(options);
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error(error.message);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
}
|