gaussian-splat-lite 1.4.2 → 1.4.3
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 +22 -1
- package/bin/build-lod.js +17 -2
- package/bin/rad/parallel.js +7 -6
- package/bin/rad/splats.js +27 -10
- package/dist/gaussian-splat-lite.cjs +104 -70
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +51 -51
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +104 -70
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +563 -544
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/data/IndexedSplats.d.cts +1 -4
- package/dist/types/data/IndexedSplats.d.ts +1 -4
- package/dist/types/data/Splats.d.cts +2 -6
- package/dist/types/data/Splats.d.ts +2 -6
- package/dist/types/data/defines.d.cts +3 -4
- package/dist/types/data/defines.d.ts +3 -4
- package/dist/types/data/splatData.d.cts +1 -1
- package/dist/types/data/splatData.d.ts +1 -1
- package/dist/types/data/splatRange.d.cts +1 -1
- package/dist/types/data/splatRange.d.ts +1 -1
- package/dist/types/loaders/morton.d.cts +6 -3
- package/dist/types/loaders/morton.d.ts +6 -3
- package/dist/types/loaders/postDecode/protocol.d.cts +1 -0
- package/dist/types/loaders/postDecode/protocol.d.ts +1 -0
- package/dist/types/loaders/rad.d.cts +5 -5
- package/dist/types/loaders/rad.d.ts +5 -5
- package/dist/types/loaders/stream/rad-stream/RadStreamScheduler.d.cts +0 -2
- package/dist/types/loaders/stream/rad-stream/RadStreamScheduler.d.ts +0 -2
- package/dist/types/loaders/stream/sog-stream/SogStreamLoader.d.cts +1 -1
- package/dist/types/loaders/stream/sog-stream/SogStreamLoader.d.ts +1 -1
- package/dist/types/loaders/stream/sog-stream/SogStreamScheduler.d.cts +16 -2
- package/dist/types/loaders/stream/sog-stream/SogStreamScheduler.d.ts +16 -2
- package/dist/types/loaders/stream/sog-stream/sogLod.d.cts +8 -9
- package/dist/types/loaders/stream/sog-stream/sogLod.d.ts +8 -9
- package/dist/types/loaders/stream/sog-stream/sogVisibility.d.cts +13 -7
- package/dist/types/loaders/stream/sog-stream/sogVisibility.d.ts +13 -7
- package/dist/types/loaders/stream/streamOptions.d.cts +1 -1
- package/dist/types/loaders/stream/streamOptions.d.ts +1 -1
- package/dist/types/scene/SplatMesh.d.cts +0 -2
- package/dist/types/scene/SplatMesh.d.ts +0 -2
- package/docs/BuildRad.md +1 -0
- package/docs/RadStreamScheduler.md +4 -4
- package/docs/SogStreamScheduler.md +14 -4
- package/docs/SplatMesh.md +2 -2
- package/docs/Splats.md +1 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.3] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added `--within-dist <x,y,z,radius>` to `gsl-build-lod` to keep splat centers inside or on a sphere in input coordinates before building LOD. See [Building RAD files](docs/BuildRad.md).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Streamed SOG keeps surrounding regions in its detail budget, with less detail behind the camera and corrected distances for oversized regions. Added runtime-adjustable `lodBehindPenalty` (default `4`) and `lodDistanceShrink` (default `0.75`).
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Removed raycast hit `sourceIndex`, `Splats.getSourceIndex()`, and `RadStreamScheduler.getGlobalIndex()`. Use `hit.object.splats.getSplat(hit.index)` to read the current hit; streamed indices can change when the visible detail changes.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Streamed SOG waits for initial detail selection before rejecting `firstRenderable`, including when an empty environment finishes last.
|
|
27
|
+
- Streamed SOG rejects `firstRenderable` when all required loads fail permanently, even while the camera keeps moving.
|
|
28
|
+
- Streamed SOG retains displayed coverage when detail selection fails, stopping further loading while existing fades finish.
|
|
29
|
+
|
|
10
30
|
## [1.4.2] - 2026-10-24
|
|
11
31
|
|
|
12
32
|
### Changed
|
|
@@ -660,7 +680,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
660
680
|
|
|
661
681
|
- Initial public release of the Three.js Gaussian Splatting renderer.
|
|
662
682
|
|
|
663
|
-
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.
|
|
683
|
+
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.3...HEAD
|
|
684
|
+
[1.4.3]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.2...v1.4.3
|
|
664
685
|
[1.4.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.1...v1.4.2
|
|
665
686
|
[1.4.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.0...v1.4.1
|
|
666
687
|
[1.4.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.2...v1.4.0
|
package/bin/build-lod.js
CHANGED
|
@@ -24,6 +24,8 @@ Output: <input-stem>-lod.rad. Existing files are not overwritten.
|
|
|
24
24
|
Options:
|
|
25
25
|
--lod-base <base> LOD spacing (default: 1.75, range: 1.1..16)
|
|
26
26
|
--max-sh <0..3> Maximum SH degree (default: 3)
|
|
27
|
+
--within-dist <x,y,z,radius>
|
|
28
|
+
Keep splat centers within this sphere (input coordinates)
|
|
27
29
|
--rad-chunked Write a .rad header and separate .radc files
|
|
28
30
|
--memory-budget <GiB> Target memory in GiB (default: 4, minimum: 1)
|
|
29
31
|
--temp-dir <path> Temporary file location (default: system temp)
|
|
@@ -62,6 +64,7 @@ function parse(args) {
|
|
|
62
64
|
const key = {
|
|
63
65
|
"--lod-base": "lodBase",
|
|
64
66
|
"--max-sh": "maxSh",
|
|
67
|
+
"--within-dist": "withinDist",
|
|
65
68
|
"--memory-budget": "budget",
|
|
66
69
|
"--temp-dir": "tempDir",
|
|
67
70
|
"-o": "output",
|
|
@@ -86,6 +89,16 @@ function parse(args) {
|
|
|
86
89
|
options.maxSh > 3
|
|
87
90
|
)
|
|
88
91
|
throw new Error("--max-sh must be an integer between 0 and 3");
|
|
92
|
+
if (options.withinDist !== undefined) {
|
|
93
|
+
const values = options.withinDist
|
|
94
|
+
.split(",")
|
|
95
|
+
.map((value) => (value.trim() ? Number(value) : Number.NaN));
|
|
96
|
+
if (values.length !== 4 || !values.every(Number.isFinite) || values[3] < 0)
|
|
97
|
+
throw new Error(
|
|
98
|
+
"--within-dist must be x,y,z,radius with finite numbers and a nonnegative radius",
|
|
99
|
+
);
|
|
100
|
+
options.withinDist = values;
|
|
101
|
+
}
|
|
89
102
|
if (
|
|
90
103
|
!Number.isFinite(options.budget) ||
|
|
91
104
|
options.budget < GIB ||
|
|
@@ -120,7 +133,7 @@ async function convert(input, options, cancelled) {
|
|
|
120
133
|
const splats = await readInput(input, options.maxSh, workspace);
|
|
121
134
|
const count = splats.length;
|
|
122
135
|
pool = new Pool(workspace, count);
|
|
123
|
-
splats.retain(pool);
|
|
136
|
+
splats.retain(pool, options.withinDist);
|
|
124
137
|
console.log(
|
|
125
138
|
`Read ${count} splats, SH${splats.inputSh}, in ${((performance.now() - start) / 1000).toFixed(2)}s`,
|
|
126
139
|
);
|
|
@@ -132,8 +145,10 @@ async function convert(input, options, cancelled) {
|
|
|
132
145
|
console.log(
|
|
133
146
|
`Removed ${splats.invalid} splats with NaN or infinite values or a zero rotation`,
|
|
134
147
|
);
|
|
135
|
-
const empty = count - splats.invalid - splats.length;
|
|
148
|
+
const empty = count - splats.invalid - splats.cropped - splats.length;
|
|
136
149
|
if (empty) console.log(`Removed ${empty} empty splats`);
|
|
150
|
+
if (splats.cropped)
|
|
151
|
+
console.log(`Removed ${splats.cropped} splats outside --within-dist`);
|
|
137
152
|
if (!splats.length) throw new Error("Input contains no usable splats");
|
|
138
153
|
const leaves = splats.length;
|
|
139
154
|
const lodStart = performance.now();
|
package/bin/rad/parallel.js
CHANGED
|
@@ -9,13 +9,12 @@ import {
|
|
|
9
9
|
import { Lookup, NONE } from "./grid.js";
|
|
10
10
|
import { HALF, halfBits } from "./math.js";
|
|
11
11
|
import {
|
|
12
|
-
EMPTY,
|
|
13
12
|
INVALID,
|
|
14
|
-
KEEP,
|
|
15
13
|
LATER,
|
|
16
14
|
Merger,
|
|
17
15
|
SH_DIMS,
|
|
18
16
|
Similarities,
|
|
17
|
+
filterRecord,
|
|
19
18
|
refresh,
|
|
20
19
|
validRecord,
|
|
21
20
|
} from "./splats.js";
|
|
@@ -231,14 +230,15 @@ export class Pool {
|
|
|
231
230
|
this.run(COPY, count, 1024);
|
|
232
231
|
}
|
|
233
232
|
|
|
234
|
-
// Refreshes and
|
|
235
|
-
//
|
|
236
|
-
check(splats, base, count) {
|
|
233
|
+
// Refreshes, checks and optionally crops `count` splats from `base` in
|
|
234
|
+
// place, storing their filter states in `results`; see Splats.retain().
|
|
235
|
+
check(splats, base, count, withinDist) {
|
|
237
236
|
this.share({
|
|
238
237
|
splatRecords: splats.records.directory,
|
|
239
238
|
splatSh: splats.sh
|
|
240
239
|
.slice(0, splats.maxSh)
|
|
241
240
|
.map((column) => column.directory),
|
|
241
|
+
withinDist,
|
|
242
242
|
});
|
|
243
243
|
Atomics.store(this.control, BASE, base);
|
|
244
244
|
this.run(CHECK, count, 1024);
|
|
@@ -370,6 +370,7 @@ class Task {
|
|
|
370
370
|
if (state.column)
|
|
371
371
|
this.column = new SharedColumn(views, state.column, Uint16Array);
|
|
372
372
|
if (state.splatRecords) {
|
|
373
|
+
this.withinDist = state.withinDist;
|
|
373
374
|
this.splatRecords = new SharedColumn(
|
|
374
375
|
views,
|
|
375
376
|
state.splatRecords,
|
|
@@ -450,7 +451,7 @@ class Task {
|
|
|
450
451
|
for (let d = 0; d < dims; d++)
|
|
451
452
|
if (!Number.isFinite(HALF[row[at + d]])) return INVALID;
|
|
452
453
|
}
|
|
453
|
-
return record
|
|
454
|
+
return filterRecord(record, this.withinDist);
|
|
454
455
|
}
|
|
455
456
|
|
|
456
457
|
copy(from, to) {
|
package/bin/rad/splats.js
CHANGED
|
@@ -20,8 +20,9 @@ export const SH_DIMS = [9, 15, 21];
|
|
|
20
20
|
export const KEEP = 0;
|
|
21
21
|
export const INVALID = 1;
|
|
22
22
|
export const EMPTY = 2;
|
|
23
|
+
export const CROPPED = 3;
|
|
23
24
|
// A worker found a page that is not cached.
|
|
24
|
-
export const LATER =
|
|
25
|
+
export const LATER = 4;
|
|
25
26
|
|
|
26
27
|
// Sets a record's feature size and area from its scales and opacity.
|
|
27
28
|
export function refresh(record) {
|
|
@@ -45,6 +46,16 @@ export function validRecord(record) {
|
|
|
45
46
|
return true;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// Classifies a valid record after refresh(), including optional cropping.
|
|
50
|
+
export function filterRecord(record, withinDist) {
|
|
51
|
+
if (record[3] <= 0 || record[14] <= 0) return EMPTY;
|
|
52
|
+
if (!withinDist) return KEEP;
|
|
53
|
+
const [x, y, z, radius] = withinDist;
|
|
54
|
+
return Math.hypot(record[0] - x, record[1] - y, record[2] - z) > radius
|
|
55
|
+
? CROPPED
|
|
56
|
+
: KEEP;
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
export class Splats {
|
|
49
60
|
constructor(workspace, maxSh) {
|
|
50
61
|
this.workspace = workspace;
|
|
@@ -52,6 +63,7 @@ export class Splats {
|
|
|
52
63
|
this.inputSh = 0;
|
|
53
64
|
this.length = 0;
|
|
54
65
|
this.invalid = 0;
|
|
66
|
+
this.cropped = 0;
|
|
55
67
|
this.records = new PagedArray(workspace, Float32Array);
|
|
56
68
|
this.sh = SH_DIMS.map(() => new PagedArray(workspace, Uint16Array));
|
|
57
69
|
// The two children of the i-th merged splat are entries 2i and 2i + 1 of
|
|
@@ -127,9 +139,11 @@ export class Splats {
|
|
|
127
139
|
|
|
128
140
|
// Skips empty splats and counts invalid ones in `invalid`: NaN or infinite
|
|
129
141
|
// values, including a zero rotation, which the decoder normalizes to NaN.
|
|
130
|
-
//
|
|
142
|
+
// Optionally keeps only centers within [x, y, z, radius], counting the
|
|
143
|
+
// otherwise usable splats outside this sphere in `cropped`.
|
|
144
|
+
// Workers refresh, check and crop blocks of splats; the main thread
|
|
131
145
|
// checks those they leave, and moves kept splats after skipped ones.
|
|
132
|
-
retain(pool) {
|
|
146
|
+
retain(pool, withinDist) {
|
|
133
147
|
const progress = new Progress("Filtering splats", this.length);
|
|
134
148
|
const { results } = pool;
|
|
135
149
|
const block = results.length;
|
|
@@ -143,6 +157,7 @@ export class Splats {
|
|
|
143
157
|
];
|
|
144
158
|
let count = 0;
|
|
145
159
|
this.invalid = 0;
|
|
160
|
+
this.cropped = 0;
|
|
146
161
|
for (let base = 0; base < this.length; base += block) {
|
|
147
162
|
const size = Math.min(block, this.length - base);
|
|
148
163
|
for (const [column, stride] of columns)
|
|
@@ -151,24 +166,26 @@ export class Splats {
|
|
|
151
166
|
(base + size) * stride,
|
|
152
167
|
column === this.records,
|
|
153
168
|
);
|
|
154
|
-
pool.check(this, base, size);
|
|
169
|
+
pool.check(this, base, size, withinDist);
|
|
155
170
|
for (let i = 0; i < size; i++) {
|
|
156
171
|
const index = base + i;
|
|
157
172
|
let state = results[i];
|
|
158
|
-
|
|
173
|
+
const later = state === LATER;
|
|
174
|
+
if (later) {
|
|
159
175
|
this.read(index, record);
|
|
160
176
|
refresh(record);
|
|
161
177
|
state = !this.valid(index, record, rows)
|
|
162
178
|
? INVALID
|
|
163
|
-
: record
|
|
164
|
-
? KEEP
|
|
165
|
-
: EMPTY;
|
|
166
|
-
if (state === KEEP) this.records.write(index * 16, record);
|
|
179
|
+
: filterRecord(record, withinDist);
|
|
167
180
|
}
|
|
168
181
|
if (state === INVALID) this.invalid++;
|
|
182
|
+
if (state === CROPPED) this.cropped++;
|
|
169
183
|
if (state !== KEEP) continue;
|
|
184
|
+
if (count !== index || later) {
|
|
185
|
+
if (!later) this.read(index, record);
|
|
186
|
+
this.records.write(count * 16, record);
|
|
187
|
+
}
|
|
170
188
|
if (count !== index) {
|
|
171
|
-
this.records.write(count * 16, this.read(index, record));
|
|
172
189
|
for (let band = 0; band < this.maxSh; band++) {
|
|
173
190
|
const dims = SH_DIMS[band];
|
|
174
191
|
this.sh[band].read(index * dims, rows[band]);
|