Template project for building WebXR worldmodel experiences with IWSDK, SparkJS Gaussian splats, and spatial UI.
Prerequisites: Node >=20.19.0, a WebXR-capable browser, and an optional headset for immersive testing.
Install and run:
npm install
npm run dev- Attach
GaussianSplatLoaderto an entity (already done insrc/index.ts). - Set
splatUrlto your local or remote.spz/.ply. - Optionally set
meshUrlto a.gltf/.glbcollision mesh. - Keep large splat files outside the repo (for example in object storage) and load via URL.
- Loads and unloads local or remote
.spz/.plysplats as children of an entity transform. - Supports optional collider loading through
meshUrl(GLTFLoader) for locomotion hit testing. - Includes timeout-based loading and explicit error propagation.
- API surface:
- Component props:
splatUrl,meshUrl,autoLoad,animate,enableLod,lodSplatScale - System methods:
load(entity, { animate? }),unload(entity, { animate? }),replayAnimation(entity, { duration? })
- Component props:
- Typical usage: add
GaussianSplatLoaderto an entity, set URLs, then letautoLoadhandle startup or call system methods for runtime swaps.
- Uses
.spzor.plyworldmodel assets. - Position, scale, and pivot are controlled by the hosting entity transform.
- Runtime loading is handled by
GaussianSplatLoader.
- World Labs Marble outputs can be exported as Gaussian splats and used as source assets.
- For interaction and locomotion, pair splats with a separate collision mesh.
- If using remote-generated assets, integrate your own fetch/API flow before assigning
splatUrl. - Host large splat files outside the repository (object storage/CDN is recommended).
- Open-source Gaussian splat renderer from the World Labs team: https://sparkjs.dev/
- Provides performant Gaussian splat rendering for WebGL2/WebXR in Three.js-based scenes.
- This project uses SparkJS 2.0 preview with Level-of-Detail (LoD) enabled.
Level of Detail (LoD)
- LoD automatically adjusts splat quality based on distance: nearby areas render at full detail while distant areas use fewer, coarser splats.
- This keeps frame rate stable on resource-constrained devices (Quest, PICO) by maintaining a fixed rendering budget instead of always rendering every splat.
- LoD is enabled by default on the
GaussianSplatLoadercomponent (enableLod: true). AdjustlodSplatScaleto trade quality for performance (lower = faster, higher = sharper). - Runtime LoD (default): Point
splatUrlat any.spzfile. The LoD tree is computed in-browser on load (~4s for 500K splats). No extra tooling needed. - Pre-built LoD (faster startup): Bake the LoD tree offline so the browser skips the computation. Clone the SparkJS repo, build the Rust CLI, and run:
cargo build --manifest-path rust/build-lod/Cargo.toml --release
./rust/target/release/build-lod --spz your-splat.spz
# produces your-splat-lod.spzThree.js Version & Compatibility
- This project uses
super-three@0.181.0(Three.js r181), upgraded from r177 to support SparkJS 2.0. - IWSDK externalizes its Three.js dependency, so it automatically uses the project's version. A custom Vite plugin (
deduplicateThreeinvite.config.ts) redirects IWSDK's bundled r177 imports to the project's single r181 instance, preventing duplicate Three.js modules. - A camera clone patch in
gaussianSplatLoader.tsprevents a per-frame crash caused by SparkJS's LoD system deep-cloning IWSDK's non-clonable UI objects. - These workarounds are temporary. IWSDK has already migrated to r181 on GitHub (Dec 2025) but has not published a new npm release since v0.2.2 (Nov 2025). Once the next IWSDK version ships, the Vite dedup plugin can be removed. The camera clone patch remains needed until SparkJS changes its
driveLodbehavior.
IWSDK (Immersive Web SDK) is a WebXR-focused ECS framework for building interactive 3D/XR apps with built-in systems such as locomotion, grabbing, spatial UI, and XR session management. see https://elixrjs.io/
How to use- The project enables the IWSDK local simulator via
@iwsdk/vite-plugin-iwerinvite.config.ts. - Run
npm run devon localhost, open the app in a desktop browser, and use the injected simulator controls to emulate headset/controller behavior during iteration.
see https://iwsdk.dev/guides/10-spatial-ui-uikitml.html The default render order can conflict with splat rendering, so this template applies a render-order/depth configuration to keep IWSDK UI above splats. Pointer depth behavior is also handled so panel interaction remains reliable.
- Built-in interactions such as one-/two-hand grab and distance grab: https://iwsdk.dev/guides/06-built-in-interactions.html
- Locomotion concepts (teleport, slide/smooth locomotion, turn): https://iwsdk.dev/concepts/locomotion/
- This template enables locomotion and grabbing in
World.create(...).
- Build/deploy basics: https://elixrjs.io/guides/08-build-deploy.html
- Local testing:
npm run dev - Preview production build locally:
npm run build && npm run preview - recommended deployment workflow: deploy static build output (
dist/) on Cloudflare.
This project was created as part of the SensAI Hack(https://sensaihack.com/) - Worlds in Action.
Powered by SensAI Hackademy (https://sensaihackademy.com/).



