Overview
ThreeView renders on demand by default (animation: false). SparkJS, however, updates sort / LoD asynchronously in workers in a camera-dependent way. After the camera comes to rest, SparkJS may finish computing a new sort order but Navara isn't notified, so the rendered splat can remain in a stale state.
Current behavior
SplatMeshDesc.createMesh() calls mesh.initialized.then(() => this.requestUpdate()) only once.
Expected behavior
Request a Navara frame whenever SparkJS's sort / LoD state changes. Avoid forcing continuous rendering (animation: true); use a SparkJS event hook (see Approach below).
Approach
SparkJS exposes a public onDirty option in SparkRendererOptions, fired internally whenever sort / LoD state changes:
if (!this.dirty) {
this.dirty = true;
this.onDirty?.();
}
Wire this callback to MeshDesc.requestUpdate() so Navara schedules a frame whenever SparkJS finishes an async update.
The shared SparkRenderer (one per transparent scene, refCount-managed) needs to fan out a single onDirty to every SplatMeshDesc listener.
This avoids forcing continuous rendering (animation: true).
Related upstream issue
onDirty itself may not fire under certain paged LoD + camera-static conditions (the upstream dirty chain can break when the LoD worker is busy and the camera is static, so background paged fetches don't trigger a re-render).
The Navara-side onDirty → requestUpdate bridge is therefore necessary but not always sufficient: when the upstream chain breaks, no event reaches Navara to relay.
Scope
- This issue (Navara side): subscribe to
onDirty and request a frame. Sufficient for sort-order refinement after camera stop and for non-paged LoD splats.
- Upstream (sparkjsdev/spark#298): additional
setDirty() calls at paged-fetch completion etc. Pending.
Overview
ThreeViewrenders on demand by default (animation: false). SparkJS, however, updates sort / LoD asynchronously in workers in a camera-dependent way. After the camera comes to rest, SparkJS may finish computing a new sort order but Navara isn't notified, so the rendered splat can remain in a stale state.Current behavior
SplatMeshDesc.createMesh()callsmesh.initialized.then(() => this.requestUpdate())only once.Expected behavior
Request a Navara frame whenever SparkJS's sort / LoD state changes. Avoid forcing continuous rendering (
animation: true); use a SparkJS event hook (see Approach below).Approach
SparkJS exposes a public
onDirtyoption inSparkRendererOptions, fired internally whenever sort / LoD state changes:Wire this callback to
MeshDesc.requestUpdate()so Navara schedules a frame whenever SparkJS finishes an async update.The shared
SparkRenderer(one per transparent scene, refCount-managed) needs to fan out a singleonDirtyto everySplatMeshDesclistener.This avoids forcing continuous rendering (
animation: true).Related upstream issue
onDirtyitself may not fire under certain paged LoD + camera-static conditions (the upstream dirty chain can break when the LoD worker is busy and the camera is static, so background paged fetches don't trigger a re-render).The Navara-side
onDirty→requestUpdatebridge is therefore necessary but not always sufficient: when the upstream chain breaks, no event reaches Navara to relay.Scope
onDirtyand request a frame. Sufficient for sort-order refinement after camera stop and for non-paged LoD splats.setDirty()calls at paged-fetch completion etc. Pending.