Skip to content

Commit 29ad3a4

Browse files
mvaligurskyMartin Valigursky
andauthored
GSplat LOD format supports environment tag (#8110)
Co-authored-by: Martin Valigursky <[email protected]>
1 parent 24658e4 commit 29ad3a4

File tree

4 files changed

+131
-40
lines changed

4 files changed

+131
-40
lines changed

examples/src/examples/gaussian-splatting/lod-streaming-sh.example.mjs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ app.on('destroy', () => {
4949
// Skatepark configuration
5050
const config = {
5151
name: 'Skatepark',
52-
url: 'https://code.playcanvas.com/examples_data/example_skatepark_01/lod-meta.json',
53-
environment: 'https://code.playcanvas.com/examples_data/example_skatepark_01/environment.sog',
52+
url: 'https://code.playcanvas.com/examples_data/example_skatepark_02/lod-meta.json',
5453
lodUpdateDistance: 1,
5554
lodUnderfillLimit: 10,
5655
cameraPosition: [32, 2, 2],
@@ -94,11 +93,6 @@ const assets = {
9493
)
9594
};
9695

97-
// Add environment asset if specified in config
98-
if (config.environment) {
99-
assets.environment = new pc.Asset('gsplat-environment', 'gsplat', { url: config.environment });
100-
}
101-
10296
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
10397
assetListLoader.load(() => {
10498
app.start();
@@ -159,19 +153,6 @@ assetListLoader.load(() => {
159153
applyPreset();
160154
data.on('lodPreset:set', applyPreset);
161155

162-
// Add environment gsplat if specified
163-
if (assets.environment) {
164-
const envEntity = new pc.Entity('gsplat-environment');
165-
envEntity.addComponent('gsplat', {
166-
asset: assets.environment,
167-
unified: true
168-
});
169-
envEntity.setLocalPosition(0, 0, 0);
170-
envEntity.setLocalEulerAngles(rotX, rotY, rotZ);
171-
envEntity.setLocalScale(1, 1, 1);
172-
app.root.addChild(envEntity);
173-
}
174-
175156
// Create a camera with fly controls
176157
const camera = new pc.Entity('camera');
177158
camera.addComponent('camera', {

examples/src/examples/gaussian-splatting/lod-streaming.example.mjs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ app.on('destroy', () => {
6262
// original dataset: https://www.youtube.com/watch?v=3RtY_cLK13k
6363
const config = {
6464
name: 'Roman-Parish',
65-
url: 'https://code.playcanvas.com/examples_data/example_roman_parish_01/lod-meta.json',
66-
environment: 'https://code.playcanvas.com/examples_data/example_roman_parish_01/environment.sog',
65+
url: 'https://code.playcanvas.com/examples_data/example_roman_parish_02/lod-meta.json',
6766
lodUpdateDistance: 0.5,
6867
lodUnderfillLimit: 5,
6968
cameraPosition: [10.3, 2, -10],
@@ -107,11 +106,6 @@ const assets = {
107106
)
108107
};
109108

110-
// Add environment asset if specified in config
111-
if (config.environment) {
112-
assets.environment = new pc.Asset('gsplat-environment', 'gsplat', { url: config.environment });
113-
}
114-
115109
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
116110
assetListLoader.load(() => {
117111
app.start();
@@ -172,19 +166,6 @@ assetListLoader.load(() => {
172166
applyPreset();
173167
data.on('lodPreset:set', applyPreset);
174168

175-
// Add environment gsplat if specified
176-
if (assets.environment) {
177-
const envEntity = new pc.Entity('gsplat-environment');
178-
envEntity.addComponent('gsplat', {
179-
asset: assets.environment,
180-
unified: true
181-
});
182-
envEntity.setLocalPosition(0, 0, 0);
183-
envEntity.setLocalEulerAngles(rotX, rotY, rotZ);
184-
envEntity.setLocalScale(1, 1, 1);
185-
app.root.addChild(envEntity);
186-
}
187-
188169
// Create a camera with fly controls
189170
const camera = new pc.Entity('camera');
190171
camera.addComponent('camera', {

src/scene/gsplat-unified/gsplat-octree-instance.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ class GSplatOctreeInstance {
112112
*/
113113
pendingVisibleAdds = new Map();
114114

115+
/**
116+
* Environment placement.
117+
* @type {GSplatPlacement|null}
118+
*/
119+
environmentPlacement = null;
120+
115121
/**
116122
* @param {GSplatOctree} octree - The octree.
117123
* @param {GSplatPlacement} placement - The placement.
@@ -129,6 +135,12 @@ class GSplatOctreeInstance {
129135
// Initialize file placements array
130136
const numFiles = octree.files.length;
131137
this.filePlacements = new Array(numFiles).fill(null);
138+
139+
// Handle environment if configured
140+
if (octree.environmentUrl) {
141+
octree.incEnvironmentRefCount();
142+
octree.ensureEnvironmentResource(assetLoader);
143+
}
132144
}
133145

134146
/**
@@ -138,6 +150,13 @@ class GSplatOctreeInstance {
138150
this.pending.clear();
139151
this.pendingDecrements.clear();
140152
this.filePlacements.length = 0;
153+
154+
// Clean up environment if present
155+
if (this.environmentPlacement) {
156+
this.activePlacements.delete(this.environmentPlacement);
157+
this.octree.decEnvironmentRefCount(this.assetLoader);
158+
this.environmentPlacement = null;
159+
}
141160
}
142161

143162
/**
@@ -599,6 +618,21 @@ class GSplatOctreeInstance {
599618
// watch prefetched loads for completion to allow promotion
600619
this.pollPrefetchCompletions();
601620

621+
// handle environment loading
622+
if (this.octree.environmentUrl && !this.environmentPlacement) {
623+
// poll for environment resource completion
624+
this.octree.ensureEnvironmentResource(this.assetLoader);
625+
const envResource = this.octree.environmentResource;
626+
627+
if (envResource) {
628+
// create environment placement with the loaded resource
629+
this.environmentPlacement = new GSplatPlacement(envResource, this.placement.node, 0);
630+
this.environmentPlacement.aabb.copy(envResource.aabb);
631+
this.activePlacements.add(this.environmentPlacement);
632+
this.dirtyModifiedPlacements = true;
633+
}
634+
}
635+
602636
// check if any placements need LOD update
603637
const dirty = this.dirtyModifiedPlacements;
604638
this.dirtyModifiedPlacements = false;

src/scene/gsplat-unified/gsplat-octree.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ class GSplatOctree {
5959
*/
6060
cooldowns = new Map();
6161

62+
/**
63+
* Optional environment asset URL.
64+
*
65+
* @type {string|null}
66+
*/
67+
environmentUrl = null;
68+
69+
/**
70+
* Loaded environment resource.
71+
*
72+
* @type {GSplatResource|null}
73+
*/
74+
environmentResource = null;
75+
76+
/**
77+
* Reference count for environment usage.
78+
*
79+
* @type {number}
80+
*/
81+
environmentRefCount = 0;
82+
6283
/**
6384
* @param {string} assetFileUrl - The file URL of the container asset.
6485
* @param {Object} data - The parsed JSON data containing info, filenames and tree.
@@ -78,6 +99,13 @@ class GSplatOctree {
7899
// initialize per-file ref counts
79100
this.fileRefCounts = new Int32Array(this.files.length);
80101

102+
// parse optional environment field and resolve path
103+
if (data.environment) {
104+
this.environmentUrl = path.isRelativePath(data.environment) ?
105+
path.join(baseDir, data.environment) :
106+
data.environment;
107+
}
108+
81109
// Extract leaf nodes from hierarchical tree structure
82110
const leafNodes = [];
83111
this._extractLeafNodes(data.tree, leafNodes);
@@ -280,6 +308,73 @@ class GSplatOctree {
280308
// Start/continue loading (asset loader handles duplicates internally)
281309
assetLoader.load(fullUrl);
282310
}
311+
312+
/**
313+
* Increments reference count for environment.
314+
*/
315+
incEnvironmentRefCount() {
316+
this.environmentRefCount++;
317+
}
318+
319+
/**
320+
* Decrements reference count for environment. When it reaches zero, immediately unload.
321+
*
322+
* @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload the resource.
323+
*/
324+
decEnvironmentRefCount(assetLoader) {
325+
this.environmentRefCount--;
326+
Debug.assert(this.environmentRefCount >= 0);
327+
328+
// unload immediately when reaching zero
329+
if (this.environmentRefCount === 0) {
330+
this.unloadEnvironmentResource(assetLoader);
331+
}
332+
}
333+
334+
/**
335+
* Ensures environment resource is loaded and available.
336+
*
337+
* @param {GSplatAssetLoaderBase} assetLoader - The asset loader.
338+
*/
339+
ensureEnvironmentResource(assetLoader) {
340+
// no environment configured
341+
if (!this.environmentUrl) {
342+
return;
343+
}
344+
345+
// resource already loaded
346+
if (this.environmentResource) {
347+
return;
348+
}
349+
350+
// Check if the resource is now available from the asset loader
351+
const res = assetLoader.getResource(this.environmentUrl);
352+
if (res) {
353+
this.environmentResource = res;
354+
355+
// if loaded but not needed, immediately unload
356+
if (this.environmentRefCount === 0) {
357+
this.unloadEnvironmentResource(assetLoader);
358+
}
359+
360+
return;
361+
}
362+
363+
// Start/continue loading (asset loader handles duplicates internally)
364+
assetLoader.load(this.environmentUrl);
365+
}
366+
367+
/**
368+
* Unloads environment resource if currently loaded.
369+
*
370+
* @param {GSplatAssetLoaderBase} assetLoader - Asset loader used to unload the resource.
371+
*/
372+
unloadEnvironmentResource(assetLoader) {
373+
if (this.environmentResource && this.environmentUrl) {
374+
assetLoader.unload(this.environmentUrl);
375+
this.environmentResource = null;
376+
}
377+
}
283378
}
284379

285380
export { GSplatOctree };

0 commit comments

Comments
 (0)