Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/editor/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions examples/envmap/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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 });
Expand Down
1 change: 1 addition & 0 deletions examples/multiple-viewpoints/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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 });
Expand Down
44 changes: 0 additions & 44 deletions src/SparkRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -367,8 +325,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() {
Expand Down
39 changes: 39 additions & 0 deletions src/SplatMesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -235,6 +236,8 @@ export class SplatMesh extends SplatGenerator {
}
}
}

this.add(createRendererDetectionMesh());
}

async asyncInitialize(options: SplatMeshOptions) {
Expand Down Expand Up @@ -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;
}
Loading