Skip to content

Commit 0c07ee2

Browse files
authored
Use child mesh in SplatMesh to auto-inject SparkRenderer instead of monkey-patching (#158)
1 parent 350a677 commit 0c07ee2

5 files changed

Lines changed: 42 additions & 44 deletions

File tree

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
@@ -61,6 +61,7 @@
6161

6262
// Explicitly create a SparkRenderer to render environment maps
6363
const spark = new SparkRenderer({ renderer });
64+
scene.add(spark);
6465

6566
const splatURL = await getAssetFileURL("fireplace.spz");
6667
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
@@ -47,6 +47,7 @@
4747

4848
// Explicitly create a SparkRenderer in the scene to spawn new viewpoints
4949
const spark = new SparkRenderer({ renderer });
50+
scene.add(spark);
5051

5152
const splatURL = await getAssetFileURL("butterfly.spz");
5253
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
@@ -368,8 +326,6 @@ export class SparkRenderer extends THREE.Mesh {
368326
this.prepareViewpoint(this.viewpoint);
369327

370328
this.clock = options.clock ? cloneClock(options.clock) : new THREE.Clock();
371-
372-
sparkRendererInstance = this;
373329
}
374330

375331
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)