Skip to content

Commit 2dfc83e

Browse files
authored
Merge pull request #413 from mkkellogg/dev
Release 0.4.7
2 parents 408943e + c3cd322 commit 2dfc83e

21 files changed

+989
-269
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,8 @@ Advanced `Viewer` parameters
339339
| `logLevel` | Verbosity of the console logging. Defaults to `GaussianSplats3D.LogLevel.None`.
340340
| `sphericalHarmonicsDegree` | Degree of spherical harmonics to utilize in rendering splats (assuming the data is present in the splat scene). Valid values are 0, 1, or 2. Default value is 0.
341341
| `enableOptionalEffects` | When true, allows for usage of extra properties and attributes during rendering for effects such as opacity adjustment. Default is `false` for performance reasons. These properties are separate from transform properties (scale, rotation, position) that are enabled by the `dynamicScene` parameter.
342-
| `inMemoryCompressionLevel` | Level to compress `.ply` or `.ksplat` files when loading them for direct rendering (not exporting to `.ksplat`). Valid values are the same as `.ksplat` compression levels (0, 1, or 2). Default is 0.
343-
| `optimizeSplatData` | Reorder splat data in memory after loading is complete to optimize cache utilization. Default is `true`. Does not apply if splat scene is progressively loaded.
342+
| `optimizeSplatData` | After loading is complete, reorder splat data in memory to optimize cache utilization as well as apply in-memory compression to reduce memory usage. Default is `true`. This option is automatically disabled if the scene is progressively loaded.
343+
| `inMemoryCompressionLevel` | Level to compress scenes when loading them for direct rendering (not exporting to `.ksplat`). Valid values are the same as `.ksplat` compression levels (0, 1, or 2). Default is 0 (uncompressed). If a scene is loaded progressively, or `optimizeSplatData` is set to `false`, `inMemoryCompressionLevel` will be 0.
344344
| `freeIntermediateSplatData` | When true, the intermediate splat data that is the result of decompressing splat bufffer(s) and used to populate data textures will be freed. This will reduces memory usage, but if that data needs to be modified it will need to be re-populated from the splat buffer(s). Defaults to `false`.
345345
| `splatRenderMode` | Determine which splat rendering mode to enable. Valid values are defined in the `SplatRenderMode` enum: `ThreeD` and `TwoD`. `ThreeD` is the original/traditional mode and `TwoD` is the new mode described here: https://surfsplatting.github.io/
346346
| `sceneFadeInRateMultiplier` | Customize the speed at which the scene is revealed. Default is 1.0.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"type": "git",
55
"url": "https://github.com/mkkellogg/GaussianSplats3D"
66
},
7-
"version": "0.4.6",
7+
"version": "0.4.7",
88
"description": "Three.js-based 3D Gaussian splat viewer",
99
"module": "build/gaussian-splats-3d.module.js",
1010
"main": "build/gaussian-splats-3d.umd.cjs",

src/Util.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ export const fetchWithProgress = function(path, onProgress, saveChunks = true, h
6464
aborted = true;
6565
};
6666

