3d-arena / src /routes /viewers /SplatViewer.ts
dylanebert's picture
dylanebert HF staff
support topology-only outputs
9c554be
import type { IViewer } from "./IViewer";
import * as SPLAT from "gsplat";
export class SplatViewer implements IViewer {
canvas: HTMLCanvasElement;
renderer: SPLAT.WebGLRenderer;
scene: SPLAT.Scene;
camera: SPLAT.Camera;
controls: SPLAT.OrbitControls;
splat: SPLAT.Splat | null;
disposed: boolean = false;
topoOnly: boolean = false;
vertexCount: number = 0;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.renderer = new SPLAT.WebGLRenderer(canvas);
this.renderer.renderProgram.outlineColor = new SPLAT.Color32(180, 180, 180);
this.scene = new SPLAT.Scene();
this.camera = new SPLAT.Camera();
this.controls = new SPLAT.OrbitControls(this.camera, canvas);
this.controls.orbitSpeed = 3.0;
this.splat = null;
this.handleResize = this.handleResize.bind(this);
}
async loadScene(url: string, loadingBarCallback?: (progress: number) => void, topoOnly?: boolean) {
this.topoOnly = topoOnly ?? false;
if (url.endsWith(".splat")) {
this.splat = await SPLAT.Loader.LoadAsync(url, this.scene, (progress) => {
loadingBarCallback?.(progress);
});
} else if (url.endsWith(".ply")) {
this.splat = await SPLAT.PLYLoader.LoadAsync(url, this.scene, (progress) => {
loadingBarCallback?.(progress);
});
} else {
throw new Error("Unsupported file format");
}
this.vertexCount = this.splat.data.vertexCount;
const frame = () => {
this.controls.update();
this.renderer.render(this.scene, this.camera);
if (!this.disposed) {
requestAnimationFrame(frame);
}
};
this.disposed = false;
this.handleResize();
window.addEventListener("resize", this.handleResize);
requestAnimationFrame(frame);
}
handleResize() {
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight);
}
dispose() {
window.removeEventListener("resize", this.handleResize);
this.controls.dispose();
this.renderer.dispose();
this.disposed = true;
}
async capture(): Promise<string | null> {
return new Promise((resolve) => {
requestAnimationFrame(() => {
const offscreenCanvas = document.createElement("canvas");
offscreenCanvas.width = 512;
offscreenCanvas.height = 512;
const offscreenContext = offscreenCanvas.getContext("2d") as CanvasRenderingContext2D;
const x = (this.canvas.width - offscreenCanvas.width) / 2;
const y = (this.canvas.height - offscreenCanvas.height) / 2;
offscreenContext.drawImage(
this.canvas,
x,
y,
offscreenCanvas.width,
offscreenCanvas.height,
0,
0,
offscreenCanvas.width,
offscreenCanvas.height
);
const dataUrl = offscreenCanvas.toDataURL("image/png");
offscreenCanvas.remove();
resolve(dataUrl);
});
});
}
setRenderMode(mode: string): void {
if (!this.splat) return;
if (mode === "wireframe") {
this.splat.selected = true;
} else {
this.splat.selected = false;
}
}
getStats(): { name: string; value: any }[] {
return [];
}
}