Skip to content

Commit 7f83f04

Browse files
committed
Use child mesh in SplatMesh to auto-inject SparkRenderer instead of monkey-patching
1 parent 0edfc8d commit 7f83f04

File tree

5 files changed

+42
-44
lines changed

5 files changed

+42
-44
lines changed

examples/editor/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
const canvas = document.getElementById("canvas");
8080
const renderer = new THREE.WebGLRenderer({ canvas });
8181
const spark = new SparkRenderer({ renderer });
82+
scene.add(spark);
8283

8384
function handleResize() {
8485
const width = canvas.clientWidth;

examples/envmap/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454

5555
// Explicitly create a SparkRenderer to render environment maps
5656
const spark = new SparkRenderer({ renderer });
57+
scene.add(spark);
5758

5859
const splatURL = await getAssetFileURL("fireplace.spz");
5960
const packedSplats = new PackedSplats({ url: splatURL });

examples/multiple-viewpoints/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
// Explicitly create a SparkRenderer in the scene to spawn new viewpoints
4242
const spark = new SparkRenderer({ renderer });
43+
scene.add(spark);
4344

4445
const splatURL = await getAssetFileURL("butterfly.spz");
4546
const butterfly = new SplatMesh({ url: splatURL });

src/SparkRenderer.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,48 +40,6 @@ import {
4040
// of 5 to avoid excessive memory usage.
4141
const MAX_ACCUMULATORS = 5;
4242

43-
// Scene.onBeforeRender monkey-patch to
44-
// inject a SparkRenderer into a scene with SplatMeshes if there isn't
45-
// one already. Restore original Scene.onBeforeRenderer and Scene.add when done.
46-
let hasSplatMesh = false;
47-
let hasSparkRenderer = false;
48-
49-
let sparkRendererInstance: SparkRenderer;
50-
51-
function containsSplatMesh(object3D: THREE.Object3D) {
52-
let hasSplatMesh = false;
53-
if (object3D instanceof SplatMesh) {
54-
return true;
55-
}
56-
object3D.traverse((child: THREE.Object3D) => {
57-
hasSplatMesh = hasSplatMesh || child instanceof SplatMesh;
58-
});
59-
return hasSplatMesh;
60-
}
61-
62-
const sceneAdd = THREE.Scene.prototype.add;
63-
THREE.Scene.prototype.add = function (object: THREE.Object3D) {
64-
hasSplatMesh = hasSplatMesh || containsSplatMesh(object);
65-
hasSparkRenderer = hasSparkRenderer || object instanceof SparkRenderer;
66-
sceneAdd.call(this, object);
67-
return this;
68-
};
69-
70-
const sceneOnBeforeRender = THREE.Scene.prototype.onBeforeRender;
71-
THREE.Scene.prototype.onBeforeRender = function (
72-
renderer: THREE.WebGLRenderer,
73-
) {
74-
if (!hasSplatMesh) {
75-
return;
76-
}
77-
if (!hasSparkRenderer) {
78-
const spark = sparkRendererInstance || new SparkRenderer({ renderer });
79-
this.add(spark);
80-
}
81-
THREE.Scene.prototype.onBeforeRender = sceneOnBeforeRender;
82-
THREE.Scene.prototype.add = sceneAdd;
83-
};
84-
8543
export type SparkRendererOptions = {
8644
/**
8745
* Pass in your THREE.WebGLRenderer instance so Spark can perform work
@@ -367,8 +325,6 @@ export class SparkRenderer extends THREE.Mesh {
367325
this.prepareViewpoint(this.viewpoint);
368326

369327
this.clock = options.clock ? cloneClock(options.clock) : new THREE.Clock();
370-
371-
sparkRendererInstance = this;
372328
}
373329

374330
static makeUniforms() {

src/SplatMesh.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type SplatEncoding,
88
} from "./PackedSplats";
99
import { type RgbaArray, readRgbaArray } from "./RgbaArray";
10+
import { SparkRenderer } from "./SparkRenderer";
1011
import { SplatEdit, SplatEditSdf, SplatEdits } from "./SplatEdit";
1112
import {
1213
type GsplatModifier,
@@ -235,6 +236,8 @@ export class SplatMesh extends SplatGenerator {
235236
}
236237
}
237238
}
239+
240+
this.add(createRendererDetectionMesh());
238241
}
239242

240243
async asyncInitialize(options: SplatMeshOptions) {
@@ -933,3 +936,39 @@ export function evaluateSH3(
933936
`),
934937
}).outputs.rgb;
935938
}
939+
940+
const EMPTY_GEOMETRY = new THREE.BufferGeometry();
941+
const EMPTY_MATERIAL = new THREE.ShaderMaterial();
942+
943+
// Creates an empty mesh to hook into Three.js rendering.
944+
// This is used to detect if a SparkRenderer is present in the scene.
945+
// If not, one will be injected automatically.
946+
function createRendererDetectionMesh(): THREE.Mesh {
947+
const mesh = new THREE.Mesh(EMPTY_GEOMETRY, EMPTY_MATERIAL);
948+
mesh.frustumCulled = false;
949+
mesh.onBeforeRender = function (renderer, scene) {
950+
if (!scene.isScene) {
951+
// The SplatMesh is part of render call that doesn't have a Scene at its root
952+
// Don't auto-inject a renderer.
953+
this.removeFromParent();
954+
return;
955+
}
956+
957+
// Check if the scene has a SparkRenderer instance
958+
let hasSparkRenderer = false;
959+
scene.traverse((c) => {
960+
if (c instanceof SparkRenderer) {
961+
hasSparkRenderer = true;
962+
}
963+
});
964+
965+
if (!hasSparkRenderer) {
966+
// No spark renderer present in the scene, inject one.
967+
scene.add(new SparkRenderer({ renderer }));
968+
}
969+
970+
// Remove mesh to stop checking
971+
this.removeFromParent();
972+
};
973+
return mesh;
974+
}

0 commit comments

Comments
 (0)