Skip to content

feat(gsplat): expose parsed manifest on GSplatOctreeResource#8699

Merged
mvaligursky merged 4 commits into
playcanvas:mainfrom
MattiasVik:feat/gsplat-expose-octree-resource-data
May 8, 2026
Merged

feat(gsplat): expose parsed manifest on GSplatOctreeResource#8699
mvaligursky merged 4 commits into
playcanvas:mainfrom
MattiasVik:feat/gsplat-expose-octree-resource-data

Conversation

@MattiasVik
Copy link
Copy Markdown
Contributor

Summary

GSplatOctreeParser fetches and parses the LOD manifest, then hands the parsed object to GSplatOctreeResource's constructor. The resource retains the fields GSplatOctree consumes (lodLevels, filenames, tree.bound, environment) and discards the rest.

That's a problem for any consumer that ships custom/extension fields in the manifest (per-asset render settings, app-specific metadata, framing defaults, etc.) — they have no way to read those fields back without re-fetching and re-parsing the same JSON file PlayCanvas already loaded.

This PR retains a reference to the parsed object on resource.data so consumers can read whatever fields they wrote into the manifest.

Diff

 class GSplatOctreeResource {
     /** @type {BoundingBox} */
     aabb = new BoundingBox();

     /**
      * Version counter for centers array changes. Always 0 for octree resources (static).
      *
      * @ignore
      */
     centersVersion = 0;

     /** @type {GSplatOctree|null} */
     octree;

+    /**
+     * Raw parsed manifest data, retained for consumers that read custom or extension
+     * fields the octree itself does not consume (for example application-specific
+     * metadata accompanying a `lod-meta.json`).
+     *
+     * @type {object}
+     */
+    data;
+
     /**
      * @param {string} assetFileUrl - The file URL of the container asset.
      * @param {object} data - Parsed JSON data.
      * @param {object} assetLoader - Asset loader instance (framework-level object).
      */
     constructor(assetFileUrl, data, assetLoader) {
         this.octree = new GSplatOctree(assetFileUrl, data);
         this.octree.assetLoader = assetLoader;
         this.aabb.setMinMax(new Vec3(data.tree.bound.min), new Vec3(data.tree.bound.max));
+        this.data = data;
     }

Memory cost

The parsed JSON object already exists during construction (the constructor parameter holds it). This change just keeps a reference to the same object — no new allocation. Per-asset cost is the size of the parsed manifest, typically a few KB, dominated in scale by the octree's per-node AABB storage that the resource already retains.

If a future consumer wanted to opt out of retention they could resource.data = null after reading what they need.

Use case (for context)

We carry per-asset metadata in lod-meta.json alongside the octree manifest — fields like aa, shadow, lodBaseDistance, and a defaults block of view/render settings — that PlayCanvas itself doesn't need to know about. Without this PR we have to fetch lod-meta.json ourselves before letting the asset load (so the data is available before entity.addComponent('gsplat', { ... })), causing a duplicate GET on every LOD load. With this PR we read everything from modelAsset.resource.data inside the asset's ready handler.

No breaking changes

  • Adds one class field (data) and one assignment.
  • Default null until the constructor runs (matches octree).
  • Doesn't touch any existing field, method, or signature.
  • Existing consumers see no behavior difference.

Verified locally

Built and used in production via patch-package on PC 2.18 with the same diff (line numbers shifted from the post-#8683 refactor); upstream PR adapted to the current main. Both confirmed working against multi-level LOD assets.

Checklist

  • I have read the contributing guidelines
  • My code follows the project's coding standards
  • This PR focuses on a single change

GSplatOctreeParser parses the LOD manifest and hands it to the
resource constructor, but the resource only retains the fields
GSplatOctree consumes (lodLevels, filenames, tree.bound, environment)
and discards the rest. Consumers that ship custom/extension fields in
the manifest (per-asset render settings, app-specific metadata, etc.)
have no way to read them back without re-fetching and re-parsing the
same JSON file.

Retain a reference to the parsed object on resource.data so consumers
can read whatever fields they wrote into the manifest.

The parsed JSON object already exists during construction; this just
holds a reference to it (typically a few KB per LOD asset, dominated
in scale by the octree's per-node AABB storage that the resource
already retains). No new allocation; no new behavior for existing
consumers.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the unified GSplat octree container resource to retain and expose the raw parsed LOD manifest JSON so downstream consumers can access custom/extension manifest fields without re-fetching and re-parsing the manifest.

Changes:

  • Add a data field to GSplatOctreeResource to store the parsed manifest object.
  • Assign the constructor’s parsed JSON parameter to this.data for later consumer access.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scene/gsplat-unified/gsplat-octree.resource.js
Comment thread src/scene/gsplat-unified/gsplat-octree.resource.js
Comment thread src/scene/gsplat-unified/gsplat-octree.resource.js
MattiasVik added 2 commits May 8, 2026 10:35
Per review feedback on playcanvas#8699: the data field is null after destroy(),
so declare it as @type {object|null} initialized to null, and add
this.data = null to destroy() so the parsed manifest is released
along with the octree reference.
Per @mvaligursky review on playcanvas#8699: the tree hierarchy is the bulk of
the parsed manifest, and GSplatOctree builds its own structure from
it during construction — keeping the original data.tree reference on
resource.data is dead memory. Null it after the octree is built and
update the field JSDoc to point consumers at resource.octree for
node-hierarchy access.
@mvaligursky
Copy link
Copy Markdown
Contributor

Also, please ignore the copilot's suggestion of data being nullable, that's pointless and makes user wonder if in some cases they get no data. I resolved thos ecomments, and hoped you'd ignore it too ;)

Per @mvaligursky on playcanvas#8699: `resource.data` is always populated for the
lifetime of the resource, so the nullable type annotation invites
unnecessary defensive coding by users. Reverts commit 752a7c2; keeps
the data.tree memory cleanup from 8460080.
@MattiasVik
Copy link
Copy Markdown
Contributor Author

Also, please ignore the copilot's suggestion of data being nullable, that's pointless and makes user wonder if in some cases they get no data. I resolved thos ecomments, and hoped you'd ignore it too ;)

Got it, reverted in 4155a72. Apologies — I jumped on the Copilot suggestion before you'd had a chance to weigh in.

Copy link
Copy Markdown
Contributor

@mvaligursky mvaligursky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for this!

@mvaligursky mvaligursky merged commit a90e22e into playcanvas:main May 8, 2026
6 of 8 checks passed
@MattiasVik MattiasVik deleted the feat/gsplat-expose-octree-resource-data branch May 8, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants