diff --git a/examples/editor/index.html b/examples/editor/index.html
index 946ab9d..fc5a851 100644
--- a/examples/editor/index.html
+++ b/examples/editor/index.html
@@ -79,6 +79,7 @@
const canvas = document.getElementById("canvas");
const renderer = new THREE.WebGLRenderer({ canvas });
const spark = new SparkRenderer({ renderer });
+ scene.add(spark);
function handleResize() {
const width = canvas.clientWidth;
diff --git a/examples/envmap/index.html b/examples/envmap/index.html
index 60c08a3..b260e1f 100644
--- a/examples/envmap/index.html
+++ b/examples/envmap/index.html
@@ -61,6 +61,7 @@
// Explicitly create a SparkRenderer to render environment maps
const spark = new SparkRenderer({ renderer });
+ scene.add(spark);
const splatURL = await getAssetFileURL("fireplace.spz");
const packedSplats = new PackedSplats({ url: splatURL });
diff --git a/examples/multiple-viewpoints/index.html b/examples/multiple-viewpoints/index.html
index c3ff97e..90984c2 100644
--- a/examples/multiple-viewpoints/index.html
+++ b/examples/multiple-viewpoints/index.html
@@ -47,6 +47,7 @@
// Explicitly create a SparkRenderer in the scene to spawn new viewpoints
const spark = new SparkRenderer({ renderer });
+ scene.add(spark);
const splatURL = await getAssetFileURL("butterfly.spz");
const butterfly = new SplatMesh({ url: splatURL });
diff --git a/src/SparkRenderer.ts b/src/SparkRenderer.ts
index 6562ec3..226aef8 100644
--- a/src/SparkRenderer.ts
+++ b/src/SparkRenderer.ts
@@ -40,48 +40,6 @@ import {
// of 5 to avoid excessive memory usage.
const MAX_ACCUMULATORS = 5;
-// Scene.onBeforeRender monkey-patch to
-// inject a SparkRenderer into a scene with SplatMeshes if there isn't
-// one already. Restore original Scene.onBeforeRenderer and Scene.add when done.
-let hasSplatMesh = false;
-let hasSparkRenderer = false;
-
-let sparkRendererInstance: SparkRenderer;
-
-function containsSplatMesh(object3D: THREE.Object3D) {
- let hasSplatMesh = false;
- if (object3D instanceof SplatMesh) {
- return true;
- }
- object3D.traverse((child: THREE.Object3D) => {
- hasSplatMesh = hasSplatMesh || child instanceof SplatMesh;
- });
- return hasSplatMesh;
-}
-
-const sceneAdd = THREE.Scene.prototype.add;
-THREE.Scene.prototype.add = function (object: THREE.Object3D) {
- hasSplatMesh = hasSplatMesh || containsSplatMesh(object);
- hasSparkRenderer = hasSparkRenderer || object instanceof SparkRenderer;
- sceneAdd.call(this, object);
- return this;
-};
-
-const sceneOnBeforeRender = THREE.Scene.prototype.onBeforeRender;
-THREE.Scene.prototype.onBeforeRender = function (
- renderer: THREE.WebGLRenderer,
-) {
- if (!hasSplatMesh) {
- return;
- }
- if (!hasSparkRenderer) {
- const spark = sparkRendererInstance || new SparkRenderer({ renderer });
- this.add(spark);
- }
- THREE.Scene.prototype.onBeforeRender = sceneOnBeforeRender;
- THREE.Scene.prototype.add = sceneAdd;
-};
-
export type SparkRendererOptions = {
/**
* Pass in your THREE.WebGLRenderer instance so Spark can perform work
@@ -368,8 +326,6 @@ export class SparkRenderer extends THREE.Mesh {
this.prepareViewpoint(this.viewpoint);
this.clock = options.clock ? cloneClock(options.clock) : new THREE.Clock();
-
- sparkRendererInstance = this;
}
static makeUniforms() {
diff --git a/src/SplatMesh.ts b/src/SplatMesh.ts
index 2940b16..3ab66cf 100644
--- a/src/SplatMesh.ts
+++ b/src/SplatMesh.ts
@@ -7,6 +7,7 @@ import {
type SplatEncoding,
} from "./PackedSplats";
import { type RgbaArray, readRgbaArray } from "./RgbaArray";
+import { SparkRenderer } from "./SparkRenderer";
import { SplatEdit, SplatEditSdf, SplatEdits } from "./SplatEdit";
import {
type GsplatModifier,
@@ -235,6 +236,8 @@ export class SplatMesh extends SplatGenerator {
}
}
}
+
+ this.add(createRendererDetectionMesh());
}
async asyncInitialize(options: SplatMeshOptions) {
@@ -933,3 +936,39 @@ export function evaluateSH3(
`),
}).outputs.rgb;
}
+
+const EMPTY_GEOMETRY = new THREE.BufferGeometry();
+const EMPTY_MATERIAL = new THREE.ShaderMaterial();
+
+// Creates an empty mesh to hook into Three.js rendering.
+// This is used to detect if a SparkRenderer is present in the scene.
+// If not, one will be injected automatically.
+function createRendererDetectionMesh(): THREE.Mesh {
+ const mesh = new THREE.Mesh(EMPTY_GEOMETRY, EMPTY_MATERIAL);
+ mesh.frustumCulled = false;
+ mesh.onBeforeRender = function (renderer, scene) {
+ if (!scene.isScene) {
+ // The SplatMesh is part of render call that doesn't have a Scene at its root
+ // Don't auto-inject a renderer.
+ this.removeFromParent();
+ return;
+ }
+
+ // Check if the scene has a SparkRenderer instance
+ let hasSparkRenderer = false;
+ scene.traverse((c) => {
+ if (c instanceof SparkRenderer) {
+ hasSparkRenderer = true;
+ }
+ });
+
+ if (!hasSparkRenderer) {
+ // No spark renderer present in the scene, inject one.
+ scene.add(new SparkRenderer({ renderer }));
+ }
+
+ // Remove mesh to stop checking
+ this.removeFromParent();
+ };
+ return mesh;
+}