gaussian-splat-lite 1.3.1 → 1.4.0
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 +30 -1
- package/CONTRIBUTING.md +14 -2
- package/NOTICE +11 -3
- package/README.md +2 -1
- 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 +101 -48
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +177 -140
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +101 -48
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +2155 -2087
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/data/IndexedSplats.d.cts +2 -2
- package/dist/types/data/IndexedSplats.d.ts +2 -2
- package/dist/types/data/RadPagedSplats.d.cts +1 -1
- package/dist/types/data/RadPagedSplats.d.ts +1 -1
- package/dist/types/data/Splats.d.cts +1 -1
- package/dist/types/data/Splats.d.ts +1 -1
- package/dist/types/data/defines.d.cts +6 -4
- package/dist/types/data/defines.d.ts +6 -4
- package/dist/types/data/splatData.d.cts +10 -3
- package/dist/types/data/splatData.d.ts +10 -3
- package/dist/types/loaders/postDecode/builder.d.cts +1 -1
- package/dist/types/loaders/postDecode/builder.d.ts +1 -1
- package/dist/types/loaders/postDecode/protocol.d.cts +7 -5
- package/dist/types/loaders/postDecode/protocol.d.ts +7 -5
- 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/PostDecode.md +2 -2
- package/docs/RadStreamScheduler.md +3 -1
- package/docs/SplatFileType.md +2 -0
- package/docs/SplatMesh.md +1 -1
- package/docs/Splats.md +1 -1
- package/package.json +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.0] - 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- 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).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- RAD files and gzip-compressed SPZ files (v1–v3) decode about 10% faster.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed incorrect orientation and unstable widths of thin Splats on WebGL, WebGPU, and the WebGL2 fallback.
|
|
23
|
+
|
|
24
|
+
## [1.3.2] - 2026-10-18
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added SH4 decoding for PLY and SPZ and rendering on WebGL, WebGPU, and the WebGL2 fallback. `Splats.getSplat()` and `postDecode` now expose all 24 view-dependent RGB coefficients.
|
|
29
|
+
- Added a Max SH control to the viewer, from SH0 to SH4. Changes apply immediately to regular and streamed models; the default uses all available coefficients.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Renamed the `SplatExtra` fields `sh1`, `sh2`, `sh3a`, `sh3b`, `sh4a`, and `sh4b` to `shBlock0`–`shBlock5`, respectively. Update direct field access to use the new names.
|
|
34
|
+
- `SplatMesh.maxSh` now defaults to `4`, so SH4 models render their full view-dependent color. Set it to `3` to limit rendering to the previous maximum degree.
|
|
35
|
+
- SH4 SPZ models now use more memory because their full view-dependent color data is retained. `SplatMesh.maxSh` only limits rendering detail and does not reduce memory use.
|
|
36
|
+
|
|
10
37
|
## [1.3.1] - 2026-10-17
|
|
11
38
|
|
|
12
39
|
### Added
|
|
@@ -608,7 +635,9 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
608
635
|
|
|
609
636
|
- Initial public release of the Three.js Gaussian Splatting renderer.
|
|
610
637
|
|
|
611
|
-
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.
|
|
638
|
+
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.0...HEAD
|
|
639
|
+
[1.4.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.2...v1.4.0
|
|
640
|
+
[1.3.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.1...v1.3.2
|
|
612
641
|
[1.3.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.0...v1.3.1
|
|
613
642
|
[1.3.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.2.2...v1.3.0
|
|
614
643
|
[1.2.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.2.1...v1.2.2
|
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
|
@@ -175,6 +175,7 @@ const streaming = new SogStreamScheduler({
|
|
|
175
175
|
|
|
176
176
|
**Large scenes**
|
|
177
177
|
|
|
178
|
+
- [Building RAD files](docs/BuildRad.md) — Convert models to streamable RAD with quality LOD.
|
|
178
179
|
- [RadStreamScheduler](docs/RadStreamScheduler.md) — Large RAD scenes with adaptive detail.
|
|
179
180
|
- [SogStreamScheduler](docs/SogStreamScheduler.md) — Large SOG scenes with adaptive detail.
|
|
180
181
|
|
|
@@ -194,7 +195,7 @@ See [Contributing](CONTRIBUTING.md#validation) for validation commands. `npm run
|
|
|
194
195
|
|
|
195
196
|
## Acknowledgements
|
|
196
197
|
|
|
197
|
-
|
|
198
|
+
Gaussian Splat Lite uses [Spark](https://github.com/sparkjsdev/spark) and [SuperSplat](https://github.com/playcanvas/supersplat) as reference implementations.
|
|
198
199
|
|
|
199
200
|
Example model adapted from [shehabmekky](https://superspl.at/scene/c1e6297e) ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)).
|
|
200
201
|
|
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
|
+
}
|