67+
let onProgressCalledAtComplete = false;
68+
const localOnProgress = (percent, percentLabel, chunk, fileSize) => {
69+
if (onProgress && !onProgressCalledAtComplete) {
70+
onProgress(percent, percentLabel, chunk, fileSize);
71+
if (percent === 100) {
72+
onProgressCalledAtComplete = true;
73+
}
74+
}
75+
};
76+
6777
return new AbortablePromise((resolve, reject) => {
6878
const fetchOptions = { signal };
6979
if (headers) fetchOptions.headers = headers;
@@ -87,9 +97,7 @@ export const fetchWithProgress = function(path, onProgress, saveChunks = true, h
8797
try {
8898
const { value: chunk, done } = await reader.read();
8999
if (done) {
90-
if (onProgress) {
91-
onProgress(100, '100%', chunk, fileSize);
92-
}
100+
localOnProgress(100, '100%', chunk, fileSize);
93101
if (saveChunks) {
94102
const buffer = new Blob(chunks).arrayBuffer();
95103
resolve(buffer);
@@ -108,9 +116,7 @@ export const fetchWithProgress = function(path, onProgress, saveChunks = true, h
108116
if (saveChunks) {
109117
chunks.push(chunk);
110118
}
111-
if (onProgress) {
112-
onProgress(percent, percentLabel, chunk, fileSize);
113-
}
119+
localOnProgress(percent, percentLabel, chunk, fileSize);
114120
} catch (error) {
115121
reject(error);
116122
return;
@@ -151,20 +157,24 @@ export const disposeAllMeshes = (object3D) => {
151157
export const delayedExecute = (func, fast) => {
152158
return new Promise((resolve) => {
153159
window.setTimeout(() => {
154-
resolve(func());
160+
resolve(func ? func() : undefined);
155161
}, fast ? 1 : 50);
156162
});
157163
};
158164

159165

160166
export const getSphericalHarmonicsComponentCountForDegree = (sphericalHarmonicsDegree = 0) => {
161-
switch (sphericalHarmonicsDegree) {
162-
case 1:
163-
return 9;
164-
case 2:
165-
return 24;
167+
let shCoeffPerSplat = 0;
168+
if (sphericalHarmonicsDegree === 1) {
169+
shCoeffPerSplat = 9;
170+
} else if (sphericalHarmonicsDegree === 2) {
171+
shCoeffPerSplat = 24;
172+
} else if (sphericalHarmonicsDegree === 3) {
173+
shCoeffPerSplat = 45;
174+
} else if (sphericalHarmonicsDegree > 3) {
175+
throw new Error('getSphericalHarmonicsComponentCountForDegree() -> Invalid spherical harmonics degree');
166176
}
167-
return 0;
177+
return shCoeffPerSplat;
168178
};
169179

170180
export const nativePromiseWithExtractedComponents = () => {

src/Viewer.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { OrbitControls } from './OrbitControls.js';
33
import { PlyLoader } from './loaders/ply/PlyLoader.js';
44
import { SplatLoader } from './loaders/splat/SplatLoader.js';
55
import { KSplatLoader } from './loaders/ksplat/KSplatLoader.js';
6+
import { SpzLoader } from './loaders/spz/SpzLoader.js';
67
import { sceneFormatFromPath } from './loaders/Utils.js';
78
import { LoadingSpinner } from './ui/LoadingSpinner.js';
89
import { LoadingProgressBar } from './ui/LoadingProgressBar.js';
@@ -857,8 +858,7 @@ export class Viewer {
857858
if (onException) onException();
858859
this.clearSplatSceneDownloadAndBuildPromise();
859860
this.removeSplatSceneDownloadPromise(downloadPromise);
860-
const error = (e instanceof AbortedPromiseError) ? e : new Error(`Viewer::addSplatScene -> Could not load file ${path}`);
861-
downloadAndBuildPromise.reject(error);
861+
downloadAndBuildPromise.reject(this.updateError(e, `Viewer::addSplatScene -> Could not load file ${path}`));
862862
});
863863

864864
this.addSplatSceneDownloadPromise(downloadPromise);
@@ -938,7 +938,7 @@ export class Viewer {
938938
.catch((e) => {
939939
this.clearSplatSceneDownloadAndBuildPromise();
940940
this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
941-
const error = (e instanceof AbortedPromiseError) ? e : new Error(`Viewer::addSplatScene -> Could not load one or more scenes`);
941+
const error = this.updateError(e, `Viewer::addSplatScene -> Could not load one or more scenes`);
942942
progressiveLoadFirstSectionBuildPromise.reject(error);
943943
if (onDownloadException) onDownloadException(error);
944944
});
@@ -1030,9 +1030,7 @@ export class Viewer {
10301030
.catch((e) => {
10311031
if (showLoadingUI) this.loadingSpinner.removeTask(loadingUITaskId);
10321032
this.clearSplatSceneDownloadAndBuildPromise();
1033-
const error = (e instanceof AbortedPromiseError) ? e :
1034-
new Error(`Viewer::addSplatScenes -> Could not load one or more splat scenes.`);
1035-
reject(error);
1033+
reject(this.updateError(e, `Viewer::addSplatScenes -> Could not load one or more splat scenes.`));
10361034
})
10371035
.finally(() => {
10381036
this.removeSplatSceneDownloadPromise(downloadAndBuildPromise);
@@ -1062,24 +1060,24 @@ export class Viewer {
10621060
*/
10631061
downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold = 1, onProgress = undefined,
10641062
progressiveBuild = false, onSectionBuilt = undefined, format, headers) {
1065-
1066-
const optimizeSplatData = progressiveBuild ? false : this.optimizeSplatData;
10671063
try {
1068-
if (format === SceneFormat.Splat) {
1069-
return SplatLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, splatAlphaRemovalThreshold,
1070-
this.inMemoryCompressionLevel, optimizeSplatData, headers);
1071-
} else if (format === SceneFormat.KSplat) {
1072-
return KSplatLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, headers);
1073-
} else if (format === SceneFormat.Ply) {
1074-
return PlyLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, splatAlphaRemovalThreshold,
1075-
this.inMemoryCompressionLevel, optimizeSplatData, this.sphericalHarmonicsDegree, headers);
1064+
if (format === SceneFormat.Splat || format === SceneFormat.KSplat || format === SceneFormat.Ply) {
1065+
const optimizeSplatData = progressiveBuild ? false : this.optimizeSplatData;
1066+
if (format === SceneFormat.Splat) {
1067+
return SplatLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, splatAlphaRemovalThreshold,
1068+
this.inMemoryCompressionLevel, optimizeSplatData, headers);
1069+
} else if (format === SceneFormat.KSplat) {
1070+
return KSplatLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, headers);
1071+
} else if (format === SceneFormat.Ply) {
1072+
return PlyLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt, splatAlphaRemovalThreshold,
1073+
this.inMemoryCompressionLevel, optimizeSplatData, this.sphericalHarmonicsDegree, headers);
1074+
}
1075+
} else if (format === SceneFormat.Spz) {
1076+
return SpzLoader.loadFromURL(path, onProgress, splatAlphaRemovalThreshold, this.inMemoryCompressionLevel,
1077+
this.optimizeSplatData, this.sphericalHarmonicsDegree, headers);
10761078
}
10771079
} catch (e) {
1078-
if (e instanceof DirectLoadError) {
1079-
throw new Error('File type or server does not support progressive loading.');
1080-
} else {
1081-
throw e;
1082-
}
1080+
throw this.updateError(e, null);
10831081
}
10841082

10851083
throw new Error(`Viewer::downloadSplatSceneToSplatBuffer -> File format not supported: ${path}`);
@@ -1301,6 +1299,14 @@ export class Viewer {
13011299
});
13021300
}
13031301

1302+
updateError(error, defaultMessage) {
1303+
if (error instanceof AbortedPromiseError) return error;
1304+
if (error instanceof DirectLoadError) {
1305+
return new Error('File type or server does not support progressive loading.');
1306+
}
1307+
return defaultMessage ? new Error(defaultMessage) : error;
1308+
}
1309+
13041310
disposeSortWorker() {
13051311
if (this.sortWorker) this.sortWorker.terminate();
13061312
this.sortWorker = null;

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PlyParser } from './loaders/ply/PlyParser.js';
22
import { PlayCanvasCompressedPlyParser } from './loaders/ply/PlayCanvasCompressedPlyParser.js';
33
import { PlyLoader } from './loaders/ply/PlyLoader.js';
4+
import { SpzLoader } from './loaders/spz/SpzLoader.js';
45
import { SplatLoader } from './loaders/splat/SplatLoader.js';
56
import { KSplatLoader } from './loaders/ksplat/KSplatLoader.js';
67
import * as LoaderUtils from './loaders/Utils.js';
@@ -23,6 +24,7 @@ export {
2324
PlyParser,
2425
PlayCanvasCompressedPlyParser,
2526
PlyLoader,
27+
SpzLoader,
2628
SplatLoader,
2729
KSplatLoader,
2830
LoaderUtils,

src/loaders/Compression.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const createStream = (data)=> {
2+
return new ReadableStream({
3+
async start(controller) {
4+
controller.enqueue(data);
5+
controller.close();
6+
},
7+
});
8+
};
9+
10+
export async function decompressGzipped(data) {
11+
try {
12+
const stream = createStream(data);
13+
if (!stream) throw new Error('Failed to create stream from data');
14+
15+
return await decompressGzipStream(stream);
16+
} catch (error) {
17+
console.error('Error decompressing gzipped data:', error);
18+
throw error;
19+
}
20+
}
21+
22+
export async function decompressGzipStream(stream) {
23+
const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip'));
24+
const response = new Response(decompressedStream);
25+
const buffer = await response.arrayBuffer();
26+
27+
return new Uint8Array(buffer);
28+
}
29+
30+
export async function compressGzipped(data) {
31+
try {
32+
const stream = createStream(data);
33+
const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
34+
const response = new Response(compressedStream);
35+
const buffer = await response.arrayBuffer();
36+
37+
return new Uint8Array(buffer);
38+
} catch (error) {
39+
console.error('Error compressing gzipped data:', error);
40+
throw error;
41+
}
42+
}

src/loaders/InternalLoadType.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const InternalLoadType = {
2-
DirectToSplatBuffer: 0,
3-
DirectToSplatArray: 1,
2+
ProgressiveToSplatBuffer: 0,
3+
ProgressiveToSplatArray: 1,
44
DownloadBeforeProcessing: 2
55
};

src/loaders/SceneFormat.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const SceneFormat = {
22
'Splat': 0,
33
'KSplat': 1,
4-
'Ply': 2
4+
'Ply': 2,
5+
'Spz': 3
56
};

src/loaders/SplatBuffer.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,4 +1398,38 @@ export class SplatBuffer {
13981398
};
13991399
}
14001400

1401+
static preallocateUncompressed(splatCount, sphericalHarmonicsDegrees) {
1402+
const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[sphericalHarmonicsDegrees];
1403+
const splatBufferDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
1404+
const splatBufferSizeBytes = splatBufferDataOffsetBytes + shDescriptor.BytesPerSplat * splatCount;
1405+
const outBuffer = new ArrayBuffer(splatBufferSizeBytes);
1406+
SplatBuffer.writeHeaderToBuffer({
1407+
versionMajor: SplatBuffer.CurrentMajorVersion,
1408+
versionMinor: SplatBuffer.CurrentMinorVersion,
1409+
maxSectionCount: 1,
1410+
sectionCount: 1,
1411+
maxSplatCount: splatCount,
1412+
splatCount: splatCount,
1413+
compressionLevel: 0,
1414+
sceneCenter: new THREE.Vector3()
1415+
}, outBuffer);
1416+
1417+
SplatBuffer.writeSectionHeaderToBuffer({
1418+
maxSplatCount: splatCount,
1419+
splatCount: splatCount,
1420+
bucketSize: 0,
1421+
bucketCount: 0,
1422+
bucketBlockSize: 0,
1423+
compressionScaleRange: 0,
1424+
storageSizeBytes: 0,
1425+
fullBucketCount: 0,
1426+
partiallyFilledBucketCount: 0,
1427+
sphericalHarmonicsDegree: sphericalHarmonicsDegrees
1428+
}, 0, outBuffer, SplatBuffer.HeaderSizeBytes);
1429+
1430+
return {
1431+
splatBuffer: new SplatBuffer(outBuffer, true),
1432+
splatBufferDataOffsetBytes
1433+
};
1434+
}
14011435
}

0 commit comments

Comments
 (0